From 33c03e30d934b3c5407f4fb677cbd0c50305a245 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Tue, 9 Jun 2026 10:49:34 +0800 Subject: [PATCH] fix(web): switch to a sibling session when deleting the active one --- channel/web/chat.html | 2 +- channel/web/static/js/console.js | 51 +++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/channel/web/chat.html b/channel/web/chat.html index 1af8e334..3a56f8c1 100644 --- a/channel/web/chat.html +++ b/channel/web/chat.html @@ -772,7 +772,7 @@ diff --git a/channel/web/static/js/console.js b/channel/web/static/js/console.js index 00598e25..e50ff0c9 100644 --- a/channel/web/static/js/console.js +++ b/channel/web/static/js/console.js @@ -123,7 +123,7 @@ const I18N = { config_max_turns: '最大记忆轮次', config_max_turns_hint: '一问一答为一轮,超过后会智能压缩处理', config_max_steps: '最大执行步数', config_max_steps_hint: '单次对话中 Agent 最多调用工具的次数', config_enable_thinking: '深度思考', config_enable_thinking_hint: '是否启用深度思考模式', - config_self_evolution: '自进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项', + config_self_evolution: '自主进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项', evolution_badge: '自主学习', config_channel_type: '通道类型', config_provider: '模型厂商', config_model_name: '模型', @@ -142,7 +142,7 @@ const I18N = { skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用', skill_toggle_error: '操作失败,请稍后再试', memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容', - memory_tab_files: '记忆文件', memory_tab_dreams: '自进化', + memory_tab_files: '记忆文件', memory_tab_dreams: '自主进化', memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处', memory_back: '返回列表', memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间', @@ -3303,7 +3303,7 @@ function addLoadingIndicator() { return el; } -function newChat() { +function newChat(optimistic = true) { // Do NOT close active streams: other sessions keep streaming in the // background (each stream self-guards against the foreign view) and their // replies still complete and persist. @@ -3407,8 +3407,16 @@ function newChat() { _showSessionOverlay(); _persistPanelState(); } + // Only prepend an optimistic "new chat" item when this is a real new-chat + // action. When called after deleting the current session, skip it: the + // fresh session has no backend record yet, so inserting it would leave an + // empty, undeletable item in the list (deleting it just spawns another). const newSid = sessionId; - loadSessionList(() => _addOptimisticSessionItem(newSid)); + if (optimistic) { + loadSessionList(() => _addOptimisticSessionItem(newSid)); + } else { + loadSessionList(); + } } // ===================================================================== @@ -3729,20 +3737,49 @@ function switchSession(newSessionId) { function deleteSession(sid) { showConfirmModal(t('delete_session_title'), t('delete_session_confirm'), () => { + // Before deleting, find the next real session to fall back to when the + // current one is removed (the sibling item in the list, which is sorted + // newest-first). Falls back to the welcome screen if none remain. + const nextSid = sid === sessionId ? _findNextSessionId(sid) : null; + fetch(`/api/sessions/${encodeURIComponent(sid)}`, { method: 'DELETE' }) .then(r => r.json()) .then(data => { if (data.status !== 'success') return; - if (sid === sessionId) { - newChat(); - } else { + if (sid !== sessionId) { loadSessionList(); + return; + } + if (nextSid) { + // Switch to an existing session; refresh the list afterwards + // so the deleted item disappears. + switchSession(nextSid); + loadSessionList(); + } else { + // No other sessions: reset to a fresh empty session without + // inserting an optimistic placeholder (it has no backend + // record and would be an empty, undeletable item). + newChat(false); } }) .catch(() => {}); }); } +// Pick the session to show after deleting `sid` (the current session): prefer +// the next item below it in the list, otherwise the previous one. Returns null +// if no other session exists. +function _findNextSessionId(sid) { + const items = Array.from(document.querySelectorAll('.session-item[data-session-id]')); + const idx = items.findIndex(el => el.dataset.sessionId === sid); + if (idx === -1) { + const other = items.find(el => el.dataset.sessionId !== sid); + return other ? other.dataset.sessionId : null; + } + const next = items[idx + 1] || items[idx - 1]; + return next ? next.dataset.sessionId : null; +} + function showConfirmModal(title, message, onConfirm) { let overlay = document.getElementById('confirm-modal-overlay'); if (overlay) overlay.remove();