feat(settings): add streaming auto-follow toggle
New Streaming section on the Chat settings page with a checkbox that controls whether the viewport follows new content while a response streams. Default stays on. With it off, the anchored user message still parks at the top on send, but no glide or end-follow correction runs and the list's maintain-scroll-at-end stays disabled; the scroll-to-bottom pill and session open keep scrolling explicitly. Persisted through desktop settings like the other chat toggles (auto-save diff, authoritative apply, sanitize), registered in settings search, and localized in every locale.
This commit is contained in:
@@ -1021,6 +1021,10 @@ const TimelineList = React.memo(({
|
|||||||
rowContext,
|
rowContext,
|
||||||
}: TimelineListProps) => {
|
}: TimelineListProps) => {
|
||||||
const listRef = React.useRef<LegendListRef | null>(null);
|
const listRef = React.useRef<LegendListRef | null>(null);
|
||||||
|
// With streaming auto-follow off, content growth must never move the
|
||||||
|
// viewport; explicit commands (the scroll-to-bottom pill, session open)
|
||||||
|
// still scroll through the imperative handle.
|
||||||
|
const streamingAutoFollowEnabled = useUIStore((state) => state.streamingAutoFollowEnabled);
|
||||||
const isAtEndRef = React.useRef(true);
|
const isAtEndRef = React.useRef(true);
|
||||||
|
|
||||||
const setListRef = React.useCallback((list: LegendListRef | null) => {
|
const setListRef = React.useCallback((list: LegendListRef | null) => {
|
||||||
@@ -1067,7 +1071,7 @@ const TimelineList = React.memo(({
|
|||||||
contentInsetEndAdjustment={composerOverlayHeight}
|
contentInsetEndAdjustment={composerOverlayHeight}
|
||||||
// While a turn is anchored, the reserved end space — not the
|
// While a turn is anchored, the reserved end space — not the
|
||||||
// live edge — defines where the viewport rests.
|
// live edge — defines where the viewport rests.
|
||||||
maintainScrollAtEnd={anchoredEndSpace
|
maintainScrollAtEnd={anchoredEndSpace || !streamingAutoFollowEnabled
|
||||||
? false
|
? false
|
||||||
: { animated: false, on: { dataChange: true, itemLayout: true, layout: true } }}
|
: { animated: false, on: { dataChange: true, itemLayout: true, layout: true } }}
|
||||||
// Prepending older history must not move what the user is
|
// Prepending older history must not move what the user is
|
||||||
|
|||||||
@@ -302,6 +302,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
const sessionGoalDefaultBudget = useUIStore(state => state.sessionGoalDefaultBudget);
|
const sessionGoalDefaultBudget = useUIStore(state => state.sessionGoalDefaultBudget);
|
||||||
const setSessionGoalDefaultBudget = useUIStore(state => state.setSessionGoalDefaultBudget);
|
const setSessionGoalDefaultBudget = useUIStore(state => state.setSessionGoalDefaultBudget);
|
||||||
const setShowReasoningTraces = useUIStore(state => state.setShowReasoningTraces);
|
const setShowReasoningTraces = useUIStore(state => state.setShowReasoningTraces);
|
||||||
|
const streamingAutoFollowEnabled = useUIStore(state => state.streamingAutoFollowEnabled);
|
||||||
|
const setStreamingAutoFollowEnabled = useUIStore(state => state.setStreamingAutoFollowEnabled);
|
||||||
const collapsibleThinkingBlocks = useUIStore(state => state.collapsibleThinkingBlocks);
|
const collapsibleThinkingBlocks = useUIStore(state => state.collapsibleThinkingBlocks);
|
||||||
const setCollapsibleThinkingBlocks = useUIStore(state => state.setCollapsibleThinkingBlocks);
|
const setCollapsibleThinkingBlocks = useUIStore(state => state.setCollapsibleThinkingBlocks);
|
||||||
|
|
||||||
@@ -1839,6 +1841,20 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
)}
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
|
<SettingsSection
|
||||||
|
title={t('settings.openchamber.visual.section.streaming')}
|
||||||
|
settingsItem="chat.streaming"
|
||||||
|
contentClassName={SETTINGS_OPTION_STACK_CLASS}
|
||||||
|
>
|
||||||
|
<SettingsCheckboxRow
|
||||||
|
checked={streamingAutoFollowEnabled}
|
||||||
|
onChange={setStreamingAutoFollowEnabled}
|
||||||
|
label={t('settings.openchamber.visual.field.streamingAutoFollow')}
|
||||||
|
ariaLabel={t('settings.openchamber.visual.field.streamingAutoFollowAria')}
|
||||||
|
info={t('settings.openchamber.visual.field.streamingAutoFollowInfo')}
|
||||||
|
settingsItem="chat.streaming-auto-follow"
|
||||||
|
/>
|
||||||
|
</SettingsSection>
|
||||||
|
|
||||||
{(shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || (shouldShow('promptNavigatorEnabled') && !isVSCode) || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('codeBlockLineWrap')) && (
|
{(shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || (shouldShow('promptNavigatorEnabled') && !isVSCode) || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('codeBlockLineWrap')) && (
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { MessageFreshnessDetector } from '@/lib/messageFreshness';
|
import { MessageFreshnessDetector } from '@/lib/messageFreshness';
|
||||||
import { createScrollSpy } from '@/components/chat/lib/scroll/scrollSpy';
|
import { createScrollSpy } from '@/components/chat/lib/scroll/scrollSpy';
|
||||||
import { useViewportStore } from '@/sync/viewport-store';
|
import { useViewportStore } from '@/sync/viewport-store';
|
||||||
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import {
|
import {
|
||||||
CHAT_LIST_ANCHOR_OFFSET,
|
CHAT_LIST_ANCHOR_OFFSET,
|
||||||
getAnchoredTurnMetrics,
|
getAnchoredTurnMetrics,
|
||||||
@@ -520,7 +521,15 @@ export const useChatTimelineScroll = ({
|
|||||||
first: null,
|
first: null,
|
||||||
second: null,
|
second: null,
|
||||||
});
|
});
|
||||||
|
// User preference: with auto-follow off, streaming growth never moves the
|
||||||
|
// viewport — the anchored user message still parks at the top on send, but
|
||||||
|
// no glide or end-follow correction runs afterwards.
|
||||||
|
const streamingAutoFollowEnabled = useUIStore((state) => state.streamingAutoFollowEnabled);
|
||||||
|
const streamingAutoFollowEnabledRef = React.useRef(streamingAutoFollowEnabled);
|
||||||
|
streamingAutoFollowEnabledRef.current = streamingAutoFollowEnabled;
|
||||||
|
|
||||||
const onTimelineDataChange = React.useCallback(() => {
|
const onTimelineDataChange = React.useCallback(() => {
|
||||||
|
if (!streamingAutoFollowEnabledRef.current) return;
|
||||||
if (!isLiveFollowActive()) return;
|
if (!isLiveFollowActive()) return;
|
||||||
|
|
||||||
// Following the end needs no animation frames: the totalSize listener
|
// Following the end needs no animation frames: the totalSize listener
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { TerminalShell } from '@/lib/api/types';
|
|||||||
|
|
||||||
type AppearanceSlice = {
|
type AppearanceSlice = {
|
||||||
showReasoningTraces: boolean;
|
showReasoningTraces: boolean;
|
||||||
|
streamingAutoFollowEnabled: boolean;
|
||||||
workStatusPanelEnabled: boolean;
|
workStatusPanelEnabled: boolean;
|
||||||
workStatusHiddenSections: string[];
|
workStatusHiddenSections: string[];
|
||||||
sessionRecapEnabled: boolean;
|
sessionRecapEnabled: boolean;
|
||||||
@@ -62,6 +63,7 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
|
|
||||||
let previous: AppearanceSlice = {
|
let previous: AppearanceSlice = {
|
||||||
showReasoningTraces: useUIStore.getState().showReasoningTraces,
|
showReasoningTraces: useUIStore.getState().showReasoningTraces,
|
||||||
|
streamingAutoFollowEnabled: useUIStore.getState().streamingAutoFollowEnabled,
|
||||||
workStatusPanelEnabled: useUIStore.getState().workStatusPanelEnabled,
|
workStatusPanelEnabled: useUIStore.getState().workStatusPanelEnabled,
|
||||||
workStatusHiddenSections: useUIStore.getState().workStatusHiddenSections,
|
workStatusHiddenSections: useUIStore.getState().workStatusHiddenSections,
|
||||||
sessionRecapEnabled: useUIStore.getState().sessionRecapEnabled,
|
sessionRecapEnabled: useUIStore.getState().sessionRecapEnabled,
|
||||||
@@ -104,6 +106,7 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
useUIStore.subscribe((state) => {
|
useUIStore.subscribe((state) => {
|
||||||
const current: AppearanceSlice = {
|
const current: AppearanceSlice = {
|
||||||
showReasoningTraces: state.showReasoningTraces,
|
showReasoningTraces: state.showReasoningTraces,
|
||||||
|
streamingAutoFollowEnabled: state.streamingAutoFollowEnabled,
|
||||||
workStatusPanelEnabled: state.workStatusPanelEnabled,
|
workStatusPanelEnabled: state.workStatusPanelEnabled,
|
||||||
workStatusHiddenSections: state.workStatusHiddenSections,
|
workStatusHiddenSections: state.workStatusHiddenSections,
|
||||||
sessionRecapEnabled: state.sessionRecapEnabled,
|
sessionRecapEnabled: state.sessionRecapEnabled,
|
||||||
@@ -156,6 +159,9 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
if (current.showReasoningTraces !== previous.showReasoningTraces) {
|
if (current.showReasoningTraces !== previous.showReasoningTraces) {
|
||||||
diff.showReasoningTraces = current.showReasoningTraces;
|
diff.showReasoningTraces = current.showReasoningTraces;
|
||||||
}
|
}
|
||||||
|
if (current.streamingAutoFollowEnabled !== previous.streamingAutoFollowEnabled) {
|
||||||
|
diff.streamingAutoFollowEnabled = current.streamingAutoFollowEnabled;
|
||||||
|
}
|
||||||
if (current.sessionRecapEnabled !== previous.sessionRecapEnabled) {
|
if (current.sessionRecapEnabled !== previous.sessionRecapEnabled) {
|
||||||
diff.sessionRecapEnabled = current.sessionRecapEnabled;
|
diff.sessionRecapEnabled = current.sessionRecapEnabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export type DesktopSettings = {
|
|||||||
defaultVariant?: string;
|
defaultVariant?: string;
|
||||||
defaultAgent?: string;
|
defaultAgent?: string;
|
||||||
smallModelUseDefault?: boolean;
|
smallModelUseDefault?: boolean;
|
||||||
|
streamingAutoFollowEnabled?: boolean;
|
||||||
sessionRecapEnabled?: boolean;
|
sessionRecapEnabled?: boolean;
|
||||||
sessionSuggestionEnabled?: boolean;
|
sessionSuggestionEnabled?: boolean;
|
||||||
sessionGoalEnabled?: boolean;
|
sessionGoalEnabled?: boolean;
|
||||||
|
|||||||
@@ -1856,6 +1856,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Werkzeuge standardmäßig geöffnet anzeigen:',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Werkzeuge standardmäßig geöffnet anzeigen:',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': 'Sitzungshilfe',
|
'settings.openchamber.visual.section.sessionAssistance': 'Sitzungshilfe',
|
||||||
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
||||||
|
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': 'Neuen Inhalten beim Streaming folgen',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Neuen Inhalten automatisch folgen, während eine Antwort gestreamt wird',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Während eine Antwort eintrifft, folgt die Ansicht laufend dem neuesten Inhalt. Deaktivieren, um die Ansicht ruhig zu halten und manuell zu scrollen.',
|
||||||
'settings.openchamber.visual.section.messageAppearance': 'Nachrichten-Erscheinungsbild',
|
'settings.openchamber.visual.section.messageAppearance': 'Nachrichten-Erscheinungsbild',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': 'Werkzeuge & Dateien',
|
'settings.openchamber.visual.section.toolsAndFiles': 'Werkzeuge & Dateien',
|
||||||
'settings.openchamber.visual.section.composer': 'Komponist',
|
'settings.openchamber.visual.section.composer': 'Komponist',
|
||||||
|
|||||||
@@ -1929,6 +1929,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Show tools opened by default',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Show tools opened by default',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': 'Session Assistance',
|
'settings.openchamber.visual.section.sessionAssistance': 'Session Assistance',
|
||||||
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
||||||
|
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': 'Follow new content while streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Automatically follow new content while a response streams',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'While a reply streams in, the view keeps gliding to the newest content. Turn this off to keep the view still and scroll manually.',
|
||||||
'settings.openchamber.visual.section.messageAppearance': 'Message Appearance',
|
'settings.openchamber.visual.section.messageAppearance': 'Message Appearance',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': 'Tools & Files',
|
'settings.openchamber.visual.section.toolsAndFiles': 'Tools & Files',
|
||||||
'settings.openchamber.visual.section.composer': 'Composer',
|
'settings.openchamber.visual.section.composer': 'Composer',
|
||||||
|
|||||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar herramientas abiertas por defecto",
|
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar herramientas abiertas por defecto",
|
||||||
"settings.openchamber.visual.section.sessionAssistance": "Asistencia de sesión",
|
"settings.openchamber.visual.section.sessionAssistance": "Asistencia de sesión",
|
||||||
"settings.openchamber.visual.section.reasoning": "Razonamiento",
|
"settings.openchamber.visual.section.reasoning": "Razonamiento",
|
||||||
|
"settings.openchamber.visual.section.streaming": "Streaming",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollow": "Seguir el contenido nuevo durante el streaming",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowAria": "Seguir automáticamente el contenido nuevo mientras se transmite una respuesta",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Mientras llega una respuesta, la vista se desplaza hacia el contenido más reciente. Desactívalo para mantener la vista quieta y desplazarte manualmente.",
|
||||||
"settings.openchamber.visual.section.messageAppearance": "Apariencia de los mensajes",
|
"settings.openchamber.visual.section.messageAppearance": "Apariencia de los mensajes",
|
||||||
"settings.openchamber.visual.section.toolsAndFiles": "Herramientas y archivos",
|
"settings.openchamber.visual.section.toolsAndFiles": "Herramientas y archivos",
|
||||||
"settings.openchamber.visual.section.composer": "Compositor",
|
"settings.openchamber.visual.section.composer": "Compositor",
|
||||||
|
|||||||
@@ -1820,6 +1820,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Afficher les outils ouverts par défaut',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Afficher les outils ouverts par défaut',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': 'Assistance de session',
|
'settings.openchamber.visual.section.sessionAssistance': 'Assistance de session',
|
||||||
'settings.openchamber.visual.section.reasoning': 'Raisonnement',
|
'settings.openchamber.visual.section.reasoning': 'Raisonnement',
|
||||||
|
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': 'Suivre le nouveau contenu pendant le streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Suivre automatiquement le nouveau contenu pendant la diffusion d’une réponse',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Pendant qu’une réponse arrive, la vue glisse vers le contenu le plus récent. Désactivez pour garder la vue immobile et défiler manuellement.',
|
||||||
'settings.openchamber.visual.section.messageAppearance': 'Apparence des messages',
|
'settings.openchamber.visual.section.messageAppearance': 'Apparence des messages',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': 'Outils et fichiers',
|
'settings.openchamber.visual.section.toolsAndFiles': 'Outils et fichiers',
|
||||||
'settings.openchamber.visual.section.composer': 'Zone de saisie',
|
'settings.openchamber.visual.section.composer': 'Zone de saisie',
|
||||||
|
|||||||
@@ -1939,6 +1939,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'デフォルトで開くツールを表示',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'デフォルトで開くツールを表示',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': 'セッション支援',
|
'settings.openchamber.visual.section.sessionAssistance': 'セッション支援',
|
||||||
'settings.openchamber.visual.section.reasoning': '推論',
|
'settings.openchamber.visual.section.reasoning': '推論',
|
||||||
|
'settings.openchamber.visual.section.streaming': 'ストリーミング',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': '応答のストリーミング中に新しい内容を追従',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': '応答のストリーミング中に新しい内容へ自動スクロールする',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': '応答の受信中、ビューは常に最新の内容へスクロールします。オフにするとビューは動かず、手動でスクロールできます。',
|
||||||
'settings.openchamber.visual.section.messageAppearance': 'メッセージの外観',
|
'settings.openchamber.visual.section.messageAppearance': 'メッセージの外観',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': 'ツールとファイル',
|
'settings.openchamber.visual.section.toolsAndFiles': 'ツールとファイル',
|
||||||
'settings.openchamber.visual.section.composer': '入力欄',
|
'settings.openchamber.visual.section.composer': '入力欄',
|
||||||
|
|||||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '도구를 기본으로 펼쳐 표시',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': '도구를 기본으로 펼쳐 표시',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': '세션 지원',
|
'settings.openchamber.visual.section.sessionAssistance': '세션 지원',
|
||||||
'settings.openchamber.visual.section.reasoning': '추론',
|
'settings.openchamber.visual.section.reasoning': '추론',
|
||||||
|
'settings.openchamber.visual.section.streaming': '스트리밍',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': '스트리밍 중 새 내용 따라가기',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': '응답 스트리밍 중 새 내용으로 자동 스크롤',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': '응답이 스트리밍되는 동안 화면이 최신 내용으로 계속 이동합니다. 끄면 화면이 고정되어 직접 스크롤할 수 있습니다.',
|
||||||
'settings.openchamber.visual.section.messageAppearance': '메시지 모양',
|
'settings.openchamber.visual.section.messageAppearance': '메시지 모양',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': '도구 및 파일',
|
'settings.openchamber.visual.section.toolsAndFiles': '도구 및 파일',
|
||||||
'settings.openchamber.visual.section.composer': '입력창',
|
'settings.openchamber.visual.section.composer': '입력창',
|
||||||
|
|||||||
@@ -1209,6 +1209,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Pokaż narzędzia domyślnie otwarte',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Pokaż narzędzia domyślnie otwarte',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': 'Wsparcie sesji',
|
'settings.openchamber.visual.section.sessionAssistance': 'Wsparcie sesji',
|
||||||
'settings.openchamber.visual.section.reasoning': 'Rozumowanie',
|
'settings.openchamber.visual.section.reasoning': 'Rozumowanie',
|
||||||
|
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': 'Podążaj za nową treścią podczas streamingu',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Automatycznie podążaj za nową treścią podczas streamowania odpowiedzi',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Podczas napływania odpowiedzi widok płynnie podąża za najnowszą treścią. Wyłącz, aby widok pozostał nieruchomy i przewijać ręcznie.',
|
||||||
'settings.openchamber.visual.section.messageAppearance': 'Wygląd wiadomości',
|
'settings.openchamber.visual.section.messageAppearance': 'Wygląd wiadomości',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': 'Narzędzia i pliki',
|
'settings.openchamber.visual.section.toolsAndFiles': 'Narzędzia i pliki',
|
||||||
'settings.openchamber.visual.section.composer': 'Pole wiadomości',
|
'settings.openchamber.visual.section.composer': 'Pole wiadomości',
|
||||||
|
|||||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar ferramentas abertas por padrão",
|
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar ferramentas abertas por padrão",
|
||||||
"settings.openchamber.visual.section.sessionAssistance": "Assistência da sessão",
|
"settings.openchamber.visual.section.sessionAssistance": "Assistência da sessão",
|
||||||
"settings.openchamber.visual.section.reasoning": "Raciocínio",
|
"settings.openchamber.visual.section.reasoning": "Raciocínio",
|
||||||
|
"settings.openchamber.visual.section.streaming": "Streaming",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollow": "Seguir o novo conteúdo durante o streaming",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowAria": "Seguir automaticamente o novo conteúdo enquanto uma resposta é transmitida",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Enquanto uma resposta chega, a visualização acompanha o conteúdo mais recente. Desative para manter a visualização parada e rolar manualmente.",
|
||||||
"settings.openchamber.visual.section.messageAppearance": "Aparência das mensagens",
|
"settings.openchamber.visual.section.messageAppearance": "Aparência das mensagens",
|
||||||
"settings.openchamber.visual.section.toolsAndFiles": "Ferramentas e arquivos",
|
"settings.openchamber.visual.section.toolsAndFiles": "Ferramentas e arquivos",
|
||||||
"settings.openchamber.visual.section.composer": "Campo de mensagem",
|
"settings.openchamber.visual.section.composer": "Campo de mensagem",
|
||||||
|
|||||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Показувати інструменти відкритими за замовчуванням",
|
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Показувати інструменти відкритими за замовчуванням",
|
||||||
"settings.openchamber.visual.section.sessionAssistance": "Допомога із сесією",
|
"settings.openchamber.visual.section.sessionAssistance": "Допомога із сесією",
|
||||||
"settings.openchamber.visual.section.reasoning": "Міркування",
|
"settings.openchamber.visual.section.reasoning": "Міркування",
|
||||||
|
"settings.openchamber.visual.section.streaming": "Стримінг",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollow": "Слідкувати за новим вмістом під час стримінгу",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowAria": "Автоматично слідкувати за новим вмістом під час стримінгу відповіді",
|
||||||
|
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Поки відповідь надходить, вигляд плавно рухається до найновішого вмісту. Вимкніть, щоб вигляд залишався нерухомим і гортати вручну.",
|
||||||
"settings.openchamber.visual.section.messageAppearance": "Вигляд повідомлень",
|
"settings.openchamber.visual.section.messageAppearance": "Вигляд повідомлень",
|
||||||
"settings.openchamber.visual.section.toolsAndFiles": "Інструменти та файли",
|
"settings.openchamber.visual.section.toolsAndFiles": "Інструменти та файли",
|
||||||
"settings.openchamber.visual.section.composer": "Поле вводу",
|
"settings.openchamber.visual.section.composer": "Поле вводу",
|
||||||
|
|||||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '默认展开以下工具',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': '默认展开以下工具',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': '会话辅助',
|
'settings.openchamber.visual.section.sessionAssistance': '会话辅助',
|
||||||
'settings.openchamber.visual.section.reasoning': '推理',
|
'settings.openchamber.visual.section.reasoning': '推理',
|
||||||
|
'settings.openchamber.visual.section.streaming': '流式输出',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': '流式输出时跟随新内容',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': '在回复流式输出时自动跟随新内容',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': '回复流式输出时,视图会持续滚动到最新内容。关闭后视图保持不动,可手动滚动。',
|
||||||
'settings.openchamber.visual.section.messageAppearance': '消息外观',
|
'settings.openchamber.visual.section.messageAppearance': '消息外观',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': '工具和文件',
|
'settings.openchamber.visual.section.toolsAndFiles': '工具和文件',
|
||||||
'settings.openchamber.visual.section.composer': '输入框',
|
'settings.openchamber.visual.section.composer': '输入框',
|
||||||
|
|||||||
@@ -1813,6 +1813,10 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '預設展開以下工具',
|
'settings.openchamber.visual.section.showToolsOpenedByDefault': '預設展開以下工具',
|
||||||
'settings.openchamber.visual.section.sessionAssistance': '工作階段輔助',
|
'settings.openchamber.visual.section.sessionAssistance': '工作階段輔助',
|
||||||
'settings.openchamber.visual.section.reasoning': '推理',
|
'settings.openchamber.visual.section.reasoning': '推理',
|
||||||
|
'settings.openchamber.visual.section.streaming': '串流',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollow': '串流時跟隨新內容',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowAria': '回覆串流時自動跟隨新內容',
|
||||||
|
'settings.openchamber.visual.field.streamingAutoFollowInfo': '回覆串流時,畫面會持續捲動到最新內容。關閉後畫面保持不動,可手動捲動。',
|
||||||
'settings.openchamber.visual.section.messageAppearance': '訊息外觀',
|
'settings.openchamber.visual.section.messageAppearance': '訊息外觀',
|
||||||
'settings.openchamber.visual.section.toolsAndFiles': '工具與檔案',
|
'settings.openchamber.visual.section.toolsAndFiles': '工具與檔案',
|
||||||
'settings.openchamber.visual.section.composer': '輸入框',
|
'settings.openchamber.visual.section.composer': '輸入框',
|
||||||
|
|||||||
@@ -531,6 +531,7 @@ const materializeAuthoritativeUiSettings = (settings: DesktopSettings): DesktopS
|
|||||||
darkThemeId: DEFAULT_DARK_THEME_ID,
|
darkThemeId: DEFAULT_DARK_THEME_ID,
|
||||||
openInAppId: DEFAULT_OPEN_IN_APP_ID,
|
openInAppId: DEFAULT_OPEN_IN_APP_ID,
|
||||||
showReasoningTraces: defaults.showReasoningTraces,
|
showReasoningTraces: defaults.showReasoningTraces,
|
||||||
|
streamingAutoFollowEnabled: defaults.streamingAutoFollowEnabled,
|
||||||
workStatusPanelEnabled: defaults.workStatusPanelEnabled,
|
workStatusPanelEnabled: defaults.workStatusPanelEnabled,
|
||||||
workStatusHiddenSections: defaults.workStatusHiddenSections,
|
workStatusHiddenSections: defaults.workStatusHiddenSections,
|
||||||
sessionRecapEnabled: defaults.sessionRecapEnabled,
|
sessionRecapEnabled: defaults.sessionRecapEnabled,
|
||||||
@@ -637,6 +638,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
|||||||
if (typeof settings.showReasoningTraces === 'boolean' && settings.showReasoningTraces !== store.showReasoningTraces) {
|
if (typeof settings.showReasoningTraces === 'boolean' && settings.showReasoningTraces !== store.showReasoningTraces) {
|
||||||
store.setShowReasoningTraces(settings.showReasoningTraces);
|
store.setShowReasoningTraces(settings.showReasoningTraces);
|
||||||
}
|
}
|
||||||
|
if (typeof settings.streamingAutoFollowEnabled === 'boolean' && settings.streamingAutoFollowEnabled !== store.streamingAutoFollowEnabled) {
|
||||||
|
store.setStreamingAutoFollowEnabled(settings.streamingAutoFollowEnabled);
|
||||||
|
}
|
||||||
if (typeof settings.sessionRecapEnabled === 'boolean' && settings.sessionRecapEnabled !== store.sessionRecapEnabled) {
|
if (typeof settings.sessionRecapEnabled === 'boolean' && settings.sessionRecapEnabled !== store.sessionRecapEnabled) {
|
||||||
store.setSessionRecapEnabled(settings.sessionRecapEnabled);
|
store.setSessionRecapEnabled(settings.sessionRecapEnabled);
|
||||||
}
|
}
|
||||||
@@ -1158,6 +1162,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|||||||
if (typeof candidate.showReasoningTraces === 'boolean') {
|
if (typeof candidate.showReasoningTraces === 'boolean') {
|
||||||
result.showReasoningTraces = candidate.showReasoningTraces;
|
result.showReasoningTraces = candidate.showReasoningTraces;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.streamingAutoFollowEnabled === 'boolean') {
|
||||||
|
result.streamingAutoFollowEnabled = candidate.streamingAutoFollowEnabled;
|
||||||
|
}
|
||||||
if (typeof candidate.sessionRecapEnabled === 'boolean') {
|
if (typeof candidate.sessionRecapEnabled === 'boolean') {
|
||||||
result.sessionRecapEnabled = candidate.sessionRecapEnabled;
|
result.sessionRecapEnabled = candidate.sessionRecapEnabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,6 +240,19 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
|||||||
titleKey: 'settings.openchamber.visual.section.reasoning',
|
titleKey: 'settings.openchamber.visual.section.reasoning',
|
||||||
keywords: ['thinking', 'traces'],
|
keywords: ['thinking', 'traces'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'chat.streaming',
|
||||||
|
page: 'chat',
|
||||||
|
titleKey: 'settings.openchamber.visual.section.streaming',
|
||||||
|
keywords: ['stream', 'scroll'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chat.streaming-auto-follow',
|
||||||
|
page: 'chat',
|
||||||
|
titleKey: 'settings.openchamber.visual.field.streamingAutoFollow',
|
||||||
|
descriptionKey: 'settings.openchamber.visual.field.streamingAutoFollowInfo',
|
||||||
|
keywords: ['autoscroll', 'auto-scroll', 'follow', 'stick to bottom', 'streaming'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'chat.sticky-user-header',
|
id: 'chat.sticky-user-header',
|
||||||
page: 'chat',
|
page: 'chat',
|
||||||
|
|||||||
@@ -674,6 +674,7 @@ interface UIStore {
|
|||||||
eventStreamStatus: EventStreamStatus;
|
eventStreamStatus: EventStreamStatus;
|
||||||
eventStreamHint: string | null;
|
eventStreamHint: string | null;
|
||||||
showReasoningTraces: boolean;
|
showReasoningTraces: boolean;
|
||||||
|
streamingAutoFollowEnabled: boolean;
|
||||||
sessionRecapEnabled: boolean;
|
sessionRecapEnabled: boolean;
|
||||||
sessionSuggestionEnabled: boolean;
|
sessionSuggestionEnabled: boolean;
|
||||||
sessionGoalEnabled: boolean;
|
sessionGoalEnabled: boolean;
|
||||||
@@ -859,6 +860,7 @@ interface UIStore {
|
|||||||
setSettingsRemoteInstancesSelectedId: (instanceId: string | null) => void;
|
setSettingsRemoteInstancesSelectedId: (instanceId: string | null) => void;
|
||||||
setEventStreamStatus: (status: EventStreamStatus, hint?: string | null) => void;
|
setEventStreamStatus: (status: EventStreamStatus, hint?: string | null) => void;
|
||||||
setShowReasoningTraces: (value: boolean) => void;
|
setShowReasoningTraces: (value: boolean) => void;
|
||||||
|
setStreamingAutoFollowEnabled: (value: boolean) => void;
|
||||||
setSessionRecapEnabled: (value: boolean) => void;
|
setSessionRecapEnabled: (value: boolean) => void;
|
||||||
setSessionSuggestionEnabled: (value: boolean) => void;
|
setSessionSuggestionEnabled: (value: boolean) => void;
|
||||||
setSessionGoalEnabled: (value: boolean) => void;
|
setSessionGoalEnabled: (value: boolean) => void;
|
||||||
@@ -1027,6 +1029,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
eventStreamStatus: 'idle',
|
eventStreamStatus: 'idle',
|
||||||
eventStreamHint: null,
|
eventStreamHint: null,
|
||||||
showReasoningTraces: true,
|
showReasoningTraces: true,
|
||||||
|
streamingAutoFollowEnabled: true,
|
||||||
sessionRecapEnabled: true,
|
sessionRecapEnabled: true,
|
||||||
sessionSuggestionEnabled: true,
|
sessionSuggestionEnabled: true,
|
||||||
sessionGoalEnabled: true,
|
sessionGoalEnabled: true,
|
||||||
@@ -1759,6 +1762,10 @@ export const useUIStore = create<UIStore>()(
|
|||||||
set({ showReasoningTraces: value });
|
set({ showReasoningTraces: value });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setStreamingAutoFollowEnabled: (value) => {
|
||||||
|
set({ streamingAutoFollowEnabled: value });
|
||||||
|
},
|
||||||
|
|
||||||
setSessionRecapEnabled: (value) => {
|
setSessionRecapEnabled: (value) => {
|
||||||
set({ sessionRecapEnabled: value });
|
set({ sessionRecapEnabled: value });
|
||||||
},
|
},
|
||||||
@@ -2639,6 +2646,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
isSessionCreateDialogOpen: state.isSessionCreateDialogOpen,
|
isSessionCreateDialogOpen: state.isSessionCreateDialogOpen,
|
||||||
// Note: isSettingsDialogOpen intentionally NOT persisted
|
// Note: isSettingsDialogOpen intentionally NOT persisted
|
||||||
showReasoningTraces: state.showReasoningTraces,
|
showReasoningTraces: state.showReasoningTraces,
|
||||||
|
streamingAutoFollowEnabled: state.streamingAutoFollowEnabled,
|
||||||
sessionRecapEnabled: state.sessionRecapEnabled,
|
sessionRecapEnabled: state.sessionRecapEnabled,
|
||||||
sessionSuggestionEnabled: state.sessionSuggestionEnabled,
|
sessionSuggestionEnabled: state.sessionSuggestionEnabled,
|
||||||
sessionGoalEnabled: state.sessionGoalEnabled,
|
sessionGoalEnabled: state.sessionGoalEnabled,
|
||||||
|
|||||||
Reference in New Issue
Block a user