fix cross-protocol conversation file compatibility

This commit is contained in:
cita
2026-03-20 21:48:05 +08:00
parent 5f744c3382
commit 2890b7daf3
20 changed files with 619 additions and 63 deletions
+5 -18
View File
@@ -23,6 +23,7 @@ import {
createLoadingAssistantMessage,
createMessage,
createConversationUserMessage,
extractConversationUploadedFilesFromMessage,
filterModelTesterModelNames,
finalizeIncompleteMessage,
findLastLoadingAssistantIndex,
@@ -898,20 +899,6 @@ export default function ModelTester() {
setConversationFiles((prev) => prev.filter((item) => item.localId !== localId));
}, [sending]);
const extractUploadedFilesFromMessage = useCallback((message: ChatMessage): ConversationUploadedFile[] => {
const parts = Array.isArray(message.parts) ? message.parts : [];
return parts.flatMap((part) => {
if (part.type !== 'input_file') return [];
const fileId = typeof part.fileId === 'string' ? part.fileId.trim() : '';
if (!fileId) return [];
return [{
fileId,
filename: typeof part.filename === 'string' && part.filename.trim() ? part.filename.trim() : null,
mimeType: typeof part.mimeType === 'string' && part.mimeType.trim() ? part.mimeType.trim() : null,
}];
});
}, []);
const uploadConversationFiles = useCallback(async (): Promise<ConversationUploadedFile[]> => {
if (conversationFiles.length <= 0) return [];
@@ -2047,11 +2034,11 @@ export default function ModelTester() {
const base = messages.slice(0, userIndex);
const prompt = messages[userIndex].content;
const files = extractUploadedFilesFromMessage(messages[userIndex]);
const files = extractConversationUploadedFilesFromMessage(messages[userIndex]);
setEditingMessageId(null);
setEditValue('');
void sendWithPrompt(prompt, base, files);
}, [extractUploadedFilesFromMessage, messages, pendingJobId, sendWithPrompt, sending]);
}, [messages, pendingJobId, sendWithPrompt, sending]);
const startEditMessage = useCallback((target: ChatMessage) => {
if (sending) return;
@@ -2085,9 +2072,9 @@ export default function ModelTester() {
if (retry && target.role === 'user') {
const base = updated.slice(0, targetIndex);
void sendWithPrompt(nextContent, base, extractUploadedFilesFromMessage(target));
void sendWithPrompt(nextContent, base, extractConversationUploadedFilesFromMessage(target));
}
}, [cancelEditMessage, editValue, editingMessageId, extractUploadedFilesFromMessage, messages, sendWithPrompt]);
}, [cancelEditMessage, editValue, editingMessageId, messages, sendWithPrompt]);
const syncMessageToBody = useCallback(() => {
const nextBody = syncMessagesToCustomRequestBody(customRequestBody, messages, inputs);
@@ -16,16 +16,16 @@ describe('resolveConversationFileCapability', () => {
});
});
it('marks Claude and Gemini as inline-only document transports', () => {
it('marks Claude and Gemini picker flows as inline-only document transports', () => {
expect(resolveConversationFileCapability('claude')).toEqual({
supported: true,
documentMode: 'inline_only',
reason: '当前协议会以内联文档方式发送会话附件。',
reason: '当前界面的会话附件会以内联文档方式发送。',
});
expect(resolveConversationFileCapability('gemini')).toEqual({
supported: true,
documentMode: 'inline_only',
reason: '当前协议会以内联文档方式发送会话附件。',
reason: '当前界面的会话附件会以内联文档方式发送。',
});
});
});
@@ -21,7 +21,7 @@ export function resolveConversationFileCapability(
return {
supported: true,
documentMode: 'inline_only',
reason: '当前协议会以内联文档方式发送会话附件。',
reason: '当前界面的会话附件会以内联文档方式发送。',
};
}
@@ -15,6 +15,7 @@ import {
collectModelTesterModelNames,
countConversationTurns,
createConversationUserMessage,
extractConversationUploadedFilesFromMessage,
filterModelTesterModelNames,
parseCustomRequestBody,
parseModelTesterSession,
@@ -222,6 +223,56 @@ describe('modelTesterSession', () => {
]);
});
it('extracts inline conversation file data for retry flows', () => {
const files = extractConversationUploadedFilesFromMessage({
id: 'u1',
role: 'user',
content: '请总结附件',
createAt: 1,
parts: [
{
type: 'input_file',
filename: 'brief.pdf',
mimeType: 'application/pdf',
data: 'data:application/pdf;base64,JVBERi0xLjQK',
},
],
});
expect(files).toEqual([
{
filename: 'brief.pdf',
mimeType: 'application/pdf',
data: 'data:application/pdf;base64,JVBERi0xLjQK',
},
]);
});
it('extracts uploaded file references for retry flows', () => {
const files = extractConversationUploadedFilesFromMessage({
id: 'u2',
role: 'user',
content: '请继续分析',
createAt: 2,
parts: [
{
type: 'input_file',
fileId: 'file-metapi-456',
filename: 'appendix.txt',
mimeType: 'text/plain',
},
],
});
expect(files).toEqual([
{
fileId: 'file-metapi-456',
filename: 'appendix.txt',
mimeType: 'text/plain',
},
]);
});
it('preserves file reference parts when building responses conversation payloads', () => {
const payload = buildApiPayload(
[{
@@ -901,6 +901,37 @@ export const createConversationUserMessage = (
});
};
export const extractConversationUploadedFilesFromMessage = (
message: ChatMessage,
): ConversationUploadedFile[] => {
const parts = Array.isArray(message.parts) ? message.parts : [];
return parts.flatMap((part) => {
if (part.type !== 'input_file') return [];
const fileId = typeof part.fileId === 'string' && part.fileId.trim()
? part.fileId.trim()
: null;
const filename = typeof part.filename === 'string' && part.filename.trim()
? part.filename.trim()
: null;
const mimeType = typeof part.mimeType === 'string' && part.mimeType.trim()
? part.mimeType.trim()
: null;
const data = typeof part.data === 'string' && part.data.trim()
? part.data.trim()
: null;
if (!fileId && !data) return [];
return [{
...(fileId ? { fileId } : {}),
...(filename ? { filename } : {}),
...(mimeType ? { mimeType } : {}),
...(data ? { data } : {}),
}];
});
};
export const createLoadingAssistantMessage = (): ChatMessage =>
createMessage('assistant', '', {
status: MESSAGE_STATUS.LOADING,