fix: uniformize attachment ref creation
This commit is contained in:
@@ -75,8 +75,8 @@
|
||||
- 应用后附件以胶囊 chip 挂在输入框上方;发出的消息内附件渲染为纯文本胶囊 chip(名称 + 小图标),**默认无缩略图,鼠标悬浮才浮出缩略图预览**。
|
||||
- 附件领域形状:统一为画布资源 / 素材库对象引用(`resourceId` / `assetId` + 可选 `objectKey`),不存在只属于对话的第三种图;单条消息上限 9 张(前后端共同校验)。前端可携带展示用 `imageSrc` / `thumbnailSrc`,后端必须按当前工程和当前账号重新归一、校验归属与 `objectKey`。
|
||||
- 附件 `label` 是人类可读的展示元数据,统一限制为最多 24 个 Unicode 码点。归一化时先去掉首尾空白,删除控制字符以及除 `-`、`_`、`.` 之外的 ASCII 标点,把连续空白折叠为一个半角空格,再按 24 码点截断;只含被过滤字符的 label 视为缺失。中文等非 ASCII 标点不属于本轮过滤范围。
|
||||
- 前端在创建画布、素材库和粘贴上传附件引用时先执行上述归一化;原 label 无有效内容时依次归一化并使用调用方提供的 fallback(默认 `referenceId`)和固定文案「图片」。后端不能信任前端结果:校验当前工程 / 当前账号归属和 `objectKey` 后,必须用同一套字符规则、同一 24 码点上限再次归一化并重建权威附件;素材库附件的提交 label 缺失或过滤为空时,才回退到同样归一化后的素材库 label。前后端常量和规则必须保持同步。
|
||||
- 输入区附件临时状态统一收口到 `useConversationAttachments`,选择弹窗由独立的 `AttachmentPicker` 负责纯展示;选择、引用、粘贴上传完成、移除、发送清空和失败恢复都必须经同一最新状态更新入口。引用历史消息附件时先保留消息中的展示快照,最终发送前再按 `source + referenceId` 从当前画布和素材库选项刷新,避免提前刷新后又被失败恢复的旧快照覆盖。发送失败时,已发送附件必须与等待期间新增的附件去重合并,不得因输入区已非空而丢弃;同一 `source + referenceId` 冲突时保留等待期间的当前草稿快照,失败请求快照只补充缺失 identity。合并后超过 9 张时优先保留等待期间的最新附件,不恢复失败请求的附件,并立即显示上限错误。异步粘贴完成时基于当时的最新附件去重并重新校验 9 张上限,不能用上传开始时捕获的旧列表覆盖期间新增的引用。
|
||||
- 前端在创建画布、素材库和粘贴上传附件引用,以及把历史消息附件重新引用到输入区时,先执行上述归一化;原 label 无有效内容时依次归一化并使用调用方提供的 fallback(默认 `referenceId`)和固定文案「图片」。后端不能信任前端结果:校验当前工程 / 当前账号归属和 `objectKey` 后,必须用同一套字符规则、同一 24 码点上限再次归一化并重建权威附件;素材库附件的提交 label 缺失或过滤为空时,才回退到同样归一化后的素材库 label。前后端常量和规则必须保持同步。
|
||||
- 输入区附件临时状态统一收口到 `useConversationAttachments`,选择弹窗由独立的 `AttachmentPicker` 负责纯展示;选择、引用、粘贴上传完成、移除、发送清空和失败恢复都必须经同一最新状态更新入口。引用历史消息附件时,原消息继续保留存量展示快照;进入输入区的新引用先归一化 label 并保留其他展示快照字段,最终发送前再按 `source + referenceId` 从当前画布和素材库选项刷新,避免提前刷新后又被失败恢复的旧快照覆盖。发送失败时,已发送附件必须与等待期间新增的附件去重合并,不得因输入区已非空而丢弃;同一 `source + referenceId` 冲突时保留等待期间的当前草稿快照,失败请求快照只补充缺失 identity。合并后超过 9 张时优先保留等待期间的最新附件,不恢复失败请求的附件,并立即显示上限错误。异步粘贴完成时基于当时的最新附件去重并重新校验 9 张上限,不能用上传开始时捕获的旧列表覆盖期间新增的引用。
|
||||
|
||||
## 工具调用确认展示契约
|
||||
|
||||
|
||||
+6
-4
@@ -549,7 +549,7 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
expect(screen.queryByRole('option', { name: 'conversation-1' })).toBeNull();
|
||||
});
|
||||
|
||||
it('references a historical pasted attachment outside the current canvas', async () => {
|
||||
it('normalizes a referenced historical attachment outside the current canvas', async () => {
|
||||
const client = createClient();
|
||||
vi.mocked(client.getConversation).mockResolvedValue({
|
||||
conversationId: 'conversation-1',
|
||||
@@ -567,7 +567,7 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
objectKey: 'editor/agent-paste/historical.png',
|
||||
imageSrc: '/generated/historical-paste.png',
|
||||
thumbnailSrc: null,
|
||||
label: '历史粘贴图',
|
||||
label: ' 历史(粘贴):图 ',
|
||||
width: 320,
|
||||
height: 240,
|
||||
},
|
||||
@@ -590,11 +590,11 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const historicalAttachment = await screen.findByText('历史粘贴图');
|
||||
const historicalAttachment = await screen.findByText('历史(粘贴):图');
|
||||
fireEvent.contextMenu(historicalAttachment.closest('.group')!);
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '引用' }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('历史粘贴图')).toHaveLength(2);
|
||||
expect(screen.getByText('历史粘贴图')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送' }));
|
||||
@@ -618,6 +618,8 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
expect(screen.getByText('历史(粘贴):图')).toBeTruthy();
|
||||
expect(screen.getByText('历史粘贴图')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('refreshes an attachment matched by source and reference id when sending', async () => {
|
||||
|
||||
@@ -265,7 +265,7 @@ export function useConversationAttachments({
|
||||
const referenceContextAsset = useCallback(
|
||||
(asset: EditorAgentContextAsset) => {
|
||||
if (asset.kind === 'attachment') {
|
||||
const directAttachment: EditorAgentAttachmentRef = {
|
||||
const directAttachment = createEditorAgentAttachmentRef({
|
||||
source: asset.source,
|
||||
referenceId: asset.referenceId,
|
||||
objectKey: asset.objectKey,
|
||||
@@ -274,7 +274,7 @@ export function useConversationAttachments({
|
||||
label: asset.label,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
};
|
||||
});
|
||||
return directAttachment.referenceId.trim() &&
|
||||
directAttachment.imageSrc.trim()
|
||||
? appendAttachments([directAttachment])
|
||||
|
||||
Reference in New Issue
Block a user