From 50827fd8cf00482091e0817f52b69a5845082c96 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Fri, 17 Apr 2026 10:33:24 +0200 Subject: [PATCH] Refactor chat title sanitization logic in backend and frontend --- backend/main.py | 29 ++++++++++++++++++++++++----- src/App.jsx | 33 ++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/backend/main.py b/backend/main.py index 289de40..f72aa85 100644 --- a/backend/main.py +++ b/backend/main.py @@ -29,10 +29,19 @@ ensure_sources_column(engine) app = FastAPI(title="LLM Desktop Backend", version="0.1.0" ) -def sanitize_generated_chat_title(title: str) -> str: +def sanitize_chat_title(title: str) -> str: cleaned_title = html.unescape(title or "") cleaned_title = re.sub(r'.*?', '', cleaned_title, flags=re.DOTALL | re.IGNORECASE) - cleaned_title = re.sub(r'[*#]', '', cleaned_title) + cleaned_title = cleaned_title.strip() + + previous_title = None + while cleaned_title and cleaned_title != previous_title: + previous_title = cleaned_title + cleaned_title = re.sub(r'^\s*#+\s*', '', cleaned_title) + cleaned_title = re.sub(r'^\s*\*{1,2}\s*', '', cleaned_title) + cleaned_title = re.sub(r'\s*\*{1,2}\s*$', '', cleaned_title) + cleaned_title = cleaned_title.strip() + cleaned_title = re.sub(r'\s+', ' ', cleaned_title) return cleaned_title.strip() @@ -248,7 +257,17 @@ async def startup_prepare_models_route(): @app.get("/sessions", response_model=schemas.SessionsResponse) def get_sessions(db: Session = Depends(get_db)): sessions = db.query(models.ChatSession).order_by(models.ChatSession.created_at.desc()).all() - return {"sessions": sessions} + return { + "sessions": [ + { + "id": session.id, + "session_id": session.session_id, + "name": sanitize_chat_title(session.name), + "created_at": session.created_at, + } + for session in sessions + ] + } @app.post("/sessions", response_model=schemas.ChatSession) def create_session(req: schemas.CreateSessionRequest, db: Session = Depends(get_db)): @@ -361,7 +380,7 @@ async def generate_title(req: schemas.GenerateTitleRequest, db: Session = Depend print(f"Original title from LLM: {title}") # Debugging line to see the raw title - cleaned_title = sanitize_generated_chat_title(title) + cleaned_title = sanitize_chat_title(title) print(f"Cleaned title before saving: {cleaned_title}") # Debugging line to see the cleaned title @@ -389,7 +408,7 @@ def rename_session(session_id: str, req: schemas.GenerateTitleResponse, db: Sess if not session: raise HTTPException(status_code=404, detail="Session not found") - session.name = req.title + session.name = sanitize_chat_title(req.title) db.commit() return {"ok": True} diff --git a/src/App.jsx b/src/App.jsx index 1c27d90..78592c5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,12 +19,22 @@ import { supportsAudioInputCapture, } from './audioInput' -function sanitizeGeneratedChatTitle(title) { - return (title || '') +function sanitizeChatTitle(title) { + let cleanedTitle = String(title || '') .replace(/[\s\S]*?<\/think(?:ing)?>/gi, '') - .replace(/[*#]/g, '') - .replace(/\s+/g, ' ') .trim() + + let previousTitle = null + while (cleanedTitle && cleanedTitle !== previousTitle) { + previousTitle = cleanedTitle + cleanedTitle = cleanedTitle + .replace(/^\s*#+\s*/, '') + .replace(/^\s*\*{1,2}\s*/, '') + .replace(/\s*\*{1,2}\s*$/, '') + .trim() + } + + return cleanedTitle.replace(/\s+/g, ' ').trim() } function appendOllamaErrorHint(text, marker, block) { @@ -1569,7 +1579,11 @@ async function regenerateFromIndex(index, overrideUserText = null) { fetch(`${backendApiUrl}/sessions`) .then(r => r.json()) .then(data => { - const sessionsWithMessages = data.sessions.map(s => ({ ...s, messages: [] })); + const sessionsWithMessages = data.sessions.map(s => ({ + ...s, + name: sanitizeChatTitle(s.name), + messages: [], + })); setChatSessions(sessionsWithMessages); if (sessionsWithMessages.length > 0) { setActiveSessionId(sessionsWithMessages[0].session_id); @@ -2190,7 +2204,7 @@ async function sendMessage() { }) .then(r => r.json()) .then(data => { - const sanitizedTitle = sanitizeGeneratedChatTitle(data.title) + const sanitizedTitle = sanitizeChatTitle(data.title) setChatSessions(prevSessions => prevSessions.map(session => session.session_id === targetSessionId ? { ...session, name: sanitizedTitle } : session @@ -2232,7 +2246,7 @@ async function createNewChat() { body: JSON.stringify({ session_id: newSessionId }) }); const newSession = await res.json(); - const sessionWithMessages = { ...newSession, messages: [] }; + const sessionWithMessages = { ...newSession, name: sanitizeChatTitle(newSession.name), messages: [] }; setChatSessions(prevSessions => [sessionWithMessages, ...prevSessions]); setActiveSessionId(newSession.session_id); textareaRef.current?.focus(); @@ -2281,15 +2295,16 @@ async function createNewChat() { } function handleRename(sessionId, newName) { + const sanitizedName = sanitizeChatTitle(newName) fetch(`${backendApiUrl}/sessions/${sessionId}/rename`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title: newName }) + body: JSON.stringify({ title: sanitizedName }) }) .then(() => { setChatSessions(prevSessions => prevSessions.map(session => - session.session_id === sessionId ? { ...session, name: newName } : session + session.session_id === sessionId ? { ...session, name: sanitizedName } : session ) ); setEditingSessionId(null);