feat: editor agent include attachment human readable label to context
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// 画布Agent对话契约:会话元数据存 SpacetimeDB,消息正文整体存 OSS(editor-agent/{conversationId}.json)。
|
||||
|
||||
export const EDITOR_AGENT_MAX_ATTACHMENTS = 9;
|
||||
export const EDITOR_AGENT_ATTACHMENT_LABEL_MAX_CODE_POINTS = 10;
|
||||
export const EDITOR_AGENT_ERROR_MESSAGE_PREFIX = 'ERROR ';
|
||||
|
||||
export type EditorAgentMessageRole = 'user' | 'assistant' | 'system';
|
||||
@@ -13,17 +14,39 @@ export type EditorAgentToolCallStatus =
|
||||
|
||||
export type EditorAgentAttachmentSource = 'canvas_resource' | 'library_asset';
|
||||
|
||||
function normalizeEditorAgentAttachmentLabel(
|
||||
label: string | null | undefined,
|
||||
fallback: string,
|
||||
) {
|
||||
const normalized = label?.trim() || fallback.trim() || '图片';
|
||||
return Array.from(normalized)
|
||||
.slice(0, EDITOR_AGENT_ATTACHMENT_LABEL_MAX_CODE_POINTS)
|
||||
.join('');
|
||||
}
|
||||
|
||||
export interface EditorAgentAttachmentRef {
|
||||
source: EditorAgentAttachmentSource;
|
||||
referenceId: string;
|
||||
objectKey?: string | null;
|
||||
imageSrc: string;
|
||||
thumbnailSrc?: string | null;
|
||||
label?: string | null;
|
||||
label: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
}
|
||||
|
||||
export function createEditorAgentAttachmentRef(
|
||||
input: Omit<EditorAgentAttachmentRef, 'label'> & {
|
||||
label?: string | null;
|
||||
},
|
||||
fallbackLabel: string = input.referenceId,
|
||||
): EditorAgentAttachmentRef {
|
||||
return {
|
||||
...input,
|
||||
label: normalizeEditorAgentAttachmentLabel(input.label, fallbackLabel),
|
||||
};
|
||||
}
|
||||
|
||||
export interface EditorAgentGeneratedImage {
|
||||
resourceId?: string | null;
|
||||
objectKey?: string | null;
|
||||
|
||||
@@ -159,8 +159,14 @@ pub async fn editor_agent_message(
|
||||
let mut attachment_info = String::new();
|
||||
attachment_info.push_str("user added these image ids to context: ");
|
||||
for (i, attachment) in attachments.iter().enumerate() {
|
||||
attachment_info
|
||||
.push_str(&format!("[{i}]: {}, ", attachment.clone().into_image_id()));
|
||||
let image_label_str = match &attachment.label {
|
||||
None => "".to_string(),
|
||||
Some(label) => {
|
||||
format!(" {label}")
|
||||
}
|
||||
};
|
||||
let image_id = attachment.clone().into_image_id();
|
||||
attachment_info.push_str(&format!("({i}{image_label_str}): {image_id}, "));
|
||||
}
|
||||
document.messages.push(EditorAgentMessage {
|
||||
id: document.messages.len(),
|
||||
|
||||
@@ -10,7 +10,7 @@ function AttachmentChip({
|
||||
attachment: EditorAgentAttachmentRef;
|
||||
onRemove?: () => void;
|
||||
}) {
|
||||
const label = attachment.label?.trim() || attachment.referenceId;
|
||||
const label = attachment.label;
|
||||
return (
|
||||
<span className="group relative inline-flex max-w-full items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 text-xs text-slate-600 shadow-sm">
|
||||
<ImageIcon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
|
||||
+4
-3
@@ -218,7 +218,7 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
{
|
||||
id: 'layer-1',
|
||||
resourceId: 'resource-1',
|
||||
title: '角色图层',
|
||||
title: '一二三四五六七八九十甲乙',
|
||||
src: '/generated/role.png',
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -284,13 +284,13 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
).toBeNull();
|
||||
fireEvent.click(
|
||||
within(attachmentDialog).getByRole('checkbox', {
|
||||
name: '选择画布图片 角色图层',
|
||||
name: '选择画布图片 一二三四五六七八九十',
|
||||
}),
|
||||
);
|
||||
fireEvent.click(
|
||||
within(attachmentDialog).getByRole('button', { name: '应用' }),
|
||||
);
|
||||
expect(screen.getByText('角色图层')).toBeTruthy();
|
||||
expect(screen.getByText('一二三四五六七八九十')).toBeTruthy();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('发送给画布 Agent'), {
|
||||
target: { value: '参考附件做像素风' },
|
||||
@@ -308,6 +308,7 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
expect.objectContaining({
|
||||
source: 'canvas_resource',
|
||||
referenceId: 'resource-1',
|
||||
label: '一二三四五六七八九十',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
+21
-17
@@ -18,6 +18,7 @@ import {
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
createEditorAgentAttachmentRef,
|
||||
EDITOR_AGENT_MAX_ATTACHMENTS,
|
||||
type EditorAgentAttachmentRef,
|
||||
} from '@/packages/shared/src/contracts';
|
||||
@@ -84,16 +85,17 @@ function createCanvasAttachmentOptions(
|
||||
layers: CanvasLayer[] = [],
|
||||
): EditorAgentAttachmentOption[] {
|
||||
return layers.filter(isImageLayer).map((layer) => {
|
||||
const attachment: EditorAgentAttachmentRef = {
|
||||
const referenceId = layer.resourceId || layer.id;
|
||||
const attachment = createEditorAgentAttachmentRef({
|
||||
source: 'canvas_resource',
|
||||
referenceId: layer.resourceId || layer.id,
|
||||
referenceId,
|
||||
objectKey: layer.objectKey ?? null,
|
||||
imageSrc: layer.src,
|
||||
thumbnailSrc: layer.thumbnailSrc ?? null,
|
||||
label: layer.title,
|
||||
width: layer.width,
|
||||
height: layer.height,
|
||||
};
|
||||
});
|
||||
return {
|
||||
key: attachmentKey(attachment),
|
||||
sourceLabel: '画布',
|
||||
@@ -106,7 +108,7 @@ function createLibraryAttachmentOptions(
|
||||
assets: EditorAsset[] = [],
|
||||
): EditorAgentAttachmentOption[] {
|
||||
return assets.filter(isImageAsset).map((asset) => {
|
||||
const attachment: EditorAgentAttachmentRef = {
|
||||
const attachment = createEditorAgentAttachmentRef({
|
||||
source: 'library_asset',
|
||||
referenceId: asset.id,
|
||||
objectKey: asset.objectKey ?? null,
|
||||
@@ -115,7 +117,7 @@ function createLibraryAttachmentOptions(
|
||||
label: asset.label,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
};
|
||||
});
|
||||
return {
|
||||
key: attachmentKey(attachment),
|
||||
sourceLabel: '素材库',
|
||||
@@ -198,8 +200,7 @@ function AttachmentPickerModal({
|
||||
) : null}
|
||||
<div className="grid grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3">
|
||||
{visibleOptions.map((option) => {
|
||||
const label =
|
||||
option.attachment.label?.trim() || option.attachment.referenceId;
|
||||
const label = option.attachment.label;
|
||||
return (
|
||||
<label
|
||||
key={option.key}
|
||||
@@ -414,16 +415,19 @@ export function EditorAgentConversationPanelView({
|
||||
height,
|
||||
sourceType: 'uploaded',
|
||||
});
|
||||
return {
|
||||
source: 'canvas_resource',
|
||||
referenceId: resource.resourceId,
|
||||
objectKey: resource.objectKey ?? upload.objectKey,
|
||||
imageSrc: resource.imageSrc,
|
||||
thumbnailSrc: null,
|
||||
label: resource.label ?? '粘贴图片',
|
||||
width: resource.width,
|
||||
height: resource.height,
|
||||
};
|
||||
return createEditorAgentAttachmentRef(
|
||||
{
|
||||
source: 'canvas_resource',
|
||||
referenceId: resource.resourceId,
|
||||
objectKey: resource.objectKey ?? upload.objectKey,
|
||||
imageSrc: resource.imageSrc,
|
||||
thumbnailSrc: null,
|
||||
label: resource.label ?? file.name,
|
||||
width: resource.width,
|
||||
height: resource.height,
|
||||
},
|
||||
'粘贴图片',
|
||||
);
|
||||
};
|
||||
function extractClipboardImageFiles(
|
||||
clipboardData: DataTransfer | null,
|
||||
|
||||
+9
-7
@@ -3,10 +3,11 @@
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type {
|
||||
EditorAgentConversationDetail,
|
||||
EditorAgentMessage,
|
||||
EditorAgentMessageResponse,
|
||||
import {
|
||||
createEditorAgentAttachmentRef,
|
||||
type EditorAgentConversationDetail,
|
||||
type EditorAgentMessage,
|
||||
type EditorAgentMessageResponse,
|
||||
} from '../../../../packages/shared/src/contracts/editorAgent.ts';
|
||||
import {
|
||||
EDITOR_AGENT_PATIENCE_NOTICE_DELAY_MS,
|
||||
@@ -545,13 +546,13 @@ describe('useEditorAgentConversation', () => {
|
||||
|
||||
await act(async () => {
|
||||
await result.current.sendMessage('', [
|
||||
{
|
||||
createEditorAgentAttachmentRef({
|
||||
source: 'canvas_resource',
|
||||
referenceId: 'resource-1',
|
||||
objectKey: 'generated-editor-assets/resource-1.png',
|
||||
imageSrc: '/resource-1.png',
|
||||
label: '参考图',
|
||||
},
|
||||
label: '一二三四五六七八九十甲乙',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -563,6 +564,7 @@ describe('useEditorAgentConversation', () => {
|
||||
expect.objectContaining({
|
||||
source: 'canvas_resource',
|
||||
referenceId: 'resource-1',
|
||||
label: '一二三四五六七八九十',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user