|
|
@@ -65,21 +65,24 @@
|
|
|
@download-word="downloadWordReport"
|
|
|
@export-md="handleExportMarkdown"
|
|
|
@export-word="handleExportWord"
|
|
|
- @file-preview="openFilePreview"
|
|
|
+ @file-preview="handleAttachmentPreview"
|
|
|
+ @download-attachment="handleAttachmentDownload"
|
|
|
@agent-detail-click="handleAgentDetailClick"
|
|
|
@locate-message="handleLocateMessage"
|
|
|
@open-web="openWebTab"
|
|
|
/>
|
|
|
|
|
|
<!-- 附件列表面板 -->
|
|
|
- <FileListPanel :visible="showFileList" :files="getCurrentTaskFiles()" @close="showFileList = false" @file-click="openFilePreview" />
|
|
|
+ <FileListPanel :visible="showFileList" :files="getCurrentTaskFiles()" @close="showFileList = false" @file-click="handleAttachmentPreview" />
|
|
|
|
|
|
<!-- 消息列表 -->
|
|
|
<ChatMessages
|
|
|
ref="chatMessagesRef"
|
|
|
:messages="messages"
|
|
|
:history-loading="isCurrentHistoryLoading"
|
|
|
- @file-preview="openFilePreview"
|
|
|
+ :orphan-attachments="currentTask?.orphanAttachments || []"
|
|
|
+ @file-preview="handleAttachmentPreview"
|
|
|
+ @download-attachment="handleAttachmentDownload"
|
|
|
@download-word="downloadWordFile"
|
|
|
@open-file-preview="handleOpenFilePreview"
|
|
|
@ask-user-submit="handleAskUserSubmit"
|
|
|
@@ -190,7 +193,7 @@
|
|
|
import SchedulePanel from '/@/views/ventAI/manageAssistent/components/panels/SchedulePanel.vue';
|
|
|
import RightPanelTabs from '/@/views/ventAI/manageAssistent/components/panels/RightPanelTabs.vue';
|
|
|
import { getScheduleList } from '/@/views/ventAI/manageAssistent/api';
|
|
|
- import type { Message, ScheduleTask, Task } from '/@/views/ventAI/manageAssistent/types/index';
|
|
|
+ import type { AttachedFile, Message, ScheduleTask, Task } from '/@/views/ventAI/manageAssistent/types/index';
|
|
|
import { buildCapsuleFiles, buildCapsuleLinks, buildCapsuleAgents } from '/@/views/ventAI/manageAssistent/lib/capsule';
|
|
|
import { useChatStream } from '../hooks/useChatStream';
|
|
|
import { useChatTasks } from '../hooks/useChatTasks';
|
|
|
@@ -203,6 +206,7 @@
|
|
|
import { useFileDrop } from '../hooks/useFileDrop';
|
|
|
import { useChatQuota } from '../hooks/useChatQuota';
|
|
|
import { useChatExport } from '../hooks/useChatExport';
|
|
|
+ import { useChatAttachments } from '../hooks/useChatAttachments';
|
|
|
|
|
|
interface Props {
|
|
|
visible: boolean;
|
|
|
@@ -295,6 +299,10 @@
|
|
|
defaultFullscreen: !!props.defaultFullscreen,
|
|
|
});
|
|
|
|
|
|
+ // —— 会话附件历史:列表按会话拉取,附件内容在预览/下载时按需取回 ——
|
|
|
+ const { loadHistoryAttachments, handleAttachmentPreview: previewAttachment, handleAttachmentDownload } = useChatAttachments();
|
|
|
+ const handleAttachmentPreview = (file: AttachedFile) => previewAttachment(file, openFilePreview);
|
|
|
+
|
|
|
// —— 拖拽/居中职责已下沉到 useChatDrag(与面板全屏共用 lib/modalWrap) ——
|
|
|
const drag = useChatDrag({
|
|
|
getIsFullscreen: () => isFullscreen.value,
|
|
|
@@ -380,6 +388,17 @@
|
|
|
if (nextTask) switchTask(nextTask.id);
|
|
|
};
|
|
|
|
|
|
+ // 消息被删除后,附件文件仍保留在服务端(message_id 变为 null);
|
|
|
+ // 这里把本地附件立即转入兜底列表,避免要刷新后才重新出现。
|
|
|
+ const moveAttachmentsToOrphans = (task: Task | undefined, files: AttachedFile[]) => {
|
|
|
+ if (!task || !files.length) return;
|
|
|
+ const merged = [...(task.orphanAttachments || [])];
|
|
|
+ for (const file of files) {
|
|
|
+ if (!merged.some((item) => item.id === file.id)) merged.push(file);
|
|
|
+ }
|
|
|
+ task.orphanAttachments = merged;
|
|
|
+ };
|
|
|
+
|
|
|
// 删除单条消息:先删数据库行,后端再清空该会话内存线程,下轮从数据库重建上下文
|
|
|
const handleDeleteMessage = (msg: Message) => {
|
|
|
if (!msg.id) return;
|
|
|
@@ -407,6 +426,7 @@
|
|
|
message.success('消息已删除');
|
|
|
if (ownerTask) {
|
|
|
ownerTask.messages = ownerTask.messages.filter((m) => m !== msg);
|
|
|
+ moveAttachmentsToOrphans(ownerTask, [msg.attachedFile, ...(msg.attachedFiles || [])].filter(Boolean) as AttachedFile[]);
|
|
|
}
|
|
|
} catch {
|
|
|
message.error('删除失败');
|
|
|
@@ -566,6 +586,30 @@
|
|
|
return currentTask?.sessionId || '';
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * 拉取单个会话的历史消息与附件。
|
|
|
+ * 流式中的任务不拉历史(避免覆盖在途消息);附件加载失败不阻断消息展示。
|
|
|
+ */
|
|
|
+ const loadTaskHistory = async (task: Task) => {
|
|
|
+ const sessionId = task.sessionId;
|
|
|
+ if (!sessionId || historyLoadedTasks.has(task.id) || task.isStreaming) return;
|
|
|
+
|
|
|
+ setHistoryLoading(task.id, true);
|
|
|
+ try {
|
|
|
+ const res = await getDetail(sessionId);
|
|
|
+ const history = Array.isArray(res?.messages) ? transformHistoryToMessages(res.messages) : [];
|
|
|
+ task.messages = history;
|
|
|
+ await loadHistoryAttachments(task, sessionId, history);
|
|
|
+ historyLoadedTasks.add(task.id);
|
|
|
+ } catch (error) {
|
|
|
+ // 拉不到历史时界面会是空白会话,容易被误认为「对话丢了」,必须给出提示
|
|
|
+ console.error('加载历史会话失败:', error);
|
|
|
+ message.error('加载历史会话失败,请稍后重试');
|
|
|
+ } finally {
|
|
|
+ setHistoryLoading(task.id, false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const switchTask = async (taskId: string) => {
|
|
|
activePanel.value = 'chat';
|
|
|
|
|
|
@@ -579,26 +623,8 @@
|
|
|
showFileList.value = false;
|
|
|
|
|
|
if (newTask.sessionId && !historyLoadedTasks.has(taskId)) {
|
|
|
- // 流式中的任务不拉历史(避免覆盖在途消息),但必须继续走完下面的初始化:
|
|
|
- // currentTaskId 已经切过来了,提前 return 会让上下文用量/模式/进度条/滚动全部停在上一个任务上。
|
|
|
- if (!newTask.isStreaming) {
|
|
|
- // 标记加载中:消息区据 isCurrentHistoryLoading 显示加载态,而不是先闪一屏空会话
|
|
|
- setHistoryLoading(taskId, true);
|
|
|
- try {
|
|
|
- const res = await getDetail(newTask.sessionId);
|
|
|
- const messages = res?.messages;
|
|
|
- if (Array.isArray(messages)) {
|
|
|
- newTask.messages = transformHistoryToMessages(messages);
|
|
|
- }
|
|
|
- historyLoadedTasks.add(taskId);
|
|
|
- } catch (error) {
|
|
|
- // 拉不到历史时界面会是空白会话,容易被误认为「对话丢了」,必须给出提示
|
|
|
- console.error('加载历史会话失败:', error);
|
|
|
- message.error('加载历史会话失败,请稍后重试');
|
|
|
- } finally {
|
|
|
- setHistoryLoading(taskId, false);
|
|
|
- }
|
|
|
- }
|
|
|
+ // 流式中的任务由 loadTaskHistory 内部跳过,但下面的上下文/模式/进度条初始化必须继续执行
|
|
|
+ await loadTaskHistory(newTask);
|
|
|
}
|
|
|
|
|
|
if (newTask.sessionId) {
|
|
|
@@ -889,22 +915,9 @@
|
|
|
|
|
|
currentTaskId.value = targetTask.id;
|
|
|
|
|
|
- // 如果是历史任务,加载其消息
|
|
|
+ // 如果是历史任务,加载其消息与附件
|
|
|
if (targetTask.sessionId && !historyLoadedTasks.has(targetTask.id)) {
|
|
|
- setHistoryLoading(targetTask.id, true);
|
|
|
- try {
|
|
|
- const res = await getDetail(targetTask.sessionId);
|
|
|
- const messages = res?.messages;
|
|
|
- if (Array.isArray(messages)) {
|
|
|
- targetTask.messages = transformHistoryToMessages(messages);
|
|
|
- }
|
|
|
- historyLoadedTasks.add(targetTask.id);
|
|
|
- } catch (error) {
|
|
|
- console.error('加载历史会话失败:', error);
|
|
|
- message.error('加载历史会话失败,请稍后重试');
|
|
|
- } finally {
|
|
|
- setHistoryLoading(targetTask.id, false);
|
|
|
- }
|
|
|
+ await loadTaskHistory(targetTask);
|
|
|
}
|
|
|
|
|
|
if (targetTask.sessionId) {
|