feat(web): add WebSocket transport for message event streaming with SSE fallback (#764)
* feat: add websocket message stream transport * fix: avoid false missing session directories in sidebar * fix: re-probe project root session directories * refactor: use button group for message stream transport * fix: resolve chat input hook dependency warning --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
fd8972a7d9
commit
bee9d19f3a
@@ -122,7 +122,7 @@ const VisualSectionContent: React.FC = () => {
|
||||
|
||||
// Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Queue mode, Persist draft
|
||||
const ChatSectionContent: React.FC = () => {
|
||||
return <OpenChamberVisualSettings visibleSettings={['chatRenderMode', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'expandedTools', 'stickyUserHeader', 'diffLayout', 'mobileStatusBar', 'dotfiles', 'queueMode', 'persistDraft', 'inputSpellcheck']} />;
|
||||
return <OpenChamberVisualSettings visibleSettings={['chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'expandedTools', 'stickyUserHeader', 'diffLayout', 'mobileStatusBar', 'dotfiles', 'queueMode', 'persistDraft', 'inputSpellcheck']} />;
|
||||
};
|
||||
|
||||
// Sessions section: Default model & agent, Session retention
|
||||
|
||||
@@ -23,6 +23,7 @@ import { isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { usePwaDetection } from '@/hooks/usePwaDetection';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import {
|
||||
setDirectoryShowHidden,
|
||||
useDirectoryShowHidden,
|
||||
@@ -126,6 +127,24 @@ const CHAT_RENDER_MODE_OPTIONS: Option<'sorted' | 'live'>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const MESSAGE_STREAM_TRANSPORT_OPTIONS: Option<'auto' | 'ws' | 'sse'>[] = [
|
||||
{
|
||||
id: 'auto',
|
||||
label: 'Auto',
|
||||
description: 'Prefer WebSocket and fall back to SSE if needed.',
|
||||
},
|
||||
{
|
||||
id: 'ws',
|
||||
label: 'WebSocket',
|
||||
description: 'Use WebSocket for message streaming.',
|
||||
},
|
||||
{
|
||||
id: 'sse',
|
||||
label: 'SSE',
|
||||
description: 'Use Server-Sent Events for message streaming.',
|
||||
},
|
||||
];
|
||||
|
||||
const ACTIVITY_RENDER_MODE_OPTIONS: Option<'collapsed' | 'summary'>[] = [
|
||||
{
|
||||
id: 'collapsed',
|
||||
@@ -177,7 +196,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
|
||||
return mode === 'markdown' ? 'markdown' : 'plain';
|
||||
};
|
||||
|
||||
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'activityRenderMode' | 'stickyUserHeader' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
|
||||
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
|
||||
|
||||
interface OpenChamberVisualSettingsProps {
|
||||
/** Which settings to show. If undefined, shows all. */
|
||||
@@ -233,6 +252,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const setWeekStartPreference = useUIStore(state => state.setWeekStartPreference);
|
||||
const showMobileSessionStatusBar = useUIStore(state => state.showMobileSessionStatusBar);
|
||||
const setShowMobileSessionStatusBar = useUIStore(state => state.setShowMobileSessionStatusBar);
|
||||
const messageStreamTransport = useConfigStore((state) => state.settingsMessageStreamTransport);
|
||||
const setMessageStreamTransport = useConfigStore((state) => state.setSettingsMessageStreamTransport);
|
||||
const isSettingsDialogOpen = useUIStore(state => state.isSettingsDialogOpen);
|
||||
const {
|
||||
themeMode,
|
||||
@@ -323,6 +344,11 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
void updateDesktopSettings({ chatRenderMode: mode });
|
||||
}, [setChatRenderMode]);
|
||||
|
||||
const handleMessageStreamTransportChange = React.useCallback((mode: 'auto' | 'ws' | 'sse') => {
|
||||
setMessageStreamTransport(mode);
|
||||
void updateDesktopSettings({ messageStreamTransport: mode });
|
||||
}, [setMessageStreamTransport]);
|
||||
|
||||
const handleActivityRenderModeChange = React.useCallback((mode: 'collapsed' | 'summary') => {
|
||||
setActivityRenderMode(mode);
|
||||
void updateDesktopSettings({ activityRenderMode: mode });
|
||||
@@ -399,6 +425,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const hasBehaviorSettings = shouldShow('mermaidRendering')
|
||||
|| shouldShow('userMessageRendering')
|
||||
|| shouldShow('chatRenderMode')
|
||||
|| shouldShow('messageTransport')
|
||||
|| (shouldShow('activityRenderMode') && chatRenderMode === 'sorted')
|
||||
|| shouldShow('stickyUserHeader')
|
||||
|| shouldShow('diffLayout')
|
||||
@@ -848,7 +875,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
|
||||
|
||||
|
||||
{(shouldShow('userMessageRendering') || shouldShow('mermaidRendering') || shouldShow('chatRenderMode') || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || (shouldShow('diffLayout') && !isVSCode)) && (
|
||||
{(shouldShow('userMessageRendering') || shouldShow('mermaidRendering') || shouldShow('chatRenderMode') || shouldShow('messageTransport') || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || (shouldShow('diffLayout') && !isVSCode)) && (
|
||||
<div className="grid grid-cols-1 gap-y-2 md:grid-cols-[minmax(0,16rem)_minmax(0,16rem)] md:justify-start md:gap-x-2">
|
||||
{shouldShow('chatRenderMode') && (
|
||||
<section className="p-2 md:col-span-2">
|
||||
@@ -934,6 +961,35 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{shouldShow('messageTransport') && (
|
||||
<section className="p-2 md:col-span-2">
|
||||
<h4 className="typography-ui-header font-medium text-foreground">Message Stream Transport</h4>
|
||||
<div className="mt-1 flex max-w-[24rem] flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{MESSAGE_STREAM_TRANSPORT_OPTIONS.map((option) => (
|
||||
<Button
|
||||
key={option.id}
|
||||
variant="outline"
|
||||
size="xs"
|
||||
className={cn(
|
||||
'!font-normal',
|
||||
messageStreamTransport === option.id
|
||||
? 'border-[var(--primary-base)] text-[var(--primary-base)] bg-[var(--primary-base)]/10 hover:text-[var(--primary-base)]'
|
||||
: 'text-foreground'
|
||||
)}
|
||||
onClick={() => handleMessageStreamTransportChange(option.id)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<span className="typography-meta text-muted-foreground">
|
||||
{MESSAGE_STREAM_TRANSPORT_OPTIONS.find((option) => option.id === messageStreamTransport)?.description}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{shouldShow('activityRenderMode') && chatRenderMode === 'sorted' && (
|
||||
<section className="p-2 md:col-span-2">
|
||||
<h4 className="typography-ui-header font-medium text-foreground">Activity Default</h4>
|
||||
|
||||
Reference in New Issue
Block a user