fix image load (#1097)

This commit is contained in:
paisley
2026-06-03 14:54:09 +08:00
committed by GitHub
parent 8ea306d91c
commit 9e531d1229
3 changed files with 171 additions and 0 deletions
+56
View File
@@ -2878,6 +2878,61 @@ export const useChatStore = create<ChatState>((set, get) => ({
: message;
});
};
type AttachedFile = NonNullable<RawMessage['_attachedFiles']>[number];
const getAttachmentMergeKey = (file: AttachedFile): string | null => (
file.filePath || file.gatewayUrl || null
);
const preserveExistingAttachmentPreviews = (
currentMessages: RawMessage[],
nextMessages: RawMessage[],
): RawMessage[] => {
const currentFilesByMessageKey = new Map<string, Map<string, AttachedFile>>();
for (const message of currentMessages) {
if (!message._attachedFiles?.length) continue;
const filesByKey = new Map<string, AttachedFile>();
for (const file of message._attachedFiles) {
const key = getAttachmentMergeKey(file);
if (!key) continue;
if (!file.preview && !file.fileSize && !file.previewStatus) continue;
filesByKey.set(key, file);
}
if (filesByKey.size > 0) {
currentFilesByMessageKey.set(getPreviewMergeKey(message), filesByKey);
}
}
if (currentFilesByMessageKey.size === 0) return nextMessages;
return nextMessages.map((message) => {
if (!message._attachedFiles?.length) return message;
const currentFiles = currentFilesByMessageKey.get(getPreviewMergeKey(message));
if (!currentFiles) return message;
let changed = false;
const attachedFiles = message._attachedFiles.map((file) => {
const key = getAttachmentMergeKey(file);
const currentFile = key ? currentFiles.get(key) : undefined;
if (!currentFile) return file;
let nextFile = file;
if (!nextFile.preview && currentFile.preview) {
nextFile = { ...nextFile, preview: currentFile.preview };
changed = true;
}
if (!nextFile.fileSize && currentFile.fileSize) {
nextFile = { ...nextFile, fileSize: currentFile.fileSize };
changed = true;
}
if (!nextFile.previewStatus && currentFile.previewStatus) {
nextFile = { ...nextFile, previewStatus: currentFile.previewStatus };
changed = true;
}
return nextFile;
});
return changed ? { ...message, _attachedFiles: attachedFiles } : message;
});
};
const applyLoadFailure = (errorMessage: string | null) => {
if (!isCurrentSession()) return;
@@ -2920,6 +2975,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
}
}
finalMessages = dropRedundantOptimisticUserMessages(currentSessionKey, finalMessages);
finalMessages = preserveExistingAttachmentPreviews(get().messages, finalMessages);
const { pendingFinal, lastUserMessageAt, sending: isSendingNow } = get();
const userMsTs = lastUserMessageAt != null ? toMs(lastUserMessageAt) : 0;
+56
View File
@@ -95,6 +95,61 @@ export function createHistoryActions(
: message;
});
};
type AttachedFile = NonNullable<RawMessage['_attachedFiles']>[number];
const getAttachmentMergeKey = (file: AttachedFile): string | null => (
file.filePath || file.gatewayUrl || null
);
const preserveExistingAttachmentPreviews = (
currentMessages: RawMessage[],
nextMessages: RawMessage[],
): RawMessage[] => {
const currentFilesByMessageKey = new Map<string, Map<string, AttachedFile>>();
for (const message of currentMessages) {
if (!message._attachedFiles?.length) continue;
const filesByKey = new Map<string, AttachedFile>();
for (const file of message._attachedFiles) {
const key = getAttachmentMergeKey(file);
if (!key) continue;
if (!file.preview && !file.fileSize && !file.previewStatus) continue;
filesByKey.set(key, file);
}
if (filesByKey.size > 0) {
currentFilesByMessageKey.set(getPreviewMergeKey(message), filesByKey);
}
}
if (currentFilesByMessageKey.size === 0) return nextMessages;
return nextMessages.map((message) => {
if (!message._attachedFiles?.length) return message;
const currentFiles = currentFilesByMessageKey.get(getPreviewMergeKey(message));
if (!currentFiles) return message;
let changed = false;
const attachedFiles = message._attachedFiles.map((file) => {
const key = getAttachmentMergeKey(file);
const currentFile = key ? currentFiles.get(key) : undefined;
if (!currentFile) return file;
let nextFile = file;
if (!nextFile.preview && currentFile.preview) {
nextFile = { ...nextFile, preview: currentFile.preview };
changed = true;
}
if (!nextFile.fileSize && currentFile.fileSize) {
nextFile = { ...nextFile, fileSize: currentFile.fileSize };
changed = true;
}
if (!nextFile.previewStatus && currentFile.previewStatus) {
nextFile = { ...nextFile, previewStatus: currentFile.previewStatus };
changed = true;
}
return nextFile;
});
return changed ? { ...message, _attachedFiles: attachedFiles } : message;
});
};
const applyLoadFailure = (errorMessage: string | null) => {
if (!isCurrentSession()) return;
@@ -133,6 +188,7 @@ export function createHistoryActions(
}
}
finalMessages = dropRedundantOptimisticUserMessages(currentSessionKey, finalMessages);
finalMessages = preserveExistingAttachmentPreviews(get().messages, finalMessages);
const { pendingFinal, lastUserMessageAt, sending: isSendingNow } = get();
const userMsTs = lastUserMessageAt ? toMs(lastUserMessageAt) : 0;
+59
View File
@@ -749,6 +749,65 @@ describe('chat history actions', () => {
expect(h.read().messages[0]?._attachedFiles?.[0]?.preview).toBe('data:image/png;base64,abc');
});
it('preserves existing image previews when quiet history refresh rebuilds attachments', async () => {
const { createHistoryActions } = await import('@/stores/chat/history-actions');
const gatewayUrl = '/api/chat/media/outgoing/agent%3Amain%3As-1/image-1/full';
const h = makeHarness({
currentSessionKey: 'agent:main:main',
messages: [
{
id: 'generated-image',
role: 'assistant',
content: 'Generated image',
timestamp: 1000,
_attachedFiles: [
{
fileName: 'generated.png',
mimeType: 'image/png',
fileSize: 42,
preview: 'data:image/png;base64,already-loaded',
gatewayUrl,
source: 'gateway-media',
},
],
},
],
});
const actions = createHistoryActions(h.set as never, h.get as never);
invokeIpcMock.mockResolvedValueOnce({
success: true,
result: {
messages: [
{
id: 'generated-image',
role: 'assistant',
content: 'Generated image',
timestamp: 1000,
_attachedFiles: [
{
fileName: 'generated.png',
mimeType: 'image/png',
fileSize: 0,
preview: null,
gatewayUrl,
source: 'gateway-media',
},
],
},
],
},
});
await actions.loadHistory(true);
expect(h.read().messages[0]?._attachedFiles?.[0]).toMatchObject({
fileSize: 42,
preview: 'data:image/png;base64,already-loaded',
gatewayUrl,
});
});
it('does not append an optimistic duplicate when history already includes the user message without timestamp', async () => {
const { createHistoryActions } = await import('@/stores/chat/history-actions');
const h = makeHarness({