From 7b40e60872e338be107d827ced4135ed195aa938 Mon Sep 17 00:00:00 2001 From: SuperManTouX <93423476+SuperManTouX@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:21:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=99=84=E4=BB=B6=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=90=91=E4=B8=8A=EF=BC=8C=20=E7=82=B9=E5=87=BB=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=8C=89=E9=92=AEoss=E7=9A=84=E9=99=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/api/conversation_routes.py | 24 ++++++++++++++++++++- server/main.py | 6 ++++++ src/components/chat/ChatMain.vue | 27 ++++++++++++++++++++++++ src/components/input/ChatInput.vue | 34 +++++++++++++++++++----------- src/services/api.ts | 17 +++++++++++++++ 5 files changed, 95 insertions(+), 13 deletions(-) diff --git a/server/api/conversation_routes.py b/server/api/conversation_routes.py index 5319831..b95cf12 100644 --- a/server/api/conversation_routes.py +++ b/server/api/conversation_routes.py @@ -306,4 +306,26 @@ async def stop_generation_handler(message_id: str = None): message = ( f"已发出停止指令,消息ID: {message_id}" if message_id else "已发出停止指令" ) - return {"success": True, "message": message} \ No newline at end of file + return {"success": True, "message": message} + + +async def delete_attachment_handler(url: str): + """删除附件处理器 - 从 OSS 删除文件""" + try: + from utils.oss_uploader import delete_file, extract_object_key_from_url + + object_key = extract_object_key_from_url(url) + if not object_key: + raise HTTPException(status_code=400, detail="无效的文件 URL") + + success = delete_file(object_key) + if success: + return {"success": True, "message": "文件删除成功"} + else: + raise HTTPException(status_code=500, detail="文件删除失败") + + except HTTPException: + raise + except Exception as e: + log_error(f"删除附件失败: {str(e)}") + raise HTTPException(status_code=500, detail=f"删除失败: {str(e)}") \ No newline at end of file diff --git a/server/main.py b/server/main.py index 39cf28f..d4bdaaf 100644 --- a/server/main.py +++ b/server/main.py @@ -55,6 +55,7 @@ load_dotenv() # ── 会话管理路由处理器 ──────────────────────────────────────────────── from api.conversation_routes import (add_message_handler, + delete_attachment_handler, delete_conversation_handler, get_conversation_handler, get_conversations_handler, @@ -227,6 +228,11 @@ async def stop_generation_by_id(message_id: str): return await stop_generation_handler(message_id) +@app.delete("/api/chat-ui/attachment") +async def delete_attachment(url: str): + return await delete_attachment_handler(url) + + # ── 程序入口 ────────────────────────────────────────────────────────── if __name__ == "__main__": import uvicorn diff --git a/src/components/chat/ChatMain.vue b/src/components/chat/ChatMain.vue index a48f475..85f0eb0 100644 --- a/src/components/chat/ChatMain.vue +++ b/src/components/chat/ChatMain.vue @@ -28,6 +28,13 @@
+ +
+ +
{ return "输入你的问题,按 Ctrl+Enter 发送"; }); +// 附件相关 +const currentAttachments = computed(() => chatInputRef.value?.attachments || []); +const hasAttachments = computed(() => currentAttachments.value.length > 0); + +function handleRemoveAttachment(id: string) { + chatInputRef.value?.removeAttachment(id); +} + function toggleWideMode() { isWideMode.value = !isWideMode.value; } @@ -513,6 +529,17 @@ watch( } } +.attachments-preview-container { + margin-bottom: 12px; + background: #f3f4f5; + border-radius: 16px; + overflow: hidden; + + .dark & { + background: #1e1e2e; + } +} + .input-container { width: 100%; // min-width: 1000px; diff --git a/src/components/input/ChatInput.vue b/src/components/input/ChatInput.vue index cac180f..2c43356 100644 --- a/src/components/input/ChatInput.vue +++ b/src/components/input/ChatInput.vue @@ -3,13 +3,6 @@ class="chat-input-container" :class="{ 'is-focused': isFocused, 'is-expanded': isExpanded }" > - - -
@@ -414,13 +407,28 @@ async function uploadFileToServer(id: string, file: File) { } // 移除附件 -function removeAttachment(id: string) { +async function removeAttachment(id: string) { const index = attachments.value.findIndex((a) => a.id === id); - if (index !== -1) { - // 释放 blob URL - URL.revokeObjectURL(attachments.value[index].url); - attachments.value.splice(index, 1); + if (index === -1) return; + + const attachment = attachments.value[index]; + + // 如果已上传到 OSS(不是本地 blob URL),则从 OSS 删除 + if (attachment.url && !attachment.url.startsWith('blob:')) { + try { + await chatApi.deleteAttachment(attachment.url); + } catch (error) { + console.error('删除 OSS 文件失败:', error); + // 即使删除失败也继续移除本地引用 + } } + + // 释放 blob URL(如果是本地的) + if (attachment.url.startsWith('blob:')) { + URL.revokeObjectURL(attachment.url); + } + + attachments.value.splice(index, 1); } // 切换功能(深度搜索与联网搜索互斥) @@ -470,6 +478,8 @@ function clear() { defineExpose({ focus, clear, + attachments, + removeAttachment, }); // 监听文本变化,自动调整高度 diff --git a/src/services/api.ts b/src/services/api.ts index 09827c2..982b8da 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -340,6 +340,23 @@ class ChatApi { return response.json(); } + + /** + * 删除附件(从 OSS 删除) + */ + async deleteAttachment(url: string): Promise { + const response = await fetch( + `${this.baseUrl}/api/chat-ui/attachment?url=${encodeURIComponent(url)}`, + { + method: "DELETE", + headers: getAuthHeaders(), + } + ); + + if (!response.ok) { + throw new Error(`删除附件失败: HTTP ${response.status}`); + } + } } // 导出单例