Skip to content

Commit de3dd04

Browse files
author
catlog22
committed
feat: Add indexing group to CodexLens environment variable schema
- Introduced a new `indexing` group in the environment variable schema with fields for AST grep usage, static graph enablement, and relationship types. - Updated the CodexLens configuration to support new indexing features. feat: Enhance DashboardToolbar with session and fullscreen controls - Added props for session sidebar visibility and fullscreen mode to the DashboardToolbar component. - Implemented handlers for toggling session sidebar and fullscreen mode. - Updated the toolbar layout to include session sidebar toggle and fullscreen button. refactor: Improve TerminalGrid and TerminalPane components - Refactored GridGroupRenderer to handle pane size changes directly via store. - Enhanced TerminalPane to remove unused file browser logic and improve layout handling. - Updated key generation for child panes to ensure stability. feat: Extend CodexLens API for staged Stage-2 expansion modes - Added support for `staged_stage2_mode` in the CodexLens API, allowing for different expansion strategies. - Updated semantic search handlers to process new stage-2 mode parameter. - Implemented validation and handling for new stage-2 modes in the backend. test: Add benchmarks for staged Stage-2 modes comparison - Created a benchmark script to compare performance and results of different staged Stage-2 modes. - Included metrics for latency, overlap, and diversity across modes.
1 parent 2202c2c commit de3dd04

13 files changed

Lines changed: 676 additions & 128 deletions

File tree

ccw/frontend/src/components/codexlens/SearchTab.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import {
2424
useCodexLensLspStatus,
2525
useCodexLensSemanticSearch,
2626
} from '@/hooks/useCodexLens';
27-
import type { CodexLensSearchParams, CodexLensSemanticSearchMode, CodexLensFusionStrategy } from '@/lib/api';
27+
import type {
28+
CodexLensSearchParams,
29+
CodexLensSemanticSearchMode,
30+
CodexLensFusionStrategy,
31+
CodexLensStagedStage2Mode,
32+
} from '@/lib/api';
2833
import { cn } from '@/lib/utils';
2934

3035
type SearchType = 'search' | 'search_files' | 'symbol' | 'semantic';
@@ -40,6 +45,7 @@ export function SearchTab({ enabled }: SearchTabProps) {
4045
const [searchMode, setSearchMode] = useState<SearchMode>('dense_rerank');
4146
const [semanticMode, setSemanticMode] = useState<CodexLensSemanticSearchMode>('fusion');
4247
const [fusionStrategy, setFusionStrategy] = useState<CodexLensFusionStrategy>('rrf');
48+
const [stagedStage2Mode, setStagedStage2Mode] = useState<CodexLensStagedStage2Mode>('precomputed');
4349
const [query, setQuery] = useState('');
4450
const [hasSearched, setHasSearched] = useState(false);
4551

@@ -76,6 +82,7 @@ export function SearchTab({ enabled }: SearchTabProps) {
7682
query,
7783
mode: semanticMode,
7884
fusion_strategy: semanticMode === 'fusion' ? fusionStrategy : undefined,
85+
staged_stage2_mode: semanticMode === 'fusion' && fusionStrategy === 'staged' ? stagedStage2Mode : undefined,
7986
limit: 20,
8087
include_match_reason: true,
8188
},
@@ -123,6 +130,11 @@ export function SearchTab({ enabled }: SearchTabProps) {
123130
setHasSearched(false);
124131
};
125132

133+
const handleStagedStage2ModeChange = (value: CodexLensStagedStage2Mode) => {
134+
setStagedStage2Mode(value);
135+
setHasSearched(false);
136+
};
137+
126138
const handleQueryChange = (value: string) => {
127139
setQuery(value);
128140
setHasSearched(false);
@@ -308,6 +320,29 @@ export function SearchTab({ enabled }: SearchTabProps) {
308320
</div>
309321
)}
310322

323+
{/* Staged Stage-2 Mode - only when semantic + fusion + staged */}
324+
{searchType === 'semantic' && semanticMode === 'fusion' && fusionStrategy === 'staged' && (
325+
<div className="space-y-2">
326+
<Label>{formatMessage({ id: 'codexlens.search.stagedStage2Mode' })}</Label>
327+
<Select value={stagedStage2Mode} onValueChange={handleStagedStage2ModeChange}>
328+
<SelectTrigger>
329+
<SelectValue />
330+
</SelectTrigger>
331+
<SelectContent>
332+
<SelectItem value="precomputed">
333+
{formatMessage({ id: 'codexlens.search.stagedStage2Mode.precomputed' })}
334+
</SelectItem>
335+
<SelectItem value="realtime">
336+
{formatMessage({ id: 'codexlens.search.stagedStage2Mode.realtime' })}
337+
</SelectItem>
338+
<SelectItem value="static_global_graph">
339+
{formatMessage({ id: 'codexlens.search.stagedStage2Mode.static_global_graph' })}
340+
</SelectItem>
341+
</SelectContent>
342+
</Select>
343+
</div>
344+
)}
345+
311346
{/* Query Input */}
312347
<div className="space-y-2">
313348
<Label htmlFor="search-query">{formatMessage({ id: 'codexlens.search.query' })}</Label>

ccw/frontend/src/components/codexlens/envVarSchema.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// CodexLens Environment Variable Schema
33
// ========================================
44
// TypeScript port of ENV_VAR_GROUPS from codexlens-manager.js
5-
// Defines the 5 structured groups: embedding, reranker, concurrency, cascade, chunking
5+
// Defines structured groups for CodexLens configuration
66

77
import type { EnvVarGroupsSchema } from '@/types/codexlens';
88

@@ -306,6 +306,36 @@ export const envVarGroupsSchema: EnvVarGroupsSchema = {
306306
},
307307
},
308308
},
309+
indexing: {
310+
id: 'indexing',
311+
labelKey: 'codexlens.envGroup.indexing',
312+
icon: 'git-branch',
313+
vars: {
314+
CODEXLENS_USE_ASTGREP: {
315+
key: 'CODEXLENS_USE_ASTGREP',
316+
labelKey: 'codexlens.envField.useAstGrep',
317+
type: 'checkbox',
318+
default: 'false',
319+
settingsPath: 'parsing.use_astgrep',
320+
},
321+
CODEXLENS_STATIC_GRAPH_ENABLED: {
322+
key: 'CODEXLENS_STATIC_GRAPH_ENABLED',
323+
labelKey: 'codexlens.envField.staticGraphEnabled',
324+
type: 'checkbox',
325+
default: 'false',
326+
settingsPath: 'indexing.static_graph_enabled',
327+
},
328+
CODEXLENS_STATIC_GRAPH_RELATIONSHIP_TYPES: {
329+
key: 'CODEXLENS_STATIC_GRAPH_RELATIONSHIP_TYPES',
330+
labelKey: 'codexlens.envField.staticGraphRelationshipTypes',
331+
type: 'text',
332+
placeholder: 'imports,inherits,calls',
333+
default: 'imports,inherits',
334+
settingsPath: 'indexing.static_graph_relationship_types',
335+
showWhen: (env) => env['CODEXLENS_STATIC_GRAPH_ENABLED'] === 'true',
336+
},
337+
},
338+
},
309339
chunking: {
310340
id: 'chunking',
311341
labelKey: 'codexlens.envGroup.chunking',

ccw/frontend/src/components/terminal-dashboard/DashboardToolbar.tsx

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import {
2121
Zap,
2222
Settings,
2323
Loader2,
24+
Folder,
25+
Maximize2,
26+
Minimize2,
2427
} from 'lucide-react';
2528
import { cn } from '@/lib/utils';
2629
import { Badge } from '@/components/ui/Badge';
@@ -57,6 +60,14 @@ interface DashboardToolbarProps {
5760
isFileSidebarOpen?: boolean;
5861
/** Callback to toggle file sidebar */
5962
onToggleFileSidebar?: () => void;
63+
/** Whether the session sidebar is open */
64+
isSessionSidebarOpen?: boolean;
65+
/** Callback to toggle session sidebar */
66+
onToggleSessionSidebar?: () => void;
67+
/** Whether fullscreen mode is active */
68+
isFullscreen?: boolean;
69+
/** Callback to toggle fullscreen mode */
70+
onToggleFullscreen?: () => void;
6071
}
6172

6273
// ========== Layout Presets ==========
@@ -83,7 +94,7 @@ const LAUNCH_COMMANDS: Record<CliTool, Record<LaunchMode, string>> = {
8394

8495
// ========== Component ==========
8596

86-
export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen, onToggleFileSidebar }: DashboardToolbarProps) {
97+
export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen, onToggleFileSidebar, isSessionSidebarOpen, onToggleSessionSidebar, isFullscreen, onToggleFullscreen }: DashboardToolbarProps) {
8798
const { formatMessage } = useIntl();
8899

89100
// Issues count
@@ -117,17 +128,30 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
117128
// Launch CLI handlers
118129
const projectPath = useWorkflowStore(selectProjectPath);
119130
const focusedPaneId = useTerminalGridStore(selectTerminalGridFocusedPaneId);
131+
const panes = useTerminalGridStore((s) => s.panes);
120132
const createSessionAndAssign = useTerminalGridStore((s) => s.createSessionAndAssign);
121133
const [isCreating, setIsCreating] = useState(false);
122134
const [selectedTool, setSelectedTool] = useState<CliTool>('gemini');
123135
const [launchMode, setLaunchMode] = useState<LaunchMode>('yolo');
124136
const [isConfigOpen, setIsConfigOpen] = useState(false);
125137

138+
// Helper to get or create a focused pane
139+
const getOrCreateFocusedPane = useCallback(() => {
140+
if (focusedPaneId) return focusedPaneId;
141+
// No focused pane - reset layout to create a single pane
142+
resetLayout('single');
143+
// Get the new focused pane id from store
144+
return useTerminalGridStore.getState().focusedPaneId;
145+
}, [focusedPaneId]);
146+
126147
const handleQuickCreate = useCallback(async () => {
127-
if (!focusedPaneId || !projectPath) return;
148+
if (!projectPath) return;
128149
setIsCreating(true);
129150
try {
130-
const created = await createSessionAndAssign(focusedPaneId, {
151+
const targetPaneId = getOrCreateFocusedPane();
152+
if (!targetPaneId) return;
153+
154+
const created = await createSessionAndAssign(targetPaneId, {
131155
workingDir: projectPath,
132156
preferredShell: 'bash',
133157
tool: selectedTool,
@@ -146,18 +170,21 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
146170
} finally {
147171
setIsCreating(false);
148172
}
149-
}, [focusedPaneId, projectPath, createSessionAndAssign, selectedTool, launchMode]);
173+
}, [projectPath, createSessionAndAssign, selectedTool, launchMode, getOrCreateFocusedPane]);
150174

151175
const handleConfigure = useCallback(() => {
152176
setIsConfigOpen(true);
153177
}, []);
154178

155179
const handleCreateConfiguredSession = useCallback(async (config: CliSessionConfig) => {
156-
if (!focusedPaneId || !projectPath) throw new Error('No focused pane or project path');
180+
if (!projectPath) throw new Error('No project path');
157181
setIsCreating(true);
158182
try {
183+
const targetPaneId = getOrCreateFocusedPane();
184+
if (!targetPaneId) throw new Error('Failed to create pane');
185+
159186
const created = await createSessionAndAssign(
160-
focusedPaneId,
187+
targetPaneId,
161188
{
162189
workingDir: config.workingDir || projectPath,
163190
preferredShell: config.preferredShell,
@@ -182,7 +209,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
182209
} finally {
183210
setIsCreating(false);
184211
}
185-
}, [focusedPaneId, projectPath, createSessionAndAssign]);
212+
}, [projectPath, createSessionAndAssign, getOrCreateFocusedPane]);
186213

187214
return (
188215
<>
@@ -254,7 +281,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
254281
<DropdownMenuSeparator />
255282
<DropdownMenuItem
256283
onClick={handleQuickCreate}
257-
disabled={isCreating || !projectPath || !focusedPaneId}
284+
disabled={isCreating || !projectPath}
258285
className="gap-2"
259286
>
260287
<Zap className="w-4 h-4" />
@@ -263,7 +290,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
263290
<DropdownMenuSeparator />
264291
<DropdownMenuItem
265292
onClick={handleConfigure}
266-
disabled={isCreating || !projectPath || !focusedPaneId}
293+
disabled={isCreating || !projectPath}
267294
className="gap-2"
268295
>
269296
<Settings className="w-4 h-4" />
@@ -275,6 +302,17 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
275302
{/* Separator */}
276303
<div className="w-px h-5 bg-border mx-1" />
277304

305+
{/* Session sidebar toggle */}
306+
<ToolbarButton
307+
icon={Folder}
308+
label={formatMessage({ id: 'terminalDashboard.toolbar.sessions', defaultMessage: 'Sessions' })}
309+
isActive={isSessionSidebarOpen ?? true}
310+
onClick={() => onToggleSessionSidebar?.()}
311+
/>
312+
313+
{/* Separator */}
314+
<div className="w-px h-5 bg-border mx-1" />
315+
278316
{/* Panel toggle buttons */}
279317
<ToolbarButton
280318
icon={AlertCircle}
@@ -322,6 +360,30 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
322360
</button>
323361
))}
324362

363+
{/* Separator */}
364+
<div className="w-px h-5 bg-border mx-1" />
365+
366+
{/* Fullscreen toggle */}
367+
<button
368+
onClick={onToggleFullscreen}
369+
className={cn(
370+
'p-1.5 rounded transition-colors',
371+
isFullscreen
372+
? 'bg-primary/10 text-primary'
373+
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
374+
)}
375+
title={isFullscreen
376+
? formatMessage({ id: 'terminalDashboard.toolbar.exitFullscreen', defaultMessage: 'Exit Fullscreen' })
377+
: formatMessage({ id: 'terminalDashboard.toolbar.fullscreen', defaultMessage: 'Fullscreen' })
378+
}
379+
>
380+
{isFullscreen ? (
381+
<Minimize2 className="w-3.5 h-3.5" />
382+
) : (
383+
<Maximize2 className="w-3.5 h-3.5" />
384+
)}
385+
</button>
386+
325387
{/* Right-aligned title */}
326388
<span className="ml-auto text-xs text-muted-foreground font-medium">
327389
{formatMessage({ id: 'terminalDashboard.page.title' })}

ccw/frontend/src/components/terminal-dashboard/TerminalGrid.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ import { TerminalPane } from './TerminalPane';
2323
interface GridGroupRendererProps {
2424
group: AllotmentLayoutGroup;
2525
minSize: number;
26-
onSizeChange: (sizes: number[]) => void;
26+
depth?: number;
2727
}
2828

2929
// ========== Recursive Group Renderer ==========
3030

31-
function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererProps) {
31+
function GridGroupRenderer({ group, minSize, depth = 0 }: GridGroupRendererProps) {
3232
const panes = useTerminalGridStore(selectTerminalGridPanes);
33+
const updateLayoutSizes = useTerminalGridStore((s) => s.updateLayoutSizes);
3334

3435
const handleChange = useCallback(
3536
(sizes: number[]) => {
36-
onSizeChange(sizes);
37+
updateLayoutSizes(sizes);
3738
},
38-
[onSizeChange]
39+
[updateLayoutSizes]
3940
);
4041

4142
const validChildren = useMemo(() => {
@@ -51,22 +52,27 @@ function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererPr
5152
return null;
5253
}
5354

55+
// Generate stable key based on children
56+
const groupKey = useMemo(() => {
57+
return validChildren.map(c => isPaneId(c) ? c : 'group').join('-');
58+
}, [validChildren]);
59+
5460
return (
5561
<Allotment
62+
key={groupKey}
5663
vertical={group.direction === 'vertical'}
5764
defaultSizes={group.sizes}
5865
onChange={handleChange}
59-
className="h-full"
6066
>
6167
{validChildren.map((child, index) => (
62-
<Allotment.Pane key={isPaneId(child) ? child : `group-${index}`} minSize={minSize}>
68+
<Allotment.Pane key={isPaneId(child) ? child : `group-${depth}-${index}`} minSize={minSize}>
6369
{isPaneId(child) ? (
6470
<TerminalPane paneId={child} />
6571
) : (
6672
<GridGroupRenderer
6773
group={child}
6874
minSize={minSize}
69-
onSizeChange={onSizeChange}
75+
depth={depth + 1}
7076
/>
7177
)}
7278
</Allotment.Pane>
@@ -80,14 +86,6 @@ function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererPr
8086
export function TerminalGrid({ className }: { className?: string }) {
8187
const layout = useTerminalGridStore(selectTerminalGridLayout);
8288
const panes = useTerminalGridStore(selectTerminalGridPanes);
83-
const updateLayoutSizes = useTerminalGridStore((s) => s.updateLayoutSizes);
84-
85-
const handleSizeChange = useCallback(
86-
(sizes: number[]) => {
87-
updateLayoutSizes(sizes);
88-
},
89-
[updateLayoutSizes]
90-
);
9189

9290
const content = useMemo(() => {
9391
if (!layout.children || layout.children.length === 0) {
@@ -105,10 +103,10 @@ export function TerminalGrid({ className }: { className?: string }) {
105103
<GridGroupRenderer
106104
group={layout}
107105
minSize={150}
108-
onSizeChange={handleSizeChange}
106+
depth={0}
109107
/>
110108
);
111-
}, [layout, panes, handleSizeChange]);
109+
}, [layout, panes]);
112110

113111
return (
114112
<div className={cn('h-full w-full overflow-hidden bg-background', className)}>

0 commit comments

Comments
 (0)