Refactor chat title sanitization logic in backend and frontend

This commit is contained in:
2026-04-17 10:33:24 +02:00
parent 3f3377118c
commit 50827fd8cf
2 changed files with 48 additions and 14 deletions

View File

@@ -29,10 +29,19 @@ ensure_sources_column(engine)
app = FastAPI(title="LLM Desktop Backend", version="0.1.0" ) 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 = html.unescape(title or "")
cleaned_title = re.sub(r'<think(?:ing)?>.*?</think(?:ing)?>', '', cleaned_title, flags=re.DOTALL | re.IGNORECASE) cleaned_title = re.sub(r'<think(?:ing)?>.*?</think(?:ing)?>', '', 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) cleaned_title = re.sub(r'\s+', ' ', cleaned_title)
return cleaned_title.strip() return cleaned_title.strip()
@@ -248,7 +257,17 @@ async def startup_prepare_models_route():
@app.get("/sessions", response_model=schemas.SessionsResponse) @app.get("/sessions", response_model=schemas.SessionsResponse)
def get_sessions(db: Session = Depends(get_db)): def get_sessions(db: Session = Depends(get_db)):
sessions = db.query(models.ChatSession).order_by(models.ChatSession.created_at.desc()).all() 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) @app.post("/sessions", response_model=schemas.ChatSession)
def create_session(req: schemas.CreateSessionRequest, db: Session = Depends(get_db)): 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 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 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: if not session:
raise HTTPException(status_code=404, detail="Session not found") raise HTTPException(status_code=404, detail="Session not found")
session.name = req.title session.name = sanitize_chat_title(req.title)
db.commit() db.commit()
return {"ok": True} return {"ok": True}

View File

@@ -19,12 +19,22 @@ import {
supportsAudioInputCapture, supportsAudioInputCapture,
} from './audioInput' } from './audioInput'
function sanitizeGeneratedChatTitle(title) { function sanitizeChatTitle(title) {
return (title || '') let cleanedTitle = String(title || '')
.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, '') .replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, '')
.replace(/[*#]/g, '')
.replace(/\s+/g, ' ')
.trim() .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) { function appendOllamaErrorHint(text, marker, block) {
@@ -1569,7 +1579,11 @@ async function regenerateFromIndex(index, overrideUserText = null) {
fetch(`${backendApiUrl}/sessions`) fetch(`${backendApiUrl}/sessions`)
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
const sessionsWithMessages = data.sessions.map(s => ({ ...s, messages: [] })); const sessionsWithMessages = data.sessions.map(s => ({
...s,
name: sanitizeChatTitle(s.name),
messages: [],
}));
setChatSessions(sessionsWithMessages); setChatSessions(sessionsWithMessages);
if (sessionsWithMessages.length > 0) { if (sessionsWithMessages.length > 0) {
setActiveSessionId(sessionsWithMessages[0].session_id); setActiveSessionId(sessionsWithMessages[0].session_id);
@@ -2190,7 +2204,7 @@ async function sendMessage() {
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
const sanitizedTitle = sanitizeGeneratedChatTitle(data.title) const sanitizedTitle = sanitizeChatTitle(data.title)
setChatSessions(prevSessions => setChatSessions(prevSessions =>
prevSessions.map(session => prevSessions.map(session =>
session.session_id === targetSessionId ? { ...session, name: sanitizedTitle } : session session.session_id === targetSessionId ? { ...session, name: sanitizedTitle } : session
@@ -2232,7 +2246,7 @@ async function createNewChat() {
body: JSON.stringify({ session_id: newSessionId }) body: JSON.stringify({ session_id: newSessionId })
}); });
const newSession = await res.json(); const newSession = await res.json();
const sessionWithMessages = { ...newSession, messages: [] }; const sessionWithMessages = { ...newSession, name: sanitizeChatTitle(newSession.name), messages: [] };
setChatSessions(prevSessions => [sessionWithMessages, ...prevSessions]); setChatSessions(prevSessions => [sessionWithMessages, ...prevSessions]);
setActiveSessionId(newSession.session_id); setActiveSessionId(newSession.session_id);
textareaRef.current?.focus(); textareaRef.current?.focus();
@@ -2281,15 +2295,16 @@ async function createNewChat() {
} }
function handleRename(sessionId, newName) { function handleRename(sessionId, newName) {
const sanitizedName = sanitizeChatTitle(newName)
fetch(`${backendApiUrl}/sessions/${sessionId}/rename`, { fetch(`${backendApiUrl}/sessions/${sessionId}/rename`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: newName }) body: JSON.stringify({ title: sanitizedName })
}) })
.then(() => { .then(() => {
setChatSessions(prevSessions => setChatSessions(prevSessions =>
prevSessions.map(session => prevSessions.map(session =>
session.session_id === sessionId ? { ...session, name: newName } : session session.session_id === sessionId ? { ...session, name: sanitizedName } : session
) )
); );
setEditingSessionId(null); setEditingSessionId(null);