fix objectKey mismatch

This commit is contained in:
2026-07-21 17:28:26 +08:00
parent d08e61453f
commit cb6dc4eb38
3 changed files with 31 additions and 24 deletions
@@ -74,7 +74,7 @@
- 附件选择弹窗使用 `PlatformToolModalShell` 承接 portal 主题变量和不透明 panel 背景;不能直接把未注入 `platform-theme``UnifiedModal` portal 到 `document.body`,否则 `--platform-modal-fill` 失效后面板会变透明。
- 应用后附件以胶囊 chip 挂在输入框上方;发出的消息内附件渲染为纯文本胶囊 chip(名称 + 小图标),**默认无缩略图,鼠标悬浮才浮出缩略图预览**。
- 附件领域形状:统一为画布资源 / 素材库对象引用(`resourceId` / `assetId` + 可选 `objectKey`),不存在只属于对话的第三种图;单条消息上限 9 张(前后端共同校验)。前端可携带展示用 `imageSrc` / `thumbnailSrc`,后端必须按当前工程和当前账号重新归一、校验归属与 `objectKey`
- 输入区附件临时状态统一收口到 `useConversationAttachments`,选择弹窗由独立的 `AttachmentPicker` 负责纯展示;选择、引用、粘贴上传完成、移除、发送清空和失败恢复都必须经同一最新状态更新入口。发送失败时,已发送附件必须与等待期间新增的附件去重合并,不得因输入区已非空而丢弃;合并后超过 9 张时优先保留等待期间的最新附件,不恢复失败请求的附件,并立即显示上限错误。异步粘贴完成时基于当时的最新附件去重并重新校验 9 张上限,不能用上传开始时捕获的旧列表覆盖期间新增的引用。
- 输入区附件临时状态统一收口到 `useConversationAttachments`,选择弹窗由独立的 `AttachmentPicker` 负责纯展示;选择、引用、粘贴上传完成、移除、发送清空和失败恢复都必须经同一最新状态更新入口。引用历史消息附件时先保留消息中的展示快照,最终发送前再按 `source + referenceId` 从当前画布和素材库选项刷新,避免提前刷新后又被失败恢复的旧快照覆盖。发送失败时,已发送附件必须与等待期间新增的附件去重合并,不得因输入区已非空而丢弃;合并后超过 9 张时优先保留等待期间的最新附件,不恢复失败请求的附件,并立即显示上限错误。异步粘贴完成时基于当时的最新附件去重并重新校验 9 张上限,不能用上传开始时捕获的旧列表覆盖期间新增的引用。
## 工具调用确认展示契约
@@ -617,7 +617,7 @@ describe('EditorAgentConversationPanelView', () => {
});
});
it('prefers the current attachment matched by source and reference id', async () => {
it('refreshes an attachment matched by source and reference id when sending', async () => {
const client = createClient();
vi.mocked(client.getConversation).mockResolvedValue({
conversationId: 'conversation-1',
@@ -673,7 +673,10 @@ describe('EditorAgentConversationPanelView', () => {
const historicalAttachment = await screen.findByText('旧版附件');
fireEvent.contextMenu(historicalAttachment.closest('.group')!);
fireEvent.click(screen.getByRole('menuitem', { name: '引用' }));
expect(await screen.findByText('最新附件')).toBeTruthy();
await waitFor(() => {
expect(screen.getAllByText('旧版附件')).toHaveLength(2);
});
expect(screen.queryByText('最新附件')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: '发送' }));
await waitFor(() => {
@@ -259,22 +259,22 @@ export function useConversationAttachments({
const referenceContextAsset = useCallback(
(asset: EditorAgentContextAsset) => {
const directAttachment: EditorAgentAttachmentRef | undefined =
asset.kind === 'attachment'
? {
source: asset.source,
referenceId: asset.referenceId,
objectKey: asset.objectKey,
imageSrc: asset.imageSrc,
thumbnailSrc: asset.thumbnailSrc,
label: asset.label,
width: asset.width,
height: asset.height,
}
: undefined;
const referenceOption = directAttachment
? attachmentOptionsByKey.get(attachmentKey(directAttachment))
: undefined;
if (asset.kind === 'attachment') {
const directAttachment: EditorAgentAttachmentRef = {
source: asset.source,
referenceId: asset.referenceId,
objectKey: asset.objectKey,
imageSrc: asset.imageSrc,
thumbnailSrc: asset.thumbnailSrc,
label: asset.label,
width: asset.width,
height: asset.height,
};
return directAttachment.referenceId.trim() &&
directAttachment.imageSrc.trim()
? appendAttachments([directAttachment])
: false;
}
const objectKey = asset.objectKey?.trim();
const mediaSrc = contextAssetMediaSrc(asset).trim();
const objectKeyOption = objectKey
@@ -290,14 +290,13 @@ export function useConversationAttachments({
attachment.thumbnailSrc?.trim() === mediaSrc
);
});
const matchedAttachment =
referenceOption?.attachment || option?.attachment || directAttachment;
const matchedAttachment = option?.attachment;
return matchedAttachment?.referenceId.trim() &&
matchedAttachment.imageSrc.trim()
? appendAttachments([matchedAttachment])
: false;
},
[appendAttachments, attachmentOptions, attachmentOptionsByKey],
[appendAttachments, attachmentOptions],
);
const handleInputPaste = useCallback(
@@ -361,9 +360,14 @@ export function useConversationAttachments({
const consumeAttachments = useCallback(() => {
const currentAttachments = attachmentsRef.current;
const refreshedAttachments = currentAttachments.map(
(attachment) =>
attachmentOptionsByKey.get(attachmentKey(attachment))?.attachment ??
attachment,
);
updateAttachments([]);
return currentAttachments;
}, [updateAttachments]);
return refreshedAttachments;
}, [attachmentOptionsByKey, updateAttachments]);
const restoreAttachments = useCallback(
(failedAttachments: EditorAgentAttachmentRef[]) => {