fix: file upload bugs and UI adjustment

- Include base64 data in file metadata for chat display (fixes broken images)
- Use file-to-disk + Read tool approach instead of SDK multimodal
  messages (fixes slow response caused by invalid prompt format)
- Move attach button before folder picker in input toolbar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-08 22:26:42 +08:00
1 parent 164f730da1
commit 37cfaacaf0
3 files changed
+21 -54

No files matched your search

+1 -1
View File
@@ -147,7 +147,7 @@ export function ChatView({ sessionId, initialMessages = [], modelName, initialMo
// Build display content: embed file metadata as HTML comment for MessageItem to parse
let displayContent = content;
if (files && files.length > 0) {
const fileMeta = files.map(f => ({ id: f.id, name: f.name, type: f.type, size: f.size }));
const fileMeta = files.map(f => ({ id: f.id, name: f.name, type: f.type, size: f.size, data: f.data }));
displayContent = `<!--files:${JSON.stringify(fileMeta)}-->${content}`;
}
+3 -3
View File
@@ -770,6 +770,9 @@ export function MessageInput({
/>
<PromptInputFooter>
<PromptInputTools>
{/* Attach file button */}
<AttachFileButton />
{/* Folder picker button */}
<PromptInputButton
onClick={() => setFolderPickerOpen(true)}
@@ -781,9 +784,6 @@ export function MessageInput({
</span>
</PromptInputButton>
{/* Attach file button */}
<AttachFileButton />
{/* Mode selector */}
<div className="relative" ref={modeMenuRef}>
<PromptInputButton
+17 -50
View File
@@ -366,61 +366,28 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
}
};
// Build the prompt: if there are file attachments, construct multimodal content
let finalPrompt: string | { content: unknown[] } = prompt;
// Build the prompt: save all file attachments to disk and reference them in the prompt.
// Claude Code's built-in Read tool can read images (converts to base64 internally),
// PDFs, and text files — this is more reliable than constructing SDK multimodal messages.
let finalPrompt: string = prompt;
if (files && files.length > 0) {
// Allowed image types for Claude multimodal: JPEG, PNG, GIF, WebP; max 5MB each
const allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
const maxImageSize = 5 * 1024 * 1024;
const imageFiles = files.filter(f =>
isImageFile(f.type) && allowedImageTypes.includes(f.type) && f.size <= maxImageSize
);
const nonImageFiles = files.filter(f => !isImageFile(f.type));
// Save non-image files to disk so Claude's Read tool can access them
let fileReferences = '';
if (nonImageFiles.length > 0) {
const workDir = workingDirectory || process.cwd();
const savedPaths = saveUploadedFiles(nonImageFiles, workDir);
fileReferences = savedPaths
.map((p, i) => `[User attached file: ${p} (${nonImageFiles[i].name})]`)
.join('\n') + '\n\n';
}
// For image files, construct multimodal content blocks
if (imageFiles.length > 0) {
const contentBlocks: unknown[] = [];
// Add image content blocks
for (const img of imageFiles) {
// Supported: image/jpeg, image/png, image/gif, image/webp
const mediaType = img.type as 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
contentBlocks.push({
type: 'image',
source: {
type: 'base64',
media_type: mediaType,
data: img.data,
},
});
}
// Add text block with optional file references prepended
contentBlocks.push({
type: 'text',
text: fileReferences + prompt,
});
finalPrompt = { content: contentBlocks };
} else {
// No images, just prepend file references to the text prompt
finalPrompt = fileReferences + prompt;
}
const workDir = workingDirectory || process.cwd();
const savedPaths = saveUploadedFiles(files, workDir);
const fileReferences = savedPaths
.map((p, i) => {
const f = files[i];
if (isImageFile(f.type)) {
return `[User attached image: ${p} (${f.name})]`;
}
return `[User attached file: ${p} (${f.name})]`;
})
.join('\n');
finalPrompt = `${fileReferences}\n\nPlease read the attached file(s) above using your Read tool, then respond to the user's message:\n\n${prompt}`;
}
const conversation = query({
prompt: finalPrompt as string,
prompt: finalPrompt,
options: queryOptions,
});