Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/web-ui/src/app/scenes/SceneViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import { useDialogCompletionNotify } from '../hooks/useDialogCompletionNotify';
import { ProcessingIndicator } from '@/flow_chat/components/modern/ProcessingIndicator';
import SettingsScene from './settings/SettingsScene';
import AssistantScene from './assistant/AssistantScene';
import SessionScene from './session/SessionScene';
import './SceneViewport.scss';

const SessionScene = lazy(() => import('./session/SessionScene'));
// Session is the primary interaction path. Keep it in the main scene bundle so
// first open does not stall on a lazy chunk fetch/parse before FlowChat mounts.
const TerminalScene = lazy(() => import('./terminal/TerminalScene'));
const GitScene = lazy(() => import('./git/GitScene'));
const FileViewerScene = lazy(() => import('./file-viewer/FileViewerScene'));
Expand Down
9 changes: 6 additions & 3 deletions src/web-ui/src/flow_chat/services/openBtwSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useAgentCanvasStore } from '@/app/components/panels/content-canvas/stor
import type { CanvasTab } from '@/app/components/panels/content-canvas/types';
import { flowChatStore } from '../store/FlowChatStore';
import { flowChatManager } from './FlowChatManager';
import { syncSessionToModernStore } from './storeSync';

export const BTW_SESSION_PANEL_TYPE = 'btw-session' as const;

Expand Down Expand Up @@ -89,7 +90,6 @@ export async function openMainSession(
activateWorkspace?: (workspaceId: string) => void | Promise<unknown>;
}
): Promise<void> {
useSceneStore.getState().openScene('session');
appManager.updateLayout({
leftPanelActiveTab: 'sessions',
leftPanelCollapsed: false,
Expand All @@ -100,10 +100,13 @@ export async function openMainSession(
}

if (flowChatStore.getState().activeSessionId === sessionId) {
return;
syncSessionToModernStore(sessionId);
} else {
await flowChatManager.switchChatSession(sessionId);
syncSessionToModernStore(sessionId);
}

await flowChatManager.switchChatSession(sessionId);
useSceneStore.getState().openScene('session');
}

export function openBtwSessionInAuxPane(params: {
Expand Down
20 changes: 18 additions & 2 deletions src/web-ui/src/flow_chat/services/storeSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import { createLogger } from '@/shared/utils/logger';

const log = createLogger('StoreSync');

function isSessionAlreadySynced(
sessionId: string,
session: object,
modernStore: ReturnType<typeof useModernFlowChatStore.getState>
): boolean {
return (
modernStore.activeSession?.sessionId === sessionId &&
modernStore.activeSession === session
);
}

/**
* Sync session data to new Store
*/
Expand All @@ -23,6 +34,9 @@ export function syncSessionToModernStore(sessionId: string): void {
}

const modernStore = useModernFlowChatStore.getState();
if (isSessionAlreadySynced(sessionId, session, modernStore)) {
return;
}
modernStore.setActiveSession(session);
}

Expand Down Expand Up @@ -61,9 +75,11 @@ export function startAutoSync(): () => void {
lastSyncedSessionId = currentState.activeSessionId;
lastSyncedSession = session;
const modernStore = useModernFlowChatStore.getState();
modernStore.setActiveSession(session);
if (!isSessionAlreadySynced(currentState.activeSessionId, session, modernStore)) {
modernStore.setActiveSession(session);
}
}
}

return unsubscribe;
}
}
23 changes: 19 additions & 4 deletions src/web-ui/src/flow_chat/store/modernFlowChatStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useShallow } from 'zustand/react/shallow';
import { immer } from 'zustand/middleware/immer';
import type { Session, DialogTurn, ModelRound, FlowItem, FlowToolItem } from '../types/flow-chat';
import { isCollapsibleTool, READ_TOOL_NAMES, SEARCH_TOOL_NAMES } from '../tool-cards';
import { flowChatStore } from './FlowChatStore';

/**
* Explore group statistics (merged computed stats)
Expand Down Expand Up @@ -272,11 +273,25 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] {
return items;
}

function getInitialModernState(): Pick<
ModernFlowChatState,
'activeSession' | 'virtualItems' | 'visibleTurnInfo'
> {
const legacyState = flowChatStore.getState();
const activeSession = legacyState.activeSessionId
? legacyState.sessions.get(legacyState.activeSessionId) ?? null
: null;

return {
activeSession,
virtualItems: sessionToVirtualItems(activeSession),
visibleTurnInfo: null,
};
}

export const useModernFlowChatStore = create<ModernFlowChatState>()(
immer((set, get) => ({
activeSession: null,
virtualItems: [],
visibleTurnInfo: null,
...getInitialModernState(),

setActiveSession: (session) => {
const items = sessionToVirtualItems(session);
Expand Down Expand Up @@ -333,4 +348,4 @@ export const useFlowChatActions = () =>
updateVirtualItems: state.updateVirtualItems,
setVisibleTurnInfo: state.setVisibleTurnInfo,
clear: state.clear,
})));
})));
18 changes: 18 additions & 0 deletions src/web-ui/src/flow_chat/tool-cards/BaseToolCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,24 @@
z-index: 0;
}

.tool-card-icon-affordance-hit {
position: absolute;
inset: 0;
z-index: 2;
margin: 0;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
appearance: none;

&:focus-visible {
outline: 2px solid var(--color-accent-500, #60a5fa);
outline-offset: 2px;
border-radius: 6px;
}
}

/* Same square box so tool glyph and chevron share center (no circular fill). */
.tool-card-icon-marks {
position: relative;
Expand Down
14 changes: 14 additions & 0 deletions src/web-ui/src/flow_chat/tool-cards/BaseToolCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ export interface ToolCardHeaderProps {
affordanceKind?: ToolCardHeaderAffordanceKind;
/** Override context: expanded state for chevron rotation */
headerExpanded?: boolean;
/** Optional dedicated affordance click handler for the left icon rail. */
onAffordanceClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
/** Action text */
action?: string;
/** Main content */
Expand All @@ -151,6 +153,7 @@ export const ToolCardHeader: React.FC<ToolCardHeaderProps> = ({
expandAffordance,
affordanceKind,
headerExpanded,
onAffordanceClick,
action,
content,
extra,
Expand Down Expand Up @@ -188,6 +191,17 @@ export const ToolCardHeader: React.FC<ToolCardHeaderProps> = ({
</span>
)}
</div>
{showExpandHint && onAffordanceClick && (
<button
type="button"
className="tool-card-icon-affordance-hit"
onClick={(e) => {
e.stopPropagation();
onAffordanceClick(e);
}}
aria-label={isPanelAffordance ? 'Open details' : 'Expand details'}
/>
)}
</div>
)}
{action && <span className="tool-card-action">{action}</span>}
Expand Down
Loading
Loading