diff --git a/src/api/settings/session/index.ts b/src/api/settings/session/index.ts deleted file mode 100644 index bdd100d..0000000 --- a/src/api/settings/session/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import request from '/@/utils/request'; - -/** - * 查询会话问答消息记录(普通对话历史)。 - * 依赖后端接口,URL/返回结构待后端提供;接口未就绪前由调用方以 localStorage 兜底。 - */ -export function getSessionMessages(sessionId: string) { - return request({ - url: '/ai-agent/session/getMessages', - method: 'get', - params: { sessionId }, - }); -} diff --git a/src/api/settings/workflow/session.ts b/src/api/settings/workflow/session.ts new file mode 100644 index 0000000..8f9a7a4 --- /dev/null +++ b/src/api/settings/workflow/session.ts @@ -0,0 +1,54 @@ +import request, { type RequestOptions } from '/@/utils/request'; + +// ===== 会话管理(/ai-agent/session/*)===== +// 后端结构: +// ListSessionReq { page } → ListSessionRes { list: VOSession[], total } +// GetSessionInfoReq { page, sessionId } → GetSessionInfoRes { list: VOSessionInfoResult[], total } +// DeleteSessionReq { id }(POST,级联删除普通对话结果,工作流结果保留) +// 说明:会话为「会话 → 会话内结果(工作流/普通对话混排,按时间倒序)」模型。 + +export interface VOSession { + id: string; + sessionName: string; + createdAt: string; +} + +export interface VOSessionInfoResult { + id: string; + type: 'workflow' | 'chat'; + status: number; // 1-运行中, 2-成功, 3-失败 + flowId: string; + requestParams: Record; + resultFileUrl: string; + totalTokens: number; + totalFee: number; + errorMsg: string; + createdAt: string; +} + +export function getSessionListV2(params?: Record, requestOptions?: RequestOptions) { + return request({ + url: '/ai-agent/session/list', + method: 'get', + params, + requestOptions, + }) as Promise<{ code: number; message: string; data: { list: VOSession[]; total: number } }>; +} + +export function getSessionResults(sessionId: string, params?: Record, requestOptions?: RequestOptions) { + return request({ + url: '/ai-agent/session/get', + method: 'get', + params: { sessionId, ...params }, + requestOptions, + }) as Promise<{ code: number; message: string; data: { list: VOSessionInfoResult[]; total: number } }>; +} + +export function deleteSessionV2(id: string, requestOptions?: RequestOptions) { + return request({ + url: '/ai-agent/session/delete', + method: 'post', + data: { id }, + requestOptions, + }); +} diff --git a/src/views/home/components/ChatList.vue b/src/views/home/components/ChatList.vue index ce31b1e..e356e41 100644 --- a/src/views/home/components/ChatList.vue +++ b/src/views/home/components/ChatList.vue @@ -40,6 +40,9 @@
+ @@ -53,7 +56,7 @@ + + diff --git a/src/views/home/components/Sidebar.vue b/src/views/home/components/Sidebar.vue index 11b7cb6..cd40d61 100644 --- a/src/views/home/components/Sidebar.vue +++ b/src/views/home/components/Sidebar.vue @@ -6,7 +6,7 @@ AI 助手
- + @@ -17,7 +17,7 @@ -
暂无对话记录
+
暂无会话记录
diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 732885d..a746a70 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -21,10 +21,24 @@ :workflow-detail="selectedWorkflowDetail" :active-history-id="activeHistoryId" :messages="currentMessages" + :results="currentSessionResults" + :results-loading="currentSessionResultsLoading" @retry="handleRetry" - @workflow-select="handlePlaceWorkflowSelect" + @regenerate="handleRetry" + @preview-result="handlePreviewResult" + @download-result="handleDownloadResult" + @workflow-select="handlePlaceWorkflowSelect" + /> + - @@ -63,17 +77,17 @@ import { applyHomeFormValues } from './utils/flowDsl'; import { getChatModel } from '/@/api/settings/modelConfigV2'; import { openWsExecute } from './utils/wsExecute'; import { parseWsMessage, getDelta, getAnswer, getErrorText, getToolCallName, getToolResultText } from './utils/wsMessage'; -import { getSessionMessages } from '/@/api/settings/session'; import type { ExecutionTreeItem } from '/@/api/settings/creation'; import { getExecutionList, getWorkflowDetail, getExecutionDetail, deleteExecutionResult, - deleteExecutionSession, - getSessionList, downloadToFile, } from '/@/api/settings/creation'; +import { getSessionListV2, getSessionResults, deleteSessionV2 } from '/@/api/settings/workflow/session'; +import type { VOSessionInfoResult } from '/@/api/settings/workflow/session'; +import { getUpFileUrl } from '/@/utils/gfast'; interface HistoryItem { id: string; @@ -161,26 +175,46 @@ const previewNode = (data: TreeNode) => { previewDialogVisible.value = true; }; -const downloadNode = async (data: TreeNode) => { - if (!data.fileUrl) return; - const url = buildAssetUrl(data.fileUrl); +const getUrlExt = (url: string): string => { + const m = String(url || '').split('?')[0].toLowerCase().match(/\.([a-z0-9]+)$/); + return m ? m[1] : ''; +}; + +// 会话结果文件预览(复用预览弹窗) +const handlePreviewResult = (url: string) => { + if (!url) return; + previewUrl.value = buildAssetUrl(url); + previewMode.value = getPreviewMode(getUrlExt(url)); + previewDialogVisible.value = true; +}; + +// 会话结果文件下载 +const handleDownloadResult = (url: string) => { + if (!url) return; + const name = String(url).split('/').pop()?.split('?')[0] || 'result'; + downloadAsset(url, name); +}; + +// 通用文件下载:优先后端代理,兜底 a 标签 +const downloadAsset = async (url: string, name: string) => { try { // 优先通过后端代理下载,避免跨域问题 - const res = await downloadToFile({ fileURL: data.fileUrl }); + const res = await downloadToFile({ fileURL: url }); const blob = res.data as Blob; const blobUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; - a.download = data.label; + a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); } catch { // 兜底:直接用 a 标签下载 + const full = buildAssetUrl(url); const a = document.createElement('a'); - a.href = url; - a.download = data.label; + a.href = full; + a.download = name; a.target = '_blank'; a.rel = 'noopener'; document.body.appendChild(a); @@ -189,6 +223,11 @@ const downloadNode = async (data: TreeNode) => { } }; +const downloadNode = async (data: TreeNode) => { + if (!data.fileUrl) return; + await downloadAsset(data.fileUrl, data.label); +}; + const handleDeleteNode = async (data: TreeNode) => { try { await ElMessageBox.confirm(`确定删除「${data.label}」?此操作不可恢复。`, '确认删除', { @@ -238,30 +277,33 @@ const getSessionData = (res: any): any[] => { const getList = async () => { treeLoading.value = true; try { - const [execRes, sessionRes] = await Promise.all([getExecutionList(), getSessionList({ pageSize: 10 })]); + const [execRes, sessionRes] = await Promise.all([getExecutionList(), getSessionListV2({ pageNum: 1, pageSize: 10 })]); imgAddressPrefix.value = execRes.data?.imgAddressPrefix || ''; treeNodes.value = buildTreeNodes(execRes.data?.tree || []); const d = new Date(); const todayStr = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); - const sessions = getSessionData(sessionRes).map((s) => { - const rawTime = s.createdAt || s.createDate || ''; - let displayTime = rawTime; - if (rawTime) { - const datePart = rawTime.substring(0, 10); - if (datePart === todayStr) { - displayTime = rawTime.substring(11, 16); - } else { - displayTime = datePart; + const sessions = getSessionData(sessionRes) + // 后端个别会话 sessionId 可能为空串,空 id 无法点击查询详情,过滤掉 + .filter((s) => s.sessionId) + .map((s) => { + const rawTime = s.createdAt || ''; + let displayTime = rawTime; + if (rawTime) { + const datePart = rawTime.substring(0, 10); + if (datePart === todayStr) { + displayTime = rawTime.substring(11, 16); + } else { + displayTime = datePart; + } } - } - return { - id: s.id, - sessionId: s.sessionId, - title: s.flowName || '未命名会话', - time: displayTime, - status: mapSessionStatus(s.status), - }; - }); + return { + id: String(s.sessionId), + sessionId: String(s.sessionId), + title: s.sessionName || '未命名会话', + time: displayTime, + status: mapSessionStatus(s.status), + }; + }); historyList.value = sessions; // 为每个后端会话初始化消息存储 sessions.forEach((s) => { @@ -281,7 +323,13 @@ const getList = async () => { const selectedWorkflowDetail = ref(null); const mainContentRef = ref(null); const sendingSessions = reactive>({}); +// 当前正在执行的 WebSocket(用于停止生成);cancelledWs 记录被主动取消的连接,避免其 onClose 误判为正常完成 +const currentWs = ref(null); +const cancelledWs = new Set(); const isHistoryWorkflow = ref(false); + +// 生成中:有活跃会话且该会话正处于发送状态(发送按钮切换为停止按钮) +const isGenerating = computed(() => !!activeHistoryId.value && !!sendingSessions[activeHistoryId.value]); const inputBarRef = ref(null); const templateDialogVisible = ref(false); const pendingTemplate = ref(null); @@ -300,6 +348,18 @@ const currentMessages = computed(() => { return sessionMessages.value.get(id) || []; }); +// 会话内结果(session/get,workflow+chat 混排):key = 会话 id +const sessionResultsMap = reactive>({}); +const sessionResultsLoading = reactive>({}); +const currentSessionResults = computed(() => { + const id = activeHistoryId.value; + return id ? sessionResultsMap[id] || [] : []; +}); +const currentSessionResultsLoading = computed(() => { + const id = activeHistoryId.value; + return !!id && !!sessionResultsLoading[id]; +}); + // 占位态:无工作流且无消息(MainContent 显示引导页)时隐藏输入框快捷胶囊,避免与占位卡片重复 const isPlaceholder = computed(() => !selectedWorkflowDetail.value && currentMessages.value.length === 0); @@ -355,39 +415,12 @@ const handleMenuChange = (menu: string) => { const formatTime = (d: Date) => String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); -// ===== 消息缓存(普通对话历史;后端接口就绪前 localStorage 兜底)===== -const CHAT_CACHE_KEY = 'home_chat_msgs_'; -const chatCacheKey = (sid: string) => CHAT_CACHE_KEY + sid; -const loadChatCache = (sid: string): ChatMessage[] => { - try { - const raw = localStorage.getItem(chatCacheKey(sid)); - return raw ? (JSON.parse(raw) as ChatMessage[]) : []; - } catch { - return []; - } -}; -const saveChatCache = (sid: string, msgs: ChatMessage[]) => { - try { - localStorage.setItem(chatCacheKey(sid), JSON.stringify(msgs)); - } catch { - /* 存储不可用时忽略 */ - } -}; -const clearChatCache = (sid: string) => { - try { - localStorage.removeItem(chatCacheKey(sid)); - } catch { - /* ignore */ - } -}; - const addMessage = (msg: ChatMessage) => { const id = activeHistoryId.value; if (!id) return; const list = sessionMessages.value.get(id); if (list) list.push(msg); else sessionMessages.value.set(id, [msg]); - saveChatCache(id, sessionMessages.value.get(id) || []); }; const handleSend = async (message: string) => { @@ -473,7 +506,6 @@ const handleRetry = async (msg: ChatMessage) => { if (!question) return; // 移除失败消息,重新执行 list.splice(idx, 1); - saveChatCache(sid, list); sendingSessions[sid] = true; session.status = 'executing'; const sessionId = session.sessionId || getSessionId(); @@ -486,6 +518,42 @@ const handleRetry = async (msg: ChatMessage) => { } }; +// ===== 停止生成:发送 workflow_cancel 并关闭连接,保留已生成内容 ===== +const handleStopGenerate = () => { + const sid = activeHistoryId.value; + if (!sid || !sendingSessions[sid]) return; + const ws = currentWs.value; + if (ws && ws.readyState === WebSocket.OPEN) { + cancelledWs.add(ws); + try { + ws.send(JSON.stringify({ type: 'workflow_cancel' })); + } catch { + /* 连接异常时忽略,直接关闭 */ + } + ws.close(); + } + currentWs.value = null; + // 标记正在生成的 AI 消息:已有内容则保留;无内容则显示停止提示 + const list = sessionMessages.value.get(sid); + const last = list && list.length > 0 ? list[list.length - 1] : null; + if (last && !last.isUser) { + last.loading = false; + if (!last.content) last.content = '⚠️ 已停止生成'; + } else if (list) { + // 工作流执行模式:无 AI 气泡,追加一条停止提示 + addMessage({ + id: 'msg-' + Date.now() + '-stop', + content: '⚠️ 已停止生成', + time: formatTime(new Date()), + isUser: false, + }); + } + // 清理会话状态(runChat 的 onClose 会再走一次 finishChat,幂等) + const session = historyList.value.find((h) => h.id === sid); + if (session && session.status === 'executing') session.status = 'completed'; + delete sendingSessions[sid]; +}; + // ===== 工作流执行:选中工作流 → 表单页 WS 执行 → 完成后切回对话页 ===== const runWorkflow = async (sid: string, sessionId: string, message: string, mc: any) => { const curSession = historyList.value.find((h) => h.id === sid); @@ -518,13 +586,15 @@ const runWorkflow = async (sid: string, sessionId: string, message: string, mc: } // 虚拟会话执行后刷新会话列表替换真实条目 if (curSession.id.startsWith('virtual_')) { - getSessionList().then((sessionRes) => { - const freshList = getSessionData(sessionRes).map((s: any) => ({ - id: s.id, - sessionId: s.sessionId, - title: s.flowName || '未命名会话', - time: s.createDate?.substring(0, 10) || '', - })); + getSessionListV2({ pageNum: 1, pageSize: 10 }).then((sessionRes) => { + const freshList = getSessionData(sessionRes) + .filter((s: any) => s.sessionId) + .map((s: any) => ({ + id: String(s.sessionId), + sessionId: String(s.sessionId), + title: s.sessionName || '未命名会话', + time: s.createdAt?.substring(0, 10) || '', + })); const match = freshList.find((s: any) => s.sessionId === curSession.sessionId); if (match) { const idx = historyList.value.findIndex((h) => h.id === curSession.id); @@ -537,8 +607,12 @@ const runWorkflow = async (sid: string, sessionId: string, message: string, mc: activeHistoryId.value = match.id; } } + loadSessionResults(match.id); } }); + } else { + // 非虚拟会话:执行完成/失败后直接刷新会话结果 + loadSessionResults(sid); } delete sendingSessions[sid]; // 执行完成后切回对话页:清空工作流选择,主区域展示消息流 @@ -597,10 +671,14 @@ const runWorkflow = async (sid: string, sessionId: string, message: string, mc: finishExec(false, '执行连接失败,请重试'); }, onClose: () => { + // 主动停止(handleStopGenerate 已发 workflow_cancel 并 close)时跳过, + // 避免被误判为正常完成(工作流完成/失败提示由停止逻辑单独处理) + if (ws && cancelledWs.has(ws)) return; // 连接关闭兜底视为完成;若已触发 onError 则按失败处理 if (!finished) finishExec(!wsFailed); }, }); + currentWs.value = ws; if (!ws) { finishExec(false, 'WebSocket 初始化失败,请检查服务地址'); } @@ -634,7 +712,6 @@ const runChat = async (sid: string, sessionId: string, message: string) => { const ai = aiIdx >= 0 && list ? list[aiIdx] : null; if (ai && ai.thinking === '思考中…') { ai.thinking = ''; - saveChatCache(sid, list || []); } twBuffer += text; startTypewriter(); @@ -658,7 +735,6 @@ const runChat = async (sid: string, sessionId: string, message: string) => { ai.thinking = (ai.thinking || '') + twBuffer.slice(0, take); twBuffer = twBuffer.slice(take); ai.loading = false; - saveChatCache(sid, list || []); }, 100); }; @@ -677,7 +753,6 @@ const runChat = async (sid: string, sessionId: string, message: string) => { const ai = aiIdx >= 0 && list ? list[aiIdx] : null; if (ai) { ai.thinking = (ai.thinking || '') + twBuffer; - saveChatCache(sid, list || []); } twBuffer = ''; }; @@ -710,13 +785,15 @@ const runChat = async (sid: string, sessionId: string, message: string) => { if (session) session.status = success ? 'completed' : 'failed'; // 虚拟会话对话后刷新会话列表替换真实条目 if (session && session.id.startsWith('virtual_')) { - getSessionList().then((sessionRes) => { - const freshList = getSessionData(sessionRes).map((s: any) => ({ - id: s.id, - sessionId: s.sessionId, - title: s.flowName || '未命名会话', - time: s.createDate?.substring(0, 10) || '', - })); + getSessionListV2({ pageNum: 1, pageSize: 10 }).then((sessionRes) => { + const freshList = getSessionData(sessionRes) + .filter((s: any) => s.sessionId) + .map((s: any) => ({ + id: String(s.sessionId), + sessionId: String(s.sessionId), + title: s.sessionName || '未命名会话', + time: s.createdAt?.substring(0, 10) || '', + })); const match = freshList.find((s: any) => s.sessionId === session.sessionId); if (match) { const idx = historyList.value.findIndex((h) => h.id === session.id); @@ -725,15 +802,17 @@ const runChat = async (sid: string, sessionId: string, message: string) => { sessionMessages.value.delete(session.id); sessionMessages.value.set(match.id, msgs || []); historyList.value[idx] = { ...match, status: session.status }; - saveChatCache(match.id, msgs || []); if (activeHistoryId.value === session.id) { activeHistoryId.value = match.id; } } + loadSessionResults(match.id); } }); + } else { + // 非虚拟会话:对话完成/失败后直接刷新会话结果 + loadSessionResults(sid); } - saveChatCache(sid, sessionMessages.value.get(sid) || []); delete sendingSessions[sid]; }; @@ -801,7 +880,6 @@ const runChat = async (sid: string, sessionId: string, message: string) => { if (ai && delta) { ai.loading = false; ai.content = (ai.content || '') + delta; - saveChatCache(sid, list || []); } return; } @@ -812,7 +890,6 @@ const runChat = async (sid: string, sessionId: string, message: string) => { ai.loading = false; ai.content = answer; ai.thinkingSeconds = thinkStart ? Math.max(1, Math.round((Date.now() - thinkStart) / 1000)) : undefined; - saveChatCache(sid, list || []); } finishChat(true); return; @@ -829,10 +906,12 @@ const runChat = async (sid: string, sessionId: string, message: string) => { finishChat(false, '对话连接失败,请重试'); }, onClose: () => { - // 连接关闭兜底完成 + // 连接关闭兜底完成(主动停止时 handleStopGenerate 已把 AI 消息标记为「已停止生成」, + // 此处走成功分支:flush 打字机剩余思考、清理会话状态,且不覆盖已有内容) if (!done) finishChat(!failed); }, }); + currentWs.value = ws; if (!ws) { finishChat(false, 'WebSocket 初始化失败,请检查服务地址'); } @@ -842,12 +921,15 @@ const handleSelectHistory = async (id: string) => { activeHistoryId.value = id; activeMenu.value = 'chat'; const session = historyList.value.find((h) => h.id === id); + // 加载会话内结果列表(session/get),用它判断会话类型:普通会话(无 workflow 结果)只依赖这一个接口,不再调 execution/get + const results = await loadSessionResults(id); + const hasWorkflow = Array.isArray(results) && results.some((r) => r.type === 'workflow'); let asForm = false; - if (session && !session.id.startsWith('virtual_')) { + if (session && hasWorkflow) { + // 工作流会话:查 execution/get 回显表单(此处逻辑暂保留,后续再优化为用 session/get 数据) try { const res = await getExecutionDetail(session.id); if (res.data) { - // 工作流会话:回显表单页(可改参数重跑),并锁定工作流选择 selectedWorkflowDetail.value = res.data; isHistoryWorkflow.value = true; asForm = true; @@ -863,11 +945,11 @@ const handleSelectHistory = async (id: string) => { } } if (!asForm) { - // 普通对话会话 / 虚拟会话:切回对话页,加载消息记录 + // 普通对话会话 / 虚拟会话:切回对话页,把 session/get 结果转换为正常对话消息流展示 selectedWorkflowDetail.value = null; isHistoryWorkflow.value = false; inputBarRef.value?.clearWorkflow?.(); - await loadSessionMessages(id); + await loadSessionMessagesFromResults(id, results); } // 清除可重新执行的标记 if (session?.status === 'completed' || session?.status === 'failed' || (session?.status === 'executing' && !sendingSessions[session.id])) { @@ -875,35 +957,58 @@ const handleSelectHistory = async (id: string) => { } }; -// 加载会话消息:优先后端接口,接口未就绪时读本地缓存兜底 -const loadSessionMessages = async (sid: string) => { - const existing = sessionMessages.value.get(sid); - if (existing && existing.length > 0) return; // 内存已有消息直接保留 +// 加载会话内结果(session/get):workflow+chat 混排,按时间倒序;返回结果列表供判断会话类型 +const loadSessionResults = async (sid: string) => { + if (!sid) return []; + sessionResultsLoading[sid] = true; try { - const res: any = await getSessionMessages(sid); - const list = Array.isArray(res?.data) ? res.data : res?.data?.list || res?.data?.messages; - if (Array.isArray(list) && list.length > 0) { - const msgs: ChatMessage[] = list.map((m: any, i: number) => ({ - id: m.id || 'msg-' + Date.now() + '-' + i, - content: m.content ?? m.message ?? m.reply ?? '', - time: m.time || m.createDate || '', - isUser: m.isUser === true || m.role === 'user', - })); - sessionMessages.value.set(sid, msgs); - saveChatCache(sid, msgs); - return; - } + const res: any = await getSessionResults(sid); + const list = Array.isArray(res?.data?.list) ? res.data.list : Array.isArray(res?.data) ? res.data : []; + sessionResultsMap[sid] = list; + return list; } catch { - // 接口不可用 → 兜底本地缓存 - } - const cached = loadChatCache(sid); - if (cached.length > 0) { - sessionMessages.value.set(sid, cached); - } else if (!sessionMessages.value.has(sid)) { - sessionMessages.value.set(sid, []); + if (!sessionResultsMap[sid]) sessionResultsMap[sid] = []; + return sessionResultsMap[sid] || []; + } finally { + sessionResultsLoading[sid] = false; } }; +// 普通对话会话回显:把 session/get 的 chat 结果转换为「问题(用户) + 回复(AI)」消息流,正常对话气泡展示 +const loadSessionMessagesFromResults = async (sid: string, results: VOSessionInfoResult[]) => { + const chats = (results || []).filter((r) => r.type === 'chat'); + const msgs: ChatMessage[] = []; + const pending: { msg: ChatMessage; url: string }[] = []; + // 结果按时间倒序 → 反转成正序,再按记录生成问答 + [...chats].reverse().forEach((r) => { + const q = r.requestParams?.question; + if (q) { + msgs.push({ id: 'rq-' + r.id, content: String(q), time: r.createdAt || '', isUser: true }); + } + if (r.resultFileUrl) { + const ai: ChatMessage = { id: 'ra-' + r.id, content: '加载中…', time: r.createdAt || '', isUser: false, loading: true }; + msgs.push(ai); + pending.push({ msg: ai, url: r.resultFileUrl }); + } + }); + sessionMessages.value.set(sid, msgs); + // 异步读取 txt 填充 AI 回复 + await Promise.all( + pending.map(async ({ msg, url }) => { + try { + const res = await fetch(getUpFileUrl(url)); + if (!res.ok) throw new Error('HTTP ' + res.status); + msg.content = await res.text(); + } catch { + msg.content = '(回复加载失败)'; + } finally { + msg.loading = false; + } + }) + ); +}; + + const createNewSession = () => { const sessionId = `virtual_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; historyList.value.unshift({ @@ -938,7 +1043,7 @@ const handleDeleteHistory = async (id: string) => { const isVirtual = id.startsWith('virtual_'); if (!isVirtual) { try { - await deleteExecutionSession({ id }); + await deleteSessionV2(id); } catch { // 后端已提示错误,继续本地删除 } @@ -947,7 +1052,8 @@ const handleDeleteHistory = async (id: string) => { if (idx < 0) return; historyList.value.splice(idx, 1); sessionMessages.value.delete(id); - clearChatCache(id); + sessionResultsMap[id] = []; + delete sessionResultsLoading[id]; if (activeHistoryId.value === id) { if (historyList.value.length > 0) { activeHistoryId.value = historyList.value[0].id;