Merge branch 'feat/kexue-ui'

This commit is contained in:
SuperManTouX 2026-03-09 17:22:16 +08:00
commit d81fb4d0a0
5 changed files with 95 additions and 13 deletions

View File

@ -307,3 +307,25 @@ async def stop_generation_handler(message_id: str = None):
f"已发出停止指令消息ID: {message_id}" if message_id else "已发出停止指令"
)
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)}")

View File

@ -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

View File

@ -28,6 +28,13 @@
<!-- 输入区域 -->
<div class="input-wrapper">
<!-- 附件预览区 -->
<div v-if="hasAttachments" class="attachments-preview-container">
<AttachmentPreview
:attachments="currentAttachments"
@remove="handleRemoveAttachment"
/>
</div>
<div class="input-container" :class="{ wide: isWideMode }">
<ChatInput
ref="chatInputRef"
@ -56,6 +63,7 @@ import { useAuthStore } from "@/stores/auth";
import ChatHeader from "./ChatHeader.vue";
import MessageList from "./MessageList.vue";
import ChatInput from "@/components/input/ChatInput.vue";
import AttachmentPreview from "@/components/input/AttachmentPreview.vue";
import { MessageType, MessageRole } from "@/types/chat";
import type { Attachment, Suggestion } from "@/types/chat";
import { chatApi, type ModelInfo } from "@/services/api";
@ -119,6 +127,14 @@ const inputPlaceholder = computed(() => {
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;

View File

@ -3,13 +3,6 @@
class="chat-input-container"
:class="{ 'is-focused': isFocused, 'is-expanded': isExpanded }"
>
<!-- 附件预览区 -->
<AttachmentPreview
v-if="attachments.length > 0"
:attachments="attachments"
@remove="removeAttachment"
/>
<!-- 输入区域 -->
<div class="input-area">
<!-- 左侧功能按钮 -->
@ -414,15 +407,30 @@ 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);
}
//
function toggleDeepSearch() {
isDeepSearch.value = !isDeepSearch.value;
@ -470,6 +478,8 @@ function clear() {
defineExpose({
focus,
clear,
attachments,
removeAttachment,
});
//

View File

@ -340,6 +340,23 @@ class ChatApi {
return response.json();
}
/**
* OSS
*/
async deleteAttachment(url: string): Promise<void> {
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}`);
}
}
}
// 导出单例