fix: rename sessions from the chat rail (fixes #331)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MervinPraisonandClaude Opus 5.5 committed 2026-11-05 10:41:19 +00:00
1 parent 085bdd3793
commit e69a06c2d5
3 files changed
+89 -3

No files matched your search

+4 -2
View File
@@ -1303,9 +1303,11 @@ async def patch_session(request: Request) -> JSONResponse:
body = await request.json()
except Exception:
return JSONResponse({"error": "Invalid JSON"}, status_code=400)
title = body.get("title")
title = body.get("title") if isinstance(body, dict) else None
if title is not None:
await _datastore.update_session(session_id, title=title)
if not isinstance(title, str) or not title.strip():
return JSONResponse({"error": "title must be a non-empty string"}, status_code=400)
await _datastore.update_session(session_id, title=title.strip()[:200])
return JSONResponse({"status": "updated", "session_id": session_id})
@@ -244,6 +244,9 @@ function injectStyles() {
.chat-sess-item.active { background:rgba(var(--db-accent-rgb,100,100,255),.15); }
.chat-sess-item .sess-title { font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.chat-sess-item .sess-meta { font-size:11px; color:var(--db-text-dim); }
.chat-sess-item { position:relative; }
.chat-sess-item .sess-rename { position:absolute; top:6px; right:6px; display:none; background:none; border:none; color:var(--db-text-dim); cursor:pointer; font-size:12px; padding:2px 4px; }
.chat-sess-item:hover .sess-rename, .chat-sess-item .sess-rename:focus { display:block; }
.chat-main-panel { flex:1; display:flex; flex-direction:column; min-width:0; }
.chat-top-bar { padding:10px 16px; border-bottom:1px solid var(--db-border); display:flex; align-items:center; justify-content:space-between; gap:10px; }
@@ -1103,10 +1106,41 @@ function renderSessionList() {
`;
el.title = id;
el.addEventListener('click', () => loadSession(id));
if (!platformName && id) {
const btn = document.createElement('button');
btn.className = 'sess-rename';
btn.title = 'Rename session';
btn.setAttribute('aria-label', 'Rename session');
btn.textContent = '✎';
btn.addEventListener('click', (e) => { e.stopPropagation(); renameSession(id, title); });
el.appendChild(btn);
}
list.appendChild(el);
});
}
async function renameSession(sessionId, currentTitle) {
const next = (window.prompt('Rename session', currentTitle || '') || '').trim();
if (!next || next === currentTitle) return;
try {
const resp = await fetch('/sessions/' + encodeURIComponent(sessionId), {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: next }),
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
const s = allSessions.find((x) => (x.id || x.session_id) === sessionId);
if (s) { s.title = next; delete s.name; }
renderSessionList();
if (sessionId === currentSessionId) {
const header = document.getElementById('chat-header-title');
if (header) header.textContent = next;
}
} catch (e) {
console.warn('[Chat] Failed to rename session:', e);
}
}
async function loadSession(sessionId) {
currentSessionId = sessionId;
refreshStreamingUI();
@@ -1158,7 +1192,8 @@ async function loadSession(sessionId) {
title.textContent = (sessionIcon || '📨') + ' ' + displayName;
}
} else if (title) {
title.textContent = 'Session ' + sessionId.substring(0, 8);
const cached = allSessions.find((x) => (x.id || x.session_id) === sessionId);
title.textContent = (cached && (cached.name || cached.title)) || ('Session ' + sessionId.substring(0, 8));
}
} catch (e) {
console.warn('[Chat] Failed to load session:', e);
+49
View File
@@ -0,0 +1,49 @@
"""Issue #331 — rename a session from the chat rail via the existing PATCH route."""
from __future__ import annotations
import asyncio
from pathlib import Path
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.testclient import TestClient
import praisonaiui.server as server
from praisonaiui.datastore import MemoryDataStore
CHAT = Path("src/praisonaiui/templates/frontend/plugins/views/chat.js")
def _client(monkeypatch):
ds = MemoryDataStore()
monkeypatch.setattr(server, "_datastore", ds)
app = Starlette(routes=[Route("/sessions/{session_id}", server.patch_session, methods=["PATCH"])])
sid = asyncio.run(ds.create_session())["id"]
return TestClient(app), ds, sid
def test_patch_renames_and_trims(monkeypatch):
client, ds, sid = _client(monkeypatch)
r = client.patch(f"/sessions/{sid}", json={"title": " My chat "})
assert r.status_code == 200
assert asyncio.run(ds.get_session(sid))["title"] == "My chat"
def test_patch_rejects_blank_or_non_string_title(monkeypatch):
client, _, sid = _client(monkeypatch)
assert client.patch(f"/sessions/{sid}", json={"title": " "}).status_code == 400
assert client.patch(f"/sessions/{sid}", json={"title": 123}).status_code == 400
def test_patch_unknown_session_404(monkeypatch):
client, _, _ = _client(monkeypatch)
assert client.patch("/sessions/nope", json={"title": "x"}).status_code == 404
def test_rail_has_rename_button_wired_to_patch():
src = CHAT.read_text()
assert "async function renameSession(sessionId, currentTitle)" in src
assert "method: 'PATCH'" in src
assert "renameSession(id, title)" in src
assert "e.stopPropagation()" in src