Merge branch 'feat/kexue-ui'
This commit is contained in:
commit
d81fb4d0a0
|
|
@ -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}
|
||||
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)}")
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,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,
|
||||
});
|
||||
|
||||
// 监听文本变化,自动调整高度
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
|
|
|||
Loading…
Reference in New Issue