From aa41bae044f5aa4f370841c5edd13bdb472cdac0 Mon Sep 17 00:00:00 2001 From: CJACK Date: Mon, 13 Apr 2026 00:04:38 +0800 Subject: [PATCH] feat: add file attachment support to chat interface and API requests --- .../features/apiTester/ApiTesterContainer.jsx | 6 ++ webui/src/features/apiTester/ChatPanel.jsx | 102 +++++++++++++++++- .../features/apiTester/useApiTesterState.js | 4 +- .../features/apiTester/useChatStreamClient.js | 18 +++- 4 files changed, 119 insertions(+), 11 deletions(-) diff --git a/webui/src/features/apiTester/ApiTesterContainer.jsx b/webui/src/features/apiTester/ApiTesterContainer.jsx index f3485c3..0752231 100644 --- a/webui/src/features/apiTester/ApiTesterContainer.jsx +++ b/webui/src/features/apiTester/ApiTesterContainer.jsx @@ -70,6 +70,7 @@ export default function ApiTesterContainer({ config, onMessage, authFetch }) { effectiveKey, selectedAccount, streamingMode, + attachedFiles, abortControllerRef, setLoading, setIsStreaming, @@ -104,6 +105,11 @@ export default function ApiTesterContainer({ config, onMessage, authFetch }) { t={t} message={message} setMessage={setMessage} + attachedFiles={attachedFiles} + setAttachedFiles={setAttachedFiles} + effectiveKey={effectiveKey} + selectedAccount={selectedAccount} + onMessage={onMessage} response={response} isStreaming={isStreaming} loading={loading} diff --git a/webui/src/features/apiTester/ChatPanel.jsx b/webui/src/features/apiTester/ChatPanel.jsx index 3fade56..4633e37 100644 --- a/webui/src/features/apiTester/ChatPanel.jsx +++ b/webui/src/features/apiTester/ChatPanel.jsx @@ -1,10 +1,16 @@ -import { Bot, Loader2, Send, Square, User, Zap } from 'lucide-react' +import { Bot, Loader2, Send, Square, User, Zap, Paperclip, X, FileIcon } from 'lucide-react' import clsx from 'clsx' +import { useRef, useState } from 'react' export default function ChatPanel({ t, message, setMessage, + attachedFiles = [], + setAttachedFiles, + effectiveKey, + selectedAccount, + onMessage, response, isStreaming, loading, @@ -13,6 +19,57 @@ export default function ChatPanel({ onRunTest, onStopGeneration, }) { + const fileInputRef = useRef(null) + const [uploadingFiles, setUploadingFiles] = useState(false) + + const handleFileSelect = async (e) => { + const files = Array.from(e.target.files) + if (files.length === 0) return + + if (!effectiveKey) { + onMessage('error', t('apiTester.missingApiKey') || 'Missing API Key') + return + } + + setUploadingFiles(true) + for (const file of files) { + const formData = new FormData() + formData.append('file', file) + formData.append('purpose', 'assistants') + + const headers = { + 'Authorization': `Bearer ${effectiveKey}`, + } + if (selectedAccount) { + headers['X-Ds2-Target-Account'] = selectedAccount + } + + try { + const res = await fetch('/v1/files', { + method: 'POST', + headers, + body: formData + }) + if (!res.ok) { + const err = await res.text() + onMessage('error', err || 'File upload failed') + continue + } + const data = await res.json() + setAttachedFiles(prev => [...prev, data]) + } catch (error) { + onMessage('error', error.message || 'Network error during upload') + } + } + setUploadingFiles(false) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + + const removeFile = (id) => { + setAttachedFiles(prev => prev.filter(f => f.id !== id)) + } return (
@@ -70,9 +127,42 @@ export default function ChatPanel({
+ {attachedFiles.length > 0 && ( +
+ {attachedFiles.map(file => ( +
+ + {file.filename || file.id} + +
+ ))} +
+ )}
+ +
+ +