Merge origin/main into codex/modeltester-attachment-review-fixes

This commit is contained in:
cita
2026-03-20 23:09:27 +08:00
46 changed files with 1633 additions and 139 deletions
@@ -31,14 +31,14 @@ describe('resolveConversationFileCapability', () => {
imageMode: 'native',
audioMode: 'unsupported',
documentMode: 'inline_only',
reason: '',
reason: '当前界面的会话附件会以内联文档方式发送。',
});
expect(resolveConversationFileCapability('gemini')).toEqual({
supported: true,
imageMode: 'native',
audioMode: 'native',
documentMode: 'inline_only',
reason: '',
reason: '当前界面的会话附件会以内联文档方式发送。',
});
});
@@ -87,7 +87,7 @@ export function resolveConversationFileCapability(
imageMode: 'native',
audioMode: 'unsupported',
documentMode: 'inline_only',
reason: '',
reason: '当前界面的会话附件会以内联文档方式发送。',
};
}
@@ -97,7 +97,7 @@ export function resolveConversationFileCapability(
imageMode: 'native',
audioMode: 'native',
documentMode: 'inline_only',
reason: '',
reason: '当前界面的会话附件会以内联文档方式发送。',
};
}
@@ -223,30 +223,52 @@ describe('modelTesterSession', () => {
]);
});
it('extracts both uploaded references and inline attachments from user messages', () => {
const message = createConversationUserMessage('请重新发送附件', [
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([
{
fileId: 'file-metapi-123',
filename: 'paper.pdf',
filename: 'brief.pdf',
mimeType: 'application/pdf',
},
{
filename: 'voice.mp3',
mimeType: 'audio/mpeg',
data: 'data:audio/mpeg;base64,QUFBQQ==',
data: 'data:application/pdf;base64,JVBERi0xLjQK',
},
]);
});
expect(extractConversationUploadedFilesFromMessage(message)).toEqual([
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-123',
filename: 'paper.pdf',
mimeType: 'application/pdf',
},
{
filename: 'voice.mp3',
mimeType: 'audio/mpeg',
data: 'data:audio/mpeg;base64,QUFBQQ==',
fileId: 'file-metapi-456',
filename: 'appendix.txt',
mimeType: 'text/plain',
},
]);
});
+17 -8
View File
@@ -918,17 +918,26 @@ export const extractConversationUploadedFilesFromMessage = (
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() : '';
const data = typeof part.data === 'string' ? part.data.trim() : '';
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 } : {}),
...(typeof part.filename === 'string' && part.filename.trim()
? { filename: part.filename.trim() }
: {}),
...(typeof part.mimeType === 'string' && part.mimeType.trim()
? { mimeType: part.mimeType.trim() }
: {}),
...(filename ? { filename } : {}),
...(mimeType ? { mimeType } : {}),
...(data ? { data } : {}),
}];
});
+10 -1
View File
@@ -16,6 +16,7 @@ describe('buildSiteSaveAction', () => {
url: 'https://a.example.com/',
externalCheckinUrl: 'https://checkin.a.example.com',
platform: 'new-api',
proxyUrl: 'socks5://127.0.0.1:1080',
customHeaders: '{"x-site-token":"alpha"}',
useSystemProxy: false,
globalWeight: 1.2,
@@ -29,6 +30,7 @@ describe('buildSiteSaveAction', () => {
url: 'https://a.example.com/',
externalCheckinUrl: 'https://checkin.a.example.com',
platform: 'new-api',
proxyUrl: 'socks5://127.0.0.1:1080',
customHeaders: '{"x-site-token":"alpha"}',
useSystemProxy: false,
globalWeight: 1.2,
@@ -44,6 +46,7 @@ describe('buildSiteSaveAction', () => {
url: 'https://b.example.com',
externalCheckinUrl: '',
platform: 'one-api',
proxyUrl: '',
useSystemProxy: true,
customHeaders: '',
globalWeight: 0.8,
@@ -58,6 +61,7 @@ describe('buildSiteSaveAction', () => {
url: 'https://b.example.com',
externalCheckinUrl: '',
platform: 'one-api',
proxyUrl: '',
useSystemProxy: true,
customHeaders: '',
globalWeight: 0.8,
@@ -74,6 +78,7 @@ describe('buildSiteSaveAction', () => {
url: 'https://c.example.com',
externalCheckinUrl: '',
platform: '',
proxyUrl: '',
useSystemProxy: false,
customHeaders: '',
globalWeight: 1,
@@ -85,16 +90,20 @@ describe('buildSiteSaveAction', () => {
it('does not expose deprecated apiKey in site editor state', () => {
expect(emptySiteForm()).not.toHaveProperty('apiKey');
expect(emptySiteForm().customHeaders).toEqual([emptySiteCustomHeader()]);
expect(emptySiteForm().proxyUrl).toBe('');
expect(siteFormFromSite({
name: 'site-d',
url: 'https://d.example.com',
externalCheckinUrl: null,
platform: 'new-api',
proxyUrl: null,
proxyUrl: 'http://127.0.0.1:8080',
customHeaders: '{"x-site-token":"alpha"}',
globalWeight: 1,
apiKey: 'sk-legacy-site-key',
})).not.toHaveProperty('apiKey');
expect(siteFormFromSite({
proxyUrl: 'http://127.0.0.1:8080',
}).proxyUrl).toBe('http://127.0.0.1:8080');
});
it('parses custom headers json into key value rows', () => {
+6 -1
View File
@@ -8,6 +8,7 @@ export type SiteForm = {
url: string;
externalCheckinUrl: string;
platform: string;
proxyUrl: string;
useSystemProxy: boolean;
customHeaders: SiteCustomHeaderField[];
globalWeight: string;
@@ -22,6 +23,7 @@ export type SiteSavePayload = {
url: string;
externalCheckinUrl: string;
platform: string;
proxyUrl: string;
useSystemProxy: boolean;
customHeaders: string;
globalWeight: number;
@@ -45,6 +47,7 @@ export function emptySiteForm(): SiteForm {
url: '',
externalCheckinUrl: '',
platform: '',
proxyUrl: '',
useSystemProxy: false,
customHeaders: [emptySiteCustomHeader()],
globalWeight: '1',
@@ -76,8 +79,9 @@ function parseCustomHeadersForEditor(raw: unknown): SiteCustomHeaderField[] {
}
}
export function siteFormFromSite(site: Partial<Omit<SiteForm, 'customHeaders' | 'globalWeight' | 'externalCheckinUrl' | 'useSystemProxy'>> & {
export function siteFormFromSite(site: Partial<Omit<SiteForm, 'customHeaders' | 'globalWeight' | 'externalCheckinUrl' | 'proxyUrl' | 'useSystemProxy'>> & {
externalCheckinUrl?: string | null;
proxyUrl?: string | null;
useSystemProxy?: boolean | null;
customHeaders?: string | null;
globalWeight?: number | string | null;
@@ -89,6 +93,7 @@ export function siteFormFromSite(site: Partial<Omit<SiteForm, 'customHeaders' |
url: site.url ?? '',
externalCheckinUrl: site.externalCheckinUrl ?? '',
platform: site.platform ?? '',
proxyUrl: site.proxyUrl ?? '',
useSystemProxy: !!site.useSystemProxy,
customHeaders: parseCustomHeadersForEditor(site.customHeaders),
globalWeight,