fix: restrict file attachments to images only

The OpenClaw gateway only supports image attachments in chat.send;
non-image files were silently dropped, confusing users (fixes #24).

- Filter non-image files in addFiles handler
- Update file input accept to image/* only
- Remove unused fileToBase64 helper
- Update i18n labels from 'Attach file' to 'Attach image' (all 8 langs)

Fixes #24
This commit is contained in:
Nicolas Varrot
2026-03-11 09:04:35 +00:00
parent c4eb7dd844
commit bc3e507a77
2 changed files with 16 additions and 35 deletions

View File

@@ -38,19 +38,6 @@ interface Props {
const MAX_BASE64_CHARS = 300 * 1024; // ~225KB real, well under 512KB WS limit (JSON overhead + base64 bloat) const MAX_BASE64_CHARS = 300 * 1024; // ~225KB real, well under 512KB WS limit (JSON overhead + base64 bloat)
const MAX_IMAGE_PIXELS = 1280; // Max dimension for resize const MAX_IMAGE_PIXELS = 1280; // Max dimension for resize
function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result as string;
const base64 = dataUrl.split(',')[1] || '';
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function compressImage(file: File, maxBase64Chars: number): Promise<{ base64: string; mimeType: string }> { function compressImage(file: File, maxBase64Chars: number): Promise<{ base64: string; mimeType: string }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); const img = new Image();
@@ -184,24 +171,18 @@ export function ChatInput({ onSend, onNewSession, onAbort, isGenerating, disable
const newFiles: FileAttachment[] = []; const newFiles: FileAttachment[] = [];
for (const file of Array.from(fileList)) { for (const file of Array.from(fileList)) {
if (file.size > 20 * 1024 * 1024) continue; // 20MB max if (file.size > 20 * 1024 * 1024) continue; // 20MB max
const isImage = file.type.startsWith('image/'); // Only images are supported — the OpenClaw gateway drops non-image attachments
let base64: string; if (!file.type.startsWith('image/')) continue;
let mimeType: string; // Compress images to fit WS payload limit
if (isImage) { const compressed = await compressImage(file, MAX_BASE64_CHARS);
// Compress images to fit WS payload limit const base64 = compressed.base64;
const compressed = await compressImage(file, MAX_BASE64_CHARS); const mimeType = compressed.mimeType;
base64 = compressed.base64;
mimeType = compressed.mimeType;
} else {
base64 = await fileToBase64(file);
mimeType = file.type || 'application/octet-stream';
}
newFiles.push({ newFiles.push({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
file, file,
base64, base64,
mimeType, mimeType,
preview: isImage ? `data:${mimeType};base64,${base64}` : undefined, preview: `data:${mimeType};base64,${base64}`,
}); });
} }
setFiles(prev => [...prev, ...newFiles]); setFiles(prev => [...prev, ...newFiles]);
@@ -418,7 +399,7 @@ export function ChatInput({ onSend, onNewSession, onAbort, isGenerating, disable
multiple multiple
className="hidden" className="hidden"
onChange={(e) => { if (e.target.files) addFiles(e.target.files); e.target.value = ''; }} onChange={(e) => { if (e.target.files) addFiles(e.target.files); e.target.value = ''; }}
accept="image/*,.pdf,.txt,.md,.json,.csv,.log,.py,.js,.ts,.tsx,.jsx,.html,.css,.yaml,.yml,.xml,.sql,.sh,.env,.toml" accept="image/*"
/> />
<textarea <textarea

View File

@@ -49,7 +49,7 @@ const en = {
'chat.loadingHistory': 'Loading messages…', 'chat.loadingHistory': 'Loading messages…',
'chat.inputPlaceholder': 'Type a message…', 'chat.inputPlaceholder': 'Type a message…',
'chat.inputLabel': 'Message', 'chat.inputLabel': 'Message',
'chat.attachFile': 'Attach file', 'chat.attachFile': 'Attach image',
'chat.send': 'Send', 'chat.send': 'Send',
'chat.stop': 'Stop', 'chat.stop': 'Stop',
'chat.showPreview': 'Preview markdown', 'chat.showPreview': 'Preview markdown',
@@ -245,7 +245,7 @@ const fr: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'Chargement des messages…', 'chat.loadingHistory': 'Chargement des messages…',
'chat.inputPlaceholder': 'Tapez un message…', 'chat.inputPlaceholder': 'Tapez un message…',
'chat.inputLabel': 'Message', 'chat.inputLabel': 'Message',
'chat.attachFile': 'Joindre un fichier', 'chat.attachFile': 'Joindre une image',
'chat.send': 'Envoyer', 'chat.send': 'Envoyer',
'chat.stop': 'Arrêter', 'chat.stop': 'Arrêter',
'chat.showPreview': 'Aperçu markdown', 'chat.showPreview': 'Aperçu markdown',
@@ -428,7 +428,7 @@ const es: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'Cargando mensajes…', 'chat.loadingHistory': 'Cargando mensajes…',
'chat.inputPlaceholder': 'Escribe un mensaje…', 'chat.inputPlaceholder': 'Escribe un mensaje…',
'chat.inputLabel': 'Mensaje', 'chat.inputLabel': 'Mensaje',
'chat.attachFile': 'Adjuntar archivo', 'chat.attachFile': 'Adjuntar imagen',
'chat.send': 'Enviar', 'chat.send': 'Enviar',
'chat.stop': 'Detener', 'chat.stop': 'Detener',
'chat.showPreview': 'Vista previa markdown', 'chat.showPreview': 'Vista previa markdown',
@@ -613,7 +613,7 @@ const de: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'Nachrichten werden geladen…', 'chat.loadingHistory': 'Nachrichten werden geladen…',
'chat.inputPlaceholder': 'Nachricht eingeben…', 'chat.inputPlaceholder': 'Nachricht eingeben…',
'chat.inputLabel': 'Nachricht', 'chat.inputLabel': 'Nachricht',
'chat.attachFile': 'Datei anhängen', 'chat.attachFile': 'Bild anhängen',
'chat.send': 'Senden', 'chat.send': 'Senden',
'chat.stop': 'Stoppen', 'chat.stop': 'Stoppen',
'chat.showPreview': 'Markdown-Vorschau', 'chat.showPreview': 'Markdown-Vorschau',
@@ -796,7 +796,7 @@ const ja: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'メッセージを読み込み中…', 'chat.loadingHistory': 'メッセージを読み込み中…',
'chat.inputPlaceholder': 'メッセージを入力…', 'chat.inputPlaceholder': 'メッセージを入力…',
'chat.inputLabel': 'メッセージ', 'chat.inputLabel': 'メッセージ',
'chat.attachFile': 'ファイルを添付', 'chat.attachFile': '画像を添付',
'chat.send': '送信', 'chat.send': '送信',
'chat.stop': '停止', 'chat.stop': '停止',
'chat.showPreview': 'Markdownプレビュー', 'chat.showPreview': 'Markdownプレビュー',
@@ -979,7 +979,7 @@ const pt: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'Carregando mensagens…', 'chat.loadingHistory': 'Carregando mensagens…',
'chat.inputPlaceholder': 'Digite uma mensagem…', 'chat.inputPlaceholder': 'Digite uma mensagem…',
'chat.inputLabel': 'Mensagem', 'chat.inputLabel': 'Mensagem',
'chat.attachFile': 'Anexar arquivo', 'chat.attachFile': 'Anexar imagem',
'chat.send': 'Enviar', 'chat.send': 'Enviar',
'chat.stop': 'Parar', 'chat.stop': 'Parar',
'chat.showPreview': 'Pré-visualizar markdown', 'chat.showPreview': 'Pré-visualizar markdown',
@@ -1162,7 +1162,7 @@ const zh: Record<keyof typeof en, string> = {
'chat.loadingHistory': '加载消息中…', 'chat.loadingHistory': '加载消息中…',
'chat.inputPlaceholder': '输入消息…', 'chat.inputPlaceholder': '输入消息…',
'chat.inputLabel': '消息', 'chat.inputLabel': '消息',
'chat.attachFile': '添加附件', 'chat.attachFile': '添加图片',
'chat.send': '发送', 'chat.send': '发送',
'chat.stop': '停止', 'chat.stop': '停止',
'chat.showPreview': '预览 Markdown', 'chat.showPreview': '预览 Markdown',
@@ -1345,7 +1345,7 @@ const it: Record<keyof typeof en, string> = {
'chat.loadingHistory': 'Caricamento messaggi…', 'chat.loadingHistory': 'Caricamento messaggi…',
'chat.inputPlaceholder': 'Scrivi un messaggio…', 'chat.inputPlaceholder': 'Scrivi un messaggio…',
'chat.inputLabel': 'Messaggio', 'chat.inputLabel': 'Messaggio',
'chat.attachFile': 'Allega file', 'chat.attachFile': 'Allega immagine',
'chat.send': 'Invia', 'chat.send': 'Invia',
'chat.stop': 'Ferma', 'chat.stop': 'Ferma',
'chat.showPreview': 'Anteprima markdown', 'chat.showPreview': 'Anteprima markdown',