diff --git a/channel/web/static/css/console.css b/channel/web/static/css/console.css
index a618265d..2283f3e8 100644
--- a/channel/web/static/css/console.css
+++ b/channel/web/static/css/console.css
@@ -244,6 +244,52 @@
}
.dark .session-delete:hover { background: rgba(239, 68, 68, 0.15); }
+/* Rename button: shares the look of the delete button, sits to its left.
+ Negative right margin tightens the gap to the delete button only. */
+.session-rename {
+ flex-shrink: 0;
+ margin-right: -6px;
+ width: 22px;
+ height: 22px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ color: #9ca3af;
+ font-size: 11px;
+ opacity: 0;
+ transition: opacity 0.15s, color 0.15s, background 0.15s;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 0;
+}
+.session-item:hover .session-rename { opacity: 1; }
+.session-rename:hover {
+ color: #4ABE6E;
+ background: rgba(74, 190, 110, 0.12);
+}
+.dark .session-rename:hover { background: rgba(74, 190, 110, 0.18); }
+
+/* Inline title editor */
+.session-title-input {
+ flex: 1;
+ min-width: 0;
+ font-size: 13px;
+ font-family: inherit;
+ color: #111827;
+ background: #ffffff;
+ border: 1px solid #4ABE6E;
+ border-radius: 6px;
+ padding: 2px 6px;
+ outline: none;
+}
+.dark .session-title-input {
+ color: #e5e5e5;
+ background: rgba(255, 255, 255, 0.06);
+ border-color: #4ABE6E;
+}
+
/* Context Divider */
.context-divider {
display: flex;
diff --git a/channel/web/static/js/console.js b/channel/web/static/js/console.js
index f5283e96..b649951d 100644
--- a/channel/web/static/js/console.js
+++ b/channel/web/static/js/console.js
@@ -220,6 +220,7 @@ const I18N = {
today: '今天', yesterday: '昨天', earlier: '更早',
delete_session_confirm: '确认删除该会话?所有消息将被清除。',
delete_session_title: '删除会话',
+ rename_session: '重命名',
delete_message_confirm: '确认删除这条消息?',
delete_message_title: '删除消息',
edit_disabled_reply_active: '正在生成回复,暂时无法编辑。',
@@ -460,6 +461,7 @@ const I18N = {
today: 'Today', yesterday: 'Yesterday', earlier: 'Earlier',
delete_session_confirm: 'Delete this session? All messages will be removed.',
delete_session_title: 'Delete Session',
+ rename_session: 'Rename',
delete_message_confirm: 'Delete this message?',
delete_message_title: 'Delete Message',
edit_disabled_reply_active: 'Reply is being generated; editing is temporarily unavailable.',
@@ -3670,6 +3672,9 @@ function _addOptimisticSessionItem(sid) {
item.innerHTML = `
${escapeHtml(title)}
+
@@ -3757,6 +3762,9 @@ function _fetchSessionPage(page, clear, onDone) {
item.innerHTML = `
${escapeHtml(title)}
+
@@ -3875,6 +3883,84 @@ function switchSession(newSessionId) {
if (currentView !== 'chat') navigateTo('chat');
}
+// In-place rename a session title: replace the title with an ,
+// commit on Enter/blur, cancel on Escape. Persists via PUT /api/sessions/.
+function renameSession(sid) {
+ const item = document.querySelector(`.session-item[data-session-id="${sid}"]`);
+ if (!item) return;
+ const titleEl = item.querySelector('.session-title');
+ if (!titleEl || item.querySelector('.session-title-input')) return;
+
+ const oldTitle = titleEl.textContent;
+
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.className = 'session-title-input';
+ input.value = oldTitle;
+ input.maxLength = 100;
+
+ // Avoid switching session while interacting with the input
+ const stop = e => e.stopPropagation();
+ input.addEventListener('click', stop);
+ input.addEventListener('mousedown', stop);
+
+ titleEl.replaceWith(input);
+ input.focus();
+ input.select();
+
+ let done = false;
+
+ const restore = (title) => {
+ if (done) return;
+ done = true;
+ const span = document.createElement('span');
+ span.className = 'session-title';
+ span.title = title;
+ span.textContent = title;
+ input.replaceWith(span);
+ };
+
+ const commit = () => {
+ if (done) return;
+ const newTitle = input.value.trim();
+ if (!newTitle || newTitle === oldTitle) {
+ restore(oldTitle);
+ return;
+ }
+ // Optimistically show the new title, then persist.
+ restore(newTitle);
+ fetch(`/api/sessions/${encodeURIComponent(sid)}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ title: newTitle })
+ })
+ .then(r => r.json())
+ .then(data => {
+ if (data.status !== 'success') {
+ // Revert UI on failure
+ const span = item.querySelector('.session-title');
+ if (span) {
+ span.title = oldTitle;
+ span.textContent = oldTitle;
+ }
+ }
+ })
+ .catch(() => {
+ const span = item.querySelector('.session-title');
+ if (span) {
+ span.title = oldTitle;
+ span.textContent = oldTitle;
+ }
+ });
+ };
+
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') { e.preventDefault(); commit(); }
+ else if (e.key === 'Escape') { e.preventDefault(); restore(oldTitle); }
+ });
+ input.addEventListener('blur', commit);
+}
+
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