From 99aff83a232262f615eb9e1c454a8589c1bcea12 Mon Sep 17 00:00:00 2001 From: fretuks Date: Fri, 16 Oct 2026 12:22:14 +0200 Subject: [PATCH] Improve chat title generation and context compacting --- public/css/shell.css | 2 +- public/css/style.css | 6 +++--- public/js/composerController.js | 1 - src/config.js | 2 ++ src/services/chatService.js | 5 ++--- test/app.test.js | 3 +++ 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/public/css/shell.css b/public/css/shell.css index 76ebe60..b3476af 100644 --- a/public/css/shell.css +++ b/public/css/shell.css @@ -38,7 +38,7 @@ body.site-page { width:100%; max-width:none; margin:0; padding:0; background:var .chat-page .sidebar-workspace-card h2 { margin:0; font-size:.85rem; letter-spacing:0; font-family:var(--font-body); color:var(--text-soft); } .chat-page .chat-main { grid-template-rows:auto minmax(0,1fr) auto; } .chat-heading { min-width:0; } -.chat-heading #workspaceNotice { margin:0; padding:10px 24px; border-width:0 0 1px; border-radius:0; font-size:.85rem; } +.chat-heading #workspaceNotice { position:absolute; top:12px; right:16px; z-index:25; width:min(420px,calc(100% - 32px)); margin:0; padding:10px 14px; border-width:1px; border-radius:var(--radius-md); font-size:.85rem; box-shadow:var(--shadow-md); } .chat-page .chat-header { min-height:76px; } @media(max-width:1100px) { .shell-header { grid-template-columns:170px minmax(0,1fr) auto; gap:12px; padding:0 24px; }.shell-nav a { padding:0 10px; }.shell-username { max-width:90px; } } @media(max-width:980px) { diff --git a/public/css/style.css b/public/css/style.css index 29145a4..f38cdd2 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -426,8 +426,9 @@ input:focus, textarea:focus, select:focus { border-color: var(--brand); } .messages-wrap { min-height: 0; position: relative; display: grid; grid-template-rows: auto auto auto minmax(0, 1fr); } .messages-wrap > .messages-panel { grid-row: 4; } -.conversation-search-toggle { display: none; } -.conversation-search { padding: 8px max(16px, calc((100% - 940px) / 2)); display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--border); } +.conversation-search-toggle { display: block; justify-self: start; min-height: 32px; margin: 4px 10px; padding: 4px 10px; font-size: .75rem; } +.conversation-search { display: none; padding: 8px max(16px, calc((100% - 940px) / 2)); align-items: center; gap: 8px; border-bottom: 1px solid var(--border); } +.conversation-search.is-open { display: flex; } .conversation-search label { flex: 0 0 auto; color: var(--text-soft); font-size: .76rem; font-weight: 700; } .conversation-search input { min-width: 0; width: min(280px, 100%); min-height: 34px; } .conversation-search span { min-width: 64px; color: var(--text-muted); font-size: .72rem; white-space: nowrap; } @@ -700,7 +701,6 @@ input:focus, textarea:focus, select:focus { border-color: var(--brand); } .messages-empty-actions { display: grid; } .chat-drawer-action-list { grid-template-columns: 1fr; } .chat-drawer-action-list > * { grid-column: auto; } - .conversation-search-toggle { display: block; justify-self: start; min-height: 32px; margin: 4px 10px; padding: 4px 10px; font-size: .75rem; } .conversation-search:not(.is-open) { display: none; } .conversation-search { padding: 6px 10px; flex-wrap: wrap; gap: 5px; } .conversation-search label { width: 100%; } diff --git a/public/js/composerController.js b/public/js/composerController.js index 10f29e7..680e869 100644 --- a/public/js/composerController.js +++ b/public/js/composerController.js @@ -200,7 +200,6 @@ export function createComposerController({ } await loadSummary(); markOnboardingCompleted(); maybeShowPersonaNudge(); - setNotice("Reply received.", "success"); } catch (error) { loadingMsg?.remove(); const messageText = signal.aborted ? "Generation stopped." : error?.message || "Unable to send message."; diff --git a/src/config.js b/src/config.js index a1f4e26..047501b 100644 --- a/src/config.js +++ b/src/config.js @@ -71,7 +71,9 @@ export function createConfig(overrides = {}) { chatHistoryLimit: positiveInt(process.env.CHAT_HISTORY_LIMIT, 20), chatSummaryUpdateEveryMessages: positiveInt(process.env.CHAT_SUMMARY_UPDATE_EVERY_MESSAGES, 8), chatSummaryMaxChars: positiveInt(process.env.CHAT_SUMMARY_MAX_CHARS, 2200), + contextSummaryModel: process.env.CONTEXT_SUMMARY_MODEL || "nomic-embed-text:latest", chatMaxRetries: nonNegativeInt(process.env.CHAT_MAX_RETRIES, 5), + titleModel: process.env.TITLE_MODEL || "nomic-embed-text:latest", modelApiBaseUrl: process.env.MODEL_API_BASE_URL || "https://ai.krishd.ch/api", modelRequestTimeoutMs: positiveInt(process.env.MODEL_REQUEST_TIMEOUT_MS, 180000), modelConnectTimeoutMs: positiveInt(process.env.MODEL_CONNECT_TIMEOUT_MS, 30000), diff --git a/src/services/chatService.js b/src/services/chatService.js index 60bfcea..0fcdb15 100644 --- a/src/services/chatService.js +++ b/src/services/chatService.js @@ -199,7 +199,7 @@ export function createChatService(repositories, modelService, config, webSearchP if (summarizable.length < config.chatSummaryUpdateEveryMessages) return; const previousSummary = String(session.context_summary || "").trim(); - const summary = await modelService.generateReply(selectedModel, [ + const summary = await modelService.generateReply(config.contextSummaryModel || "nomic-embed-text:latest", [ { role: "system", content: [ @@ -687,8 +687,7 @@ export function createChatService(repositories, modelService, config, webSearchP if (session.assistant_persona_id || session.title !== "New chat" || repositories.getLatestChatMessage(chatId)) { return {title: session.title}; } - const {model: effectiveModel} = getGenerationSettings(session, model); - const title = await generateChatTitle(message, effectiveModel, signal); + const title = await generateChatTitle(message, config.titleModel || "nomic-embed-text:latest", signal); if (title !== "New chat") repositories.updateChatTitle(chatId, user, title); return {title}; }, diff --git a/test/app.test.js b/test/app.test.js index e95c38d..e2a0c41 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -752,9 +752,11 @@ test("chat memories are durable facts included in model payload", async () => { test("chat context summary rolls up older messages into future payloads", async () => { let replyCount = 0; let latestPayload = []; + let summaryModel = null; const modelService = { async generateReply(model, messagesPayload) { if (messagesPayload.some((message) => message.content.includes("Merge the previous summary"))) { + summaryModel = model; return "Summary: user greeted the assistant and asked for continuity."; } latestPayload = messagesPayload; @@ -810,6 +812,7 @@ test("chat context summary rolls up older messages into future payloads", async assert.ok(latestPayload.some((message) => message.role === "system" && message.content.includes("CONVERSATION SUMMARY"))); assert.ok(latestPayload.some((message) => message.content.includes("user greeted the assistant"))); + assert.equal(summaryModel, "nomic-embed-text:latest"); await server.close(); });