fix(frontend): parse uploaded filenames containing parentheses (#4608)

parseUploadedFiles stopped the filename capture at the first "("
([^\n(]+), so an entry like "- photo (1).png (12.3 KB)" failed to
match and the file silently disappeared from the message's file chips.
Browsers produce such names for duplicate downloads, making this a
common real-world shape.

Anchor the size group on the "(<number> <unit>)" pair the backend
emits (uploads_middleware formats sizes as "%.1f KB"/"%.1f MB") and
let the filename match greedily up to it, so parenthesized filenames
parse correctly.
This commit is contained in:
Baldwinzc 2026-07-31 18:08:34 +08:00 committed by GitHub
parent 72c9701410
commit 17461ee52e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 30 additions and 1 deletions

View File

@ -845,7 +845,11 @@ export function parseUploadedFiles(content: string): FileInMessage[] {
// Parse file list // Parse file list
// Format: - filename (size)\n Path: /path/to/file // Format: - filename (size)\n Path: /path/to/file
const fileRegex = /- ([^\n(]+)\s*\(([^)]+)\)\s*\n\s*Path:\s*([^\n]+)/g; // The filename itself may contain parentheses (e.g. "photo (1).png"), so
// the size group is anchored on the trailing "(<number> <unit>)" pair the
// backend emits instead of stopping the filename at the first "(".
const fileRegex =
/- (.+)\s*\(([\d.]+\s*(?:B|KB|MB|GB|TB))\)\s*\n\s*Path:\s*([^\n]+)/gi;
const files: FileInMessage[] = []; const files: FileInMessage[] = [];
let fileMatch; let fileMatch;

View File

@ -603,6 +603,31 @@ describe("human message internal context stripping", () => {
]); ]);
}); });
test("parses uploaded filenames that contain parentheses", () => {
// Browsers name duplicate downloads "photo (1).png"; the backend emits the
// filename verbatim, so the parser must not stop the name at the first "(".
const content =
"<current_uploads>\nThe following files were uploaded in this message:\n\n- photo (1).png (12.3 KB)\n Path: /mnt/user-data/uploads/photo (1).png\n- report (final) (2).docx (1.5 MB)\n Path: /mnt/user-data/uploads/report (final) (2).docx\n- normal.pdf (3.0 KB)\n Path: /mnt/user-data/uploads/normal.pdf\n</current_uploads>\n\nSummarize";
expect(parseUploadedFiles(content)).toEqual([
{
filename: "photo (1).png",
size: Math.round(12.3 * 1024),
path: "/mnt/user-data/uploads/photo (1).png",
},
{
filename: "report (final) (2).docx",
size: Math.round(1.5 * 1024 * 1024),
path: "/mnt/user-data/uploads/report (final) (2).docx",
},
{
filename: "normal.pdf",
size: 3 * 1024,
path: "/mnt/user-data/uploads/normal.pdf",
},
]);
});
test("stripInternalMarkers removes current_uploads blocks on export", () => { test("stripInternalMarkers removes current_uploads blocks on export", () => {
const content = const content =
"<current_uploads>\n- paper.docx (177.6 KB)\n Path: /mnt/user-data/uploads/paper.docx\n</current_uploads>\n\nExport me"; "<current_uploads>\n- paper.docx (177.6 KB)\n Path: /mnt/user-data/uploads/paper.docx\n</current_uploads>\n\nExport me";