mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
feat(web): make WebChat commentary persistence configurable
Add a per-viewer 'Keep commentary' toggle (UiSettings.chatPersistCommentary, default true) that controls whether keyed Codex preamble/commentary blocks stay after the final answer or clear with it. - Persist (default): keyed commentary materializes as durable blocks, current behavior, existing proof unchanged. - Transient (toggle off): commentary stays live during streaming but is never materialized, so it disappears as the final message arrives. This is the transient-only behavior from #92236, now user-selectable instead of a maintainer-level either/or policy choice. Single gating point in materializeVisibleStreamState (skip itemId-keyed parts when persistCommentary is false); threaded from settings through the chat event handler. Adds desktop + mobile header toggles and an en.ts label (locale bundles regenerated via ui:i18n:sync, English fallback). Tests: reconciliation persist/transient coverage, final-event handler honors the setting, settings round-trip + header button assertions updated.
This commit is contained in:
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:07:21.796Z",
|
||||
"generatedAt": "2026-06-29T17:06:18.945Z",
|
||||
"locale": "ar",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:06:02.374Z",
|
||||
"generatedAt": "2026-06-29T17:06:17.867Z",
|
||||
"locale": "de",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:06:31.127Z",
|
||||
"generatedAt": "2026-06-29T17:06:18.056Z",
|
||||
"locale": "es",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:50.278Z",
|
||||
"generatedAt": "2026-06-29T17:06:20.668Z",
|
||||
"locale": "fa",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:06:59.616Z",
|
||||
"generatedAt": "2026-06-29T17:06:18.559Z",
|
||||
"locale": "fr",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+7
-4
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T21:38:17.797Z",
|
||||
"fallbackKeys": [
|
||||
"chat.commentaryLabel",
|
||||
"chat.commentaryToggle"
|
||||
],
|
||||
"generatedAt": "2026-06-29T17:06:18.728Z",
|
||||
"locale": "hi",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1416,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:00.729Z",
|
||||
"generatedAt": "2026-06-29T17:06:19.737Z",
|
||||
"locale": "id",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:07:12.745Z",
|
||||
"generatedAt": "2026-06-29T17:06:19.156Z",
|
||||
"locale": "it",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:06:34.355Z",
|
||||
"generatedAt": "2026-06-29T17:06:18.241Z",
|
||||
"locale": "ja-JP",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:06:42.132Z",
|
||||
"generatedAt": "2026-06-29T17:06:18.409Z",
|
||||
"locale": "ko",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:47.496Z",
|
||||
"generatedAt": "2026-06-29T17:06:20.490Z",
|
||||
"locale": "nl",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:08.174Z",
|
||||
"generatedAt": "2026-06-29T17:06:19.915Z",
|
||||
"locale": "pl",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:05:51.685Z",
|
||||
"generatedAt": "2026-06-29T17:06:17.682Z",
|
||||
"locale": "pt-BR",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+14
@@ -1513,6 +1513,13 @@
|
||||
"path": "ui/src/ui/views/channels.whatsapp.ts",
|
||||
"text": "WhatsApp"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
"name": "aria-label",
|
||||
"path": "ui/src/ui/views/chat.ts",
|
||||
"text": "Cancel reply"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
@@ -1597,6 +1604,13 @@
|
||||
"path": "ui/src/ui/views/chat.ts",
|
||||
"text": "Search messages..."
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
"name": "title",
|
||||
"path": "ui/src/ui/views/chat.ts",
|
||||
"text": "Cancel reply"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"kind": "html-attribute",
|
||||
|
||||
Generated
+7
-4
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T21:42:51.346Z",
|
||||
"fallbackKeys": [
|
||||
"chat.commentaryLabel",
|
||||
"chat.commentaryToggle"
|
||||
],
|
||||
"generatedAt": "2026-06-29T17:06:20.849Z",
|
||||
"locale": "ru",
|
||||
"model": "gpt-5.5",
|
||||
"provider": "openai",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1416,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:16.442Z",
|
||||
"generatedAt": "2026-06-29T17:06:20.099Z",
|
||||
"locale": "th",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:07:26.912Z",
|
||||
"generatedAt": "2026-06-29T17:06:19.344Z",
|
||||
"locale": "tr",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:07:50.082Z",
|
||||
"generatedAt": "2026-06-29T17:06:19.546Z",
|
||||
"locale": "uk",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:08:44.881Z",
|
||||
"generatedAt": "2026-06-29T17:06:20.308Z",
|
||||
"locale": "vi",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:05:51.877Z",
|
||||
"generatedAt": "2026-06-29T17:06:17.341Z",
|
||||
"locale": "zh-CN",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"fallbackKeys": [],
|
||||
"generatedAt": "2026-06-26T22:05:54.449Z",
|
||||
"generatedAt": "2026-06-29T17:06:17.519Z",
|
||||
"locale": "zh-TW",
|
||||
"model": "claude-opus-4-8",
|
||||
"provider": "anthropic",
|
||||
"sourceHash": "08e6ec5c2e8416d0049d2452906c20d096e17e384299af89cde872f0cfbb5015",
|
||||
"totalKeys": 1416,
|
||||
"translatedKeys": 1416,
|
||||
"sourceHash": "29f546ff372524aa64d09bd700f76899045da99552fa295a32eda9fad2a0407f",
|
||||
"totalKeys": 1418,
|
||||
"translatedKeys": 1418,
|
||||
"workflow": 1
|
||||
}
|
||||
|
||||
Generated
+2
@@ -1313,6 +1313,8 @@ export const ar: TranslationMap = {
|
||||
settings: "إعدادات الدردشة",
|
||||
thinkingToggle: "تبديل مخرجات تفكير/عمل المساعد",
|
||||
toolCallsToggle: "تبديل استدعاءات الأدوات ونتائج الأدوات",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "وضع التمرير التلقائي",
|
||||
autoScrollAlways: "دائمًا",
|
||||
autoScrollNearBottom: "قرب الأسفل",
|
||||
|
||||
Generated
+2
@@ -1340,6 +1340,8 @@ export const de: TranslationMap = {
|
||||
settings: "Chat-Einstellungen",
|
||||
thinkingToggle: "Ausgabe des Assistenten ein-/ausblenden",
|
||||
toolCallsToggle: "Tool-Aufrufe und Tool-Ergebnisse umschalten",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Automatischer Scrollmodus",
|
||||
autoScrollAlways: "Immer",
|
||||
autoScrollNearBottom: "Nahe am unteren Rand",
|
||||
|
||||
@@ -1319,6 +1319,8 @@ export const en: TranslationMap = {
|
||||
settings: "Chat settings",
|
||||
thinkingToggle: "Toggle assistant thinking/working output",
|
||||
toolCallsToggle: "Toggle tool calls and tool results",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Auto-scroll mode",
|
||||
autoScrollAlways: "Always",
|
||||
autoScrollNearBottom: "Near bottom",
|
||||
|
||||
Generated
+2
@@ -1337,6 +1337,8 @@ export const es: TranslationMap = {
|
||||
settings: "Configuración del chat",
|
||||
thinkingToggle: "Alternar salida de pensamiento/trabajo del asistente",
|
||||
toolCallsToggle: "Alternar llamadas a herramientas y resultados de herramientas",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Modo de desplazamiento automático",
|
||||
autoScrollAlways: "Siempre",
|
||||
autoScrollNearBottom: "Cerca del final",
|
||||
|
||||
Generated
+2
@@ -1331,6 +1331,8 @@ export const fa: TranslationMap = {
|
||||
settings: "تنظیمات چت",
|
||||
thinkingToggle: "تغییر وضعیت خروجی فکر/کار دستیار",
|
||||
toolCallsToggle: "تغییر وضعیت نمایش فراخوانیهای ابزار و نتایج ابزار",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "حالت پیمایش خودکار",
|
||||
autoScrollAlways: "همیشه",
|
||||
autoScrollNearBottom: "نزدیک پایین",
|
||||
|
||||
Generated
+2
@@ -1344,6 +1344,8 @@ export const fr: TranslationMap = {
|
||||
settings: "Paramètres de chat",
|
||||
thinkingToggle: "Afficher/masquer la sortie de réflexion/travail de l’assistant",
|
||||
toolCallsToggle: "Afficher/masquer les appels d’outil et les résultats d’outil",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Mode de défilement automatique",
|
||||
autoScrollAlways: "Toujours",
|
||||
autoScrollNearBottom: "Près du bas",
|
||||
|
||||
Generated
+2
@@ -1315,6 +1315,8 @@ export const hi: TranslationMap = {
|
||||
settings: "चैट सेटिंग्स",
|
||||
thinkingToggle: "सहायक की सोच/कार्य आउटपुट टॉगल करें",
|
||||
toolCallsToggle: "टूल कॉल और टूल परिणाम टॉगल करें",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "ऑटो-स्क्रॉल मोड",
|
||||
autoScrollAlways: "हमेशा",
|
||||
autoScrollNearBottom: "नीचे के पास",
|
||||
|
||||
Generated
+2
@@ -1329,6 +1329,8 @@ export const id: TranslationMap = {
|
||||
settings: "Pengaturan chat",
|
||||
thinkingToggle: "Alihkan output berpikir/bekerja asisten",
|
||||
toolCallsToggle: "Alihkan panggilan alat dan hasil alat",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Mode gulir otomatis",
|
||||
autoScrollAlways: "Selalu",
|
||||
autoScrollNearBottom: "Dekat bagian bawah",
|
||||
|
||||
Generated
+2
@@ -1336,6 +1336,8 @@ export const it: TranslationMap = {
|
||||
settings: "Impostazioni chat",
|
||||
thinkingToggle: "Attiva/disattiva output di pensiero/elaborazione dell'assistente",
|
||||
toolCallsToggle: "Attiva/disattiva chiamate agli strumenti e risultati strumenti",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Modalità di scorrimento automatico",
|
||||
autoScrollAlways: "Sempre",
|
||||
autoScrollNearBottom: "Vicino al fondo",
|
||||
|
||||
Generated
+2
@@ -1334,6 +1334,8 @@ export const ja_JP: TranslationMap = {
|
||||
settings: "チャット設定",
|
||||
thinkingToggle: "アシスタントの思考 / 作業出力の表示を切り替え",
|
||||
toolCallsToggle: "ツール呼び出しとツール結果の表示を切り替え",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "自動スクロールモード",
|
||||
autoScrollAlways: "常に",
|
||||
autoScrollNearBottom: "下部付近",
|
||||
|
||||
Generated
+2
@@ -1320,6 +1320,8 @@ export const ko: TranslationMap = {
|
||||
settings: "채팅 설정",
|
||||
thinkingToggle: "어시스턴트 생각/작업 출력 전환",
|
||||
toolCallsToggle: "도구 호출 및 도구 결과 전환",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "자동 스크롤 모드",
|
||||
autoScrollAlways: "항상",
|
||||
autoScrollNearBottom: "하단 근처",
|
||||
|
||||
Generated
+2
@@ -1335,6 +1335,8 @@ export const nl: TranslationMap = {
|
||||
settings: "Chatinstellingen",
|
||||
thinkingToggle: "Denken-/werken-output van assistent schakelen",
|
||||
toolCallsToggle: "Tool-aanroepen en toolresultaten schakelen",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Automatisch scrollen",
|
||||
autoScrollAlways: "Altijd",
|
||||
autoScrollNearBottom: "Bijna onderaan",
|
||||
|
||||
Generated
+2
@@ -1334,6 +1334,8 @@ export const pl: TranslationMap = {
|
||||
settings: "Ustawienia czatu",
|
||||
thinkingToggle: "Przełącz wyświetlanie myślenia/pracy asystenta",
|
||||
toolCallsToggle: "Przełącz wyświetlanie wywołań narzędzi i wyników narzędzi",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Tryb automatycznego przewijania",
|
||||
autoScrollAlways: "Zawsze",
|
||||
autoScrollNearBottom: "Blisko dołu",
|
||||
|
||||
Generated
+2
@@ -1331,6 +1331,8 @@ export const pt_BR: TranslationMap = {
|
||||
settings: "Configurações do chat",
|
||||
thinkingToggle: "Alternar saída de pensamento/trabalho do assistente",
|
||||
toolCallsToggle: "Alternar chamadas de ferramenta e resultados de ferramenta",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Modo de rolagem automática",
|
||||
autoScrollAlways: "Sempre",
|
||||
autoScrollNearBottom: "Próximo ao final",
|
||||
|
||||
Generated
+2
@@ -1339,6 +1339,8 @@ export const ru: TranslationMap = {
|
||||
settings: "Настройки чата",
|
||||
thinkingToggle: "Показать или скрыть вывод размышлений/работы ассистента",
|
||||
toolCallsToggle: "Показать или скрыть вызовы инструментов и их результаты",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Режим автопрокрутки",
|
||||
autoScrollAlways: "Всегда",
|
||||
autoScrollNearBottom: "Рядом с нижним краем",
|
||||
|
||||
Generated
+2
@@ -1297,6 +1297,8 @@ export const th: TranslationMap = {
|
||||
settings: "การตั้งค่าแชท",
|
||||
thinkingToggle: "สลับการแสดงผลการคิด/การทำงานของผู้ช่วย",
|
||||
toolCallsToggle: "สลับการแสดงการเรียกใช้ tool และผลลัพธ์ของ tool",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "โหมดเลื่อนอัตโนมัติ",
|
||||
autoScrollAlways: "เสมอ",
|
||||
autoScrollNearBottom: "ใกล้ด้านล่าง",
|
||||
|
||||
Generated
+2
@@ -1335,6 +1335,8 @@ export const tr: TranslationMap = {
|
||||
settings: "Sohbet ayarları",
|
||||
thinkingToggle: "Asistanın düşünme/çalışma çıktısını aç/kapat",
|
||||
toolCallsToggle: "Araç çağrılarını ve araç sonuçlarını aç/kapat",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Otomatik kaydırma modu",
|
||||
autoScrollAlways: "Her zaman",
|
||||
autoScrollNearBottom: "Alta yakın",
|
||||
|
||||
Generated
+2
@@ -1333,6 +1333,8 @@ export const uk: TranslationMap = {
|
||||
settings: "Налаштування чату",
|
||||
thinkingToggle: "Перемкнути показ мислення/роботи асистента",
|
||||
toolCallsToggle: "Перемкнути виклики інструментів і результати інструментів",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Режим автопрокручування",
|
||||
autoScrollAlways: "Завжди",
|
||||
autoScrollNearBottom: "Біля низу",
|
||||
|
||||
Generated
+2
@@ -1321,6 +1321,8 @@ export const vi: TranslationMap = {
|
||||
settings: "Cài đặt trò chuyện",
|
||||
thinkingToggle: "Bật/tắt đầu ra suy nghĩ/đang xử lý của trợ lý",
|
||||
toolCallsToggle: "Bật/tắt lượt gọi công cụ và kết quả công cụ",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "Chế độ tự động cuộn",
|
||||
autoScrollAlways: "Luôn luôn",
|
||||
autoScrollNearBottom: "Gần cuối",
|
||||
|
||||
Generated
+2
@@ -1292,6 +1292,8 @@ export const zh_CN: TranslationMap = {
|
||||
settings: "聊天设置",
|
||||
thinkingToggle: "切换助手思考/工作输出",
|
||||
toolCallsToggle: "切换工具调用和工具结果",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "自动滚动模式",
|
||||
autoScrollAlways: "始终",
|
||||
autoScrollNearBottom: "接近底部",
|
||||
|
||||
Generated
+2
@@ -1294,6 +1294,8 @@ export const zh_TW: TranslationMap = {
|
||||
settings: "聊天設定",
|
||||
thinkingToggle: "切換助手思考/工作輸出",
|
||||
toolCallsToggle: "切換工具呼叫與工具結果",
|
||||
commentaryToggle: "Keep commentary after the final answer (off: clears with the final message)",
|
||||
commentaryLabel: "Commentary",
|
||||
autoScrollMode: "自動捲動模式",
|
||||
autoScrollAlways: "一律",
|
||||
autoScrollNearBottom: "接近底部",
|
||||
|
||||
@@ -119,7 +119,7 @@ describe("chat header controls (browser)", () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(buttons).toHaveLength(5);
|
||||
expect(buttons).toHaveLength(6);
|
||||
|
||||
const labels = buttons.map((button) => button.getAttribute("data-tooltip"));
|
||||
expect(labels).toEqual([
|
||||
@@ -127,6 +127,7 @@ describe("chat header controls (browser)", () => {
|
||||
`${t("chat.autoScrollMode")}: ${t("chat.autoScrollNearBottom")}`,
|
||||
t("chat.thinkingToggle"),
|
||||
t("chat.toolCallsToggle"),
|
||||
t("chat.commentaryToggle"),
|
||||
t("chat.showCronSessions"),
|
||||
]);
|
||||
|
||||
@@ -231,7 +232,7 @@ describe("chat header controls (browser)", () => {
|
||||
container.querySelectorAll<HTMLButtonElement>(".chat-controls__thinking .btn--icon"),
|
||||
);
|
||||
|
||||
expect(buttons).toHaveLength(4);
|
||||
expect(buttons).toHaveLength(5);
|
||||
const autoScrollButton = requireButton(buttons.at(0), "auto-scroll mode");
|
||||
expect(autoScrollButton.dataset.chatAutoScrollMode).toBe("near-bottom");
|
||||
const cronButton = requireButton(buttons.at(-1), "cron sessions");
|
||||
|
||||
@@ -365,12 +365,16 @@ export function renderChatControls(state: AppViewState) {
|
||||
const disableThinkingToggle = state.onboarding;
|
||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||
const persistCommentary = state.settings.chatPersistCommentary !== false;
|
||||
const thinkingLabel = disableThinkingToggle
|
||||
? t("chat.onboardingDisabled")
|
||||
: t("chat.thinkingToggle");
|
||||
const toolCallsLabel = disableThinkingToggle
|
||||
? t("chat.onboardingDisabled")
|
||||
: t("chat.toolCallsToggle");
|
||||
const commentaryLabel = disableThinkingToggle
|
||||
? t("chat.onboardingDisabled")
|
||||
: t("chat.commentaryToggle");
|
||||
const refreshDisabled =
|
||||
!state.connected ||
|
||||
state.chatManualRefreshInFlight ||
|
||||
@@ -503,6 +507,28 @@ export function renderChatControls(state: AppViewState) {
|
||||
${toolCallsIcon}
|
||||
<span class="chat-settings-action__text">${t("agents.tabs.tools")}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm btn--icon chat-settings-action ${persistCommentary
|
||||
? "active"
|
||||
: ""}"
|
||||
?disabled=${disableThinkingToggle}
|
||||
@click=${() => {
|
||||
if (disableThinkingToggle) {
|
||||
return;
|
||||
}
|
||||
state.applySettings({
|
||||
...state.settings,
|
||||
chatPersistCommentary: !persistCommentary,
|
||||
});
|
||||
}}
|
||||
aria-pressed=${persistCommentary}
|
||||
title=${commentaryLabel}
|
||||
aria-label=${commentaryLabel}
|
||||
data-tooltip=${commentaryLabel}
|
||||
>
|
||||
${persistCommentary ? icons.pin : icons.pinOff}
|
||||
<span class="chat-settings-action__text">${t("chat.commentaryLabel")}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm btn--icon chat-settings-action ${hideCron ? "active" : ""}"
|
||||
@click=${() => {
|
||||
@@ -534,6 +560,7 @@ export function renderChatMobileToggle(state: AppViewState) {
|
||||
const disableThinkingToggle = state.onboarding;
|
||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||
const persistCommentary = state.settings.chatPersistCommentary !== false;
|
||||
const hideCron = state.sessionsHideCron ?? true;
|
||||
const hiddenCronCount = hideCron ? countHiddenCronSessions(state, state.sessionsResult) : 0;
|
||||
const toolCallsIcon = html`
|
||||
@@ -627,6 +654,22 @@ export function renderChatMobileToggle(state: AppViewState) {
|
||||
>
|
||||
${toolCallsIcon}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm btn--icon ${persistCommentary ? "active" : ""}"
|
||||
?disabled=${disableThinkingToggle}
|
||||
@click=${() => {
|
||||
if (!disableThinkingToggle) {
|
||||
state.applySettings({
|
||||
...state.settings,
|
||||
chatPersistCommentary: !persistCommentary,
|
||||
});
|
||||
}
|
||||
}}
|
||||
aria-pressed=${persistCommentary}
|
||||
title=${t("chat.commentaryToggle")}
|
||||
>
|
||||
${persistCommentary ? icons.pin : icons.pinOff}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm btn--icon ${hideCron ? "active" : ""}"
|
||||
@click=${() => {
|
||||
|
||||
@@ -183,6 +183,72 @@ describe("stream reconciliation", () => {
|
||||
expect(next.map(messageText)).toEqual(["latest ask", "before tool\nfinal answer"]);
|
||||
});
|
||||
|
||||
it("omits keyed commentary parts when persistCommentary is false (transient mode)", () => {
|
||||
const state = {
|
||||
chatStream: null,
|
||||
chatStreamStartedAt: null,
|
||||
chatStreamSegments: [
|
||||
{ text: "first preamble", ts: 2, itemId: "preamble-1" },
|
||||
{ text: "second preamble", ts: 3, itemId: "preamble-2" },
|
||||
],
|
||||
} satisfies StreamReconciliationState & {
|
||||
chatStreamSegments: Array<{ text: string; ts: number; itemId: string }>;
|
||||
};
|
||||
const messages = [
|
||||
{ role: "user", content: "latest ask", timestamp: 1 },
|
||||
{ role: "assistant", content: [{ type: "text", text: "final reply" }], timestamp: 4 },
|
||||
];
|
||||
|
||||
const next = materializeVisibleStreamState(messages, state, {
|
||||
...visibleStreamOptions,
|
||||
persistCommentary: false,
|
||||
});
|
||||
|
||||
// Keyed commentary stays live during streaming but is never materialized,
|
||||
// so only the user ask and the final reply remain.
|
||||
expect(next.map(messageText)).toEqual(["latest ask", "final reply"]);
|
||||
});
|
||||
|
||||
it("still materializes the current stream tail when persistCommentary is false", () => {
|
||||
const state = {
|
||||
chatStream: "draft answer",
|
||||
chatStreamStartedAt: 3,
|
||||
chatStreamSegments: [{ text: "transient preamble", ts: 2, itemId: "preamble-1" }],
|
||||
} satisfies StreamReconciliationState & {
|
||||
chatStreamSegments: Array<{ text: string; ts: number; itemId: string }>;
|
||||
};
|
||||
const messages = [{ role: "user", content: "latest ask", timestamp: 1 }];
|
||||
|
||||
const next = materializeVisibleStreamState(messages, state, {
|
||||
...visibleStreamOptions,
|
||||
persistCommentary: false,
|
||||
});
|
||||
|
||||
// The keyed preamble is dropped, but the non-keyed current draft is preserved.
|
||||
expect(next.map(messageText)).toEqual(["latest ask", "draft answer"]);
|
||||
});
|
||||
|
||||
it("materializes keyed commentary parts when persistCommentary is true (persist mode)", () => {
|
||||
const state = {
|
||||
chatStream: null,
|
||||
chatStreamStartedAt: null,
|
||||
chatStreamSegments: [{ text: "kept preamble", ts: 2, itemId: "preamble-1" }],
|
||||
} satisfies StreamReconciliationState & {
|
||||
chatStreamSegments: Array<{ text: string; ts: number; itemId: string }>;
|
||||
};
|
||||
const messages = [
|
||||
{ role: "user", content: "latest ask", timestamp: 1 },
|
||||
{ role: "assistant", content: [{ type: "text", text: "final reply" }], timestamp: 4 },
|
||||
];
|
||||
|
||||
const next = materializeVisibleStreamState(messages, state, {
|
||||
...visibleStreamOptions,
|
||||
persistCommentary: true,
|
||||
});
|
||||
|
||||
expect(next.map(messageText)).toEqual(["latest ask", "kept preamble", "final reply"]);
|
||||
});
|
||||
|
||||
it("replaces current-stream fallbacks with matching terminal messages", () => {
|
||||
const state = {
|
||||
chatStream: "draft answer",
|
||||
|
||||
@@ -42,6 +42,10 @@ export type MaterializeVisibleStreamOptions = {
|
||||
includeCurrent?: boolean;
|
||||
requirePersistedTool?: boolean;
|
||||
replacementMessages?: unknown[];
|
||||
// When false, keyed commentary/preamble parts are not materialized into the
|
||||
// persistent message list, so they clear with the final answer (transient mode).
|
||||
// Defaults to true (persist commentary).
|
||||
persistCommentary?: boolean;
|
||||
isHiddenAssistantMessage: AssistantMessageVisibility;
|
||||
isHiddenStreamText: StreamVisibility;
|
||||
};
|
||||
@@ -450,7 +454,13 @@ export function materializeVisibleStreamState(
|
||||
opts: MaterializeVisibleStreamOptions,
|
||||
): unknown[] {
|
||||
let nextMessages = messages;
|
||||
const persistCommentary = opts.persistCommentary !== false;
|
||||
for (const part of visibleAssistantStreamParts(state, opts)) {
|
||||
// Transient mode: keyed commentary stays live during streaming but is never
|
||||
// materialized, so it disappears once the final message lands.
|
||||
if (!persistCommentary && part.itemId) {
|
||||
continue;
|
||||
}
|
||||
const replacementMessages = opts.replacementMessages ?? [];
|
||||
if (
|
||||
hasAssistantStreamPartReplacement(
|
||||
|
||||
@@ -583,6 +583,68 @@ describe("handleChatEvent", () => {
|
||||
expect(state.chatStreamStartedAt).toBe(null);
|
||||
});
|
||||
|
||||
it("persists keyed commentary alongside the final answer by default", () => {
|
||||
const user = { role: "user", content: [{ type: "text", text: "Ask" }], timestamp: 1 };
|
||||
const state = createState({
|
||||
sessionKey: "main",
|
||||
chatRunId: "run-1",
|
||||
chatMessages: [user],
|
||||
chatStream: null,
|
||||
chatStreamStartedAt: null,
|
||||
}) as ChatState & {
|
||||
chatStreamSegments: Array<{ text: string; ts: number; itemId: string }>;
|
||||
};
|
||||
state.chatStreamSegments = [{ text: "Looking into it.", ts: 2, itemId: "preamble-1" }];
|
||||
const payload: ChatEventPayload = {
|
||||
runId: "run-1",
|
||||
sessionKey: "main",
|
||||
state: "final",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Final answer." }],
|
||||
timestamp: 5,
|
||||
},
|
||||
};
|
||||
|
||||
expect(handleChatEvent(state, payload)).toBe("final");
|
||||
expect(state.chatMessages).toHaveLength(3);
|
||||
expectTextChatMessage(state.chatMessages[0], "user", "Ask");
|
||||
expectTextChatMessage(state.chatMessages[1], "assistant", "Looking into it.");
|
||||
expectTextChatMessage(state.chatMessages[2], "assistant", "Final answer.");
|
||||
expect(state.chatStreamSegments).toEqual([]);
|
||||
});
|
||||
|
||||
it("clears keyed commentary with the final answer when chatPersistCommentary is false", () => {
|
||||
const user = { role: "user", content: [{ type: "text", text: "Ask" }], timestamp: 1 };
|
||||
const state = createState({
|
||||
sessionKey: "main",
|
||||
chatRunId: "run-1",
|
||||
chatMessages: [user],
|
||||
chatStream: null,
|
||||
chatStreamStartedAt: null,
|
||||
settings: { chatPersistCommentary: false },
|
||||
}) as ChatState & {
|
||||
chatStreamSegments: Array<{ text: string; ts: number; itemId: string }>;
|
||||
};
|
||||
state.chatStreamSegments = [{ text: "Looking into it.", ts: 2, itemId: "preamble-1" }];
|
||||
const payload: ChatEventPayload = {
|
||||
runId: "run-1",
|
||||
sessionKey: "main",
|
||||
state: "final",
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Final answer." }],
|
||||
timestamp: 5,
|
||||
},
|
||||
};
|
||||
|
||||
expect(handleChatEvent(state, payload)).toBe("final");
|
||||
expect(state.chatMessages).toHaveLength(2);
|
||||
expectTextChatMessage(state.chatMessages[0], "user", "Ask");
|
||||
expectTextChatMessage(state.chatMessages[1], "assistant", "Final answer.");
|
||||
expect(state.chatStreamSegments).toEqual([]);
|
||||
});
|
||||
|
||||
it("reconciles cached run and indicator state on terminal events", () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -204,11 +204,17 @@ function materializeVisibleAssistantStreamMessages(
|
||||
): unknown[] {
|
||||
return materializeVisibleStreamState(messages, state, {
|
||||
...opts,
|
||||
persistCommentary: chatPersistCommentaryEnabled(state),
|
||||
isHiddenAssistantMessage: shouldHideAssistantChatMessage,
|
||||
isHiddenStreamText: isHiddenAssistantStreamText,
|
||||
});
|
||||
}
|
||||
|
||||
function chatPersistCommentaryEnabled(state: ChatState): boolean {
|
||||
// Per-viewer display preference; defaults to persist when unset (e.g. tests/headless).
|
||||
return state.settings?.chatPersistCommentary !== false;
|
||||
}
|
||||
|
||||
function hasTranscriptMeta(message: unknown): boolean {
|
||||
return Boolean(
|
||||
message &&
|
||||
@@ -405,6 +411,8 @@ export type ChatState = {
|
||||
agentsList?: ChatAgentsListSnapshot | null;
|
||||
agentsSelectedId?: string | null;
|
||||
hello?: GatewayHelloOk | null;
|
||||
// Narrow view of UiSettings; the runtime host is the full app component.
|
||||
settings?: { chatPersistCommentary?: boolean };
|
||||
};
|
||||
|
||||
type ChatAgentsListSnapshot = Partial<Omit<AgentsListResult, "agents">> & {
|
||||
|
||||
@@ -140,6 +140,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
themeMode: "system",
|
||||
chatShowThinking: true,
|
||||
chatShowToolCalls: true,
|
||||
chatPersistCommentary: true,
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
@@ -271,6 +272,7 @@ describe("loadSettings default gateway URL derivation", () => {
|
||||
themeMode: "system",
|
||||
chatShowThinking: true,
|
||||
chatShowToolCalls: true,
|
||||
chatPersistCommentary: true,
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
|
||||
@@ -88,6 +88,9 @@ export type UiSettings = {
|
||||
themeMode: ThemeMode;
|
||||
chatShowThinking: boolean;
|
||||
chatShowToolCalls: boolean;
|
||||
// Keep streamed commentary/preamble blocks visible after the final answer (default true).
|
||||
// When false, commentary clears as the final message arrives (transient progress).
|
||||
chatPersistCommentary?: boolean;
|
||||
chatAutoScroll?: ChatAutoScrollMode;
|
||||
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
|
||||
navCollapsed: boolean; // Collapsible sidebar state
|
||||
@@ -246,6 +249,7 @@ export function loadSettings(): UiSettings {
|
||||
themeMode: "system",
|
||||
chatShowThinking: true,
|
||||
chatShowToolCalls: true,
|
||||
chatPersistCommentary: true,
|
||||
chatAutoScroll: "near-bottom",
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
@@ -291,6 +295,10 @@ export function loadSettings(): UiSettings {
|
||||
typeof parsed.chatShowToolCalls === "boolean"
|
||||
? parsed.chatShowToolCalls
|
||||
: defaults.chatShowToolCalls,
|
||||
chatPersistCommentary:
|
||||
typeof parsed.chatPersistCommentary === "boolean"
|
||||
? parsed.chatPersistCommentary
|
||||
: defaults.chatPersistCommentary,
|
||||
chatAutoScroll: normalizeChatAutoScrollMode(parsed.chatAutoScroll),
|
||||
splitRatio:
|
||||
typeof parsed.splitRatio === "number" &&
|
||||
@@ -490,6 +498,7 @@ function persistSettings(next: UiSettings) {
|
||||
themeMode: next.themeMode,
|
||||
chatShowThinking: next.chatShowThinking,
|
||||
chatShowToolCalls: next.chatShowToolCalls,
|
||||
chatPersistCommentary: next.chatPersistCommentary ?? true,
|
||||
chatAutoScroll: normalizeChatAutoScrollMode(next.chatAutoScroll),
|
||||
splitRatio: next.splitRatio,
|
||||
navCollapsed: next.navCollapsed,
|
||||
|
||||
Reference in New Issue
Block a user