From de3dd044b92154c298235e628d4576dabefe4f47 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Mon, 16 Feb 2026 12:12:38 +0800 Subject: [PATCH] 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. --- .../src/components/codexlens/SearchTab.tsx | 37 +- .../src/components/codexlens/envVarSchema.ts | 32 +- .../terminal-dashboard/DashboardToolbar.tsx | 80 +++- .../terminal-dashboard/TerminalGrid.tsx | 32 +- .../terminal-dashboard/TerminalPane.tsx | 65 +-- ccw/frontend/src/lib/api.ts | 2 + ccw/frontend/src/locales/en/codexlens.json | 8 + ccw/frontend/src/locales/zh/codexlens.json | 8 + .../src/pages/TerminalDashboardPage.tsx | 104 +++-- .../routes/codexlens/semantic-handlers.ts | 23 ++ .../benchmarks/compare_staged_stage2_modes.py | 391 ++++++++++++++++++ codex-lens/src/codexlens/api/semantic.py | 16 + codex-lens/tests/api/test_semantic_search.py | 2 + 13 files changed, 674 insertions(+), 126 deletions(-) create mode 100644 codex-lens/benchmarks/compare_staged_stage2_modes.py diff --git a/ccw/frontend/src/components/codexlens/SearchTab.tsx b/ccw/frontend/src/components/codexlens/SearchTab.tsx index 10e7e5dc..513878ff 100644 --- a/ccw/frontend/src/components/codexlens/SearchTab.tsx +++ b/ccw/frontend/src/components/codexlens/SearchTab.tsx @@ -24,7 +24,12 @@ import { useCodexLensLspStatus, useCodexLensSemanticSearch, } from '@/hooks/useCodexLens'; -import type { CodexLensSearchParams, CodexLensSemanticSearchMode, CodexLensFusionStrategy } from '@/lib/api'; +import type { + CodexLensSearchParams, + CodexLensSemanticSearchMode, + CodexLensFusionStrategy, + CodexLensStagedStage2Mode, +} from '@/lib/api'; import { cn } from '@/lib/utils'; type SearchType = 'search' | 'search_files' | 'symbol' | 'semantic'; @@ -40,6 +45,7 @@ export function SearchTab({ enabled }: SearchTabProps) { const [searchMode, setSearchMode] = useState('dense_rerank'); const [semanticMode, setSemanticMode] = useState('fusion'); const [fusionStrategy, setFusionStrategy] = useState('rrf'); + const [stagedStage2Mode, setStagedStage2Mode] = useState('precomputed'); const [query, setQuery] = useState(''); const [hasSearched, setHasSearched] = useState(false); @@ -76,6 +82,7 @@ export function SearchTab({ enabled }: SearchTabProps) { query, mode: semanticMode, fusion_strategy: semanticMode === 'fusion' ? fusionStrategy : undefined, + staged_stage2_mode: semanticMode === 'fusion' && fusionStrategy === 'staged' ? stagedStage2Mode : undefined, limit: 20, include_match_reason: true, }, @@ -123,6 +130,11 @@ export function SearchTab({ enabled }: SearchTabProps) { setHasSearched(false); }; + const handleStagedStage2ModeChange = (value: CodexLensStagedStage2Mode) => { + setStagedStage2Mode(value); + setHasSearched(false); + }; + const handleQueryChange = (value: string) => { setQuery(value); setHasSearched(false); @@ -308,6 +320,29 @@ export function SearchTab({ enabled }: SearchTabProps) { )} + {/* Staged Stage-2 Mode - only when semantic + fusion + staged */} + {searchType === 'semantic' && semanticMode === 'fusion' && fusionStrategy === 'staged' && ( +
+ + +
+ )} + {/* Query Input */}
diff --git a/ccw/frontend/src/components/codexlens/envVarSchema.ts b/ccw/frontend/src/components/codexlens/envVarSchema.ts index 4e1a7f17..3b1a3768 100644 --- a/ccw/frontend/src/components/codexlens/envVarSchema.ts +++ b/ccw/frontend/src/components/codexlens/envVarSchema.ts @@ -2,7 +2,7 @@ // CodexLens Environment Variable Schema // ======================================== // TypeScript port of ENV_VAR_GROUPS from codexlens-manager.js -// Defines the 5 structured groups: embedding, reranker, concurrency, cascade, chunking +// Defines structured groups for CodexLens configuration import type { EnvVarGroupsSchema } from '@/types/codexlens'; @@ -306,6 +306,36 @@ export const envVarGroupsSchema: EnvVarGroupsSchema = { }, }, }, + indexing: { + id: 'indexing', + labelKey: 'codexlens.envGroup.indexing', + icon: 'git-branch', + vars: { + CODEXLENS_USE_ASTGREP: { + key: 'CODEXLENS_USE_ASTGREP', + labelKey: 'codexlens.envField.useAstGrep', + type: 'checkbox', + default: 'false', + settingsPath: 'parsing.use_astgrep', + }, + CODEXLENS_STATIC_GRAPH_ENABLED: { + key: 'CODEXLENS_STATIC_GRAPH_ENABLED', + labelKey: 'codexlens.envField.staticGraphEnabled', + type: 'checkbox', + default: 'false', + settingsPath: 'indexing.static_graph_enabled', + }, + CODEXLENS_STATIC_GRAPH_RELATIONSHIP_TYPES: { + key: 'CODEXLENS_STATIC_GRAPH_RELATIONSHIP_TYPES', + labelKey: 'codexlens.envField.staticGraphRelationshipTypes', + type: 'text', + placeholder: 'imports,inherits,calls', + default: 'imports,inherits', + settingsPath: 'indexing.static_graph_relationship_types', + showWhen: (env) => env['CODEXLENS_STATIC_GRAPH_ENABLED'] === 'true', + }, + }, + }, chunking: { id: 'chunking', labelKey: 'codexlens.envGroup.chunking', diff --git a/ccw/frontend/src/components/terminal-dashboard/DashboardToolbar.tsx b/ccw/frontend/src/components/terminal-dashboard/DashboardToolbar.tsx index 392edb92..ac9444e6 100644 --- a/ccw/frontend/src/components/terminal-dashboard/DashboardToolbar.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/DashboardToolbar.tsx @@ -21,6 +21,9 @@ import { Zap, Settings, Loader2, + Folder, + Maximize2, + Minimize2, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Badge } from '@/components/ui/Badge'; @@ -57,6 +60,14 @@ interface DashboardToolbarProps { isFileSidebarOpen?: boolean; /** Callback to toggle file sidebar */ onToggleFileSidebar?: () => void; + /** Whether the session sidebar is open */ + isSessionSidebarOpen?: boolean; + /** Callback to toggle session sidebar */ + onToggleSessionSidebar?: () => void; + /** Whether fullscreen mode is active */ + isFullscreen?: boolean; + /** Callback to toggle fullscreen mode */ + onToggleFullscreen?: () => void; } // ========== Layout Presets ========== @@ -83,7 +94,7 @@ const LAUNCH_COMMANDS: Record> = { // ========== Component ========== -export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen, onToggleFileSidebar }: DashboardToolbarProps) { +export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen, onToggleFileSidebar, isSessionSidebarOpen, onToggleSessionSidebar, isFullscreen, onToggleFullscreen }: DashboardToolbarProps) { const { formatMessage } = useIntl(); // Issues count @@ -117,17 +128,30 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen // Launch CLI handlers const projectPath = useWorkflowStore(selectProjectPath); const focusedPaneId = useTerminalGridStore(selectTerminalGridFocusedPaneId); + const panes = useTerminalGridStore((s) => s.panes); const createSessionAndAssign = useTerminalGridStore((s) => s.createSessionAndAssign); const [isCreating, setIsCreating] = useState(false); const [selectedTool, setSelectedTool] = useState('gemini'); const [launchMode, setLaunchMode] = useState('yolo'); const [isConfigOpen, setIsConfigOpen] = useState(false); + // Helper to get or create a focused pane + const getOrCreateFocusedPane = useCallback(() => { + if (focusedPaneId) return focusedPaneId; + // No focused pane - reset layout to create a single pane + resetLayout('single'); + // Get the new focused pane id from store + return useTerminalGridStore.getState().focusedPaneId; + }, [focusedPaneId]); + const handleQuickCreate = useCallback(async () => { - if (!focusedPaneId || !projectPath) return; + if (!projectPath) return; setIsCreating(true); try { - const created = await createSessionAndAssign(focusedPaneId, { + const targetPaneId = getOrCreateFocusedPane(); + if (!targetPaneId) return; + + const created = await createSessionAndAssign(targetPaneId, { workingDir: projectPath, preferredShell: 'bash', tool: selectedTool, @@ -146,18 +170,21 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen } finally { setIsCreating(false); } - }, [focusedPaneId, projectPath, createSessionAndAssign, selectedTool, launchMode]); + }, [projectPath, createSessionAndAssign, selectedTool, launchMode, getOrCreateFocusedPane]); const handleConfigure = useCallback(() => { setIsConfigOpen(true); }, []); const handleCreateConfiguredSession = useCallback(async (config: CliSessionConfig) => { - if (!focusedPaneId || !projectPath) throw new Error('No focused pane or project path'); + if (!projectPath) throw new Error('No project path'); setIsCreating(true); try { + const targetPaneId = getOrCreateFocusedPane(); + if (!targetPaneId) throw new Error('Failed to create pane'); + const created = await createSessionAndAssign( - focusedPaneId, + targetPaneId, { workingDir: config.workingDir || projectPath, preferredShell: config.preferredShell, @@ -182,7 +209,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen } finally { setIsCreating(false); } - }, [focusedPaneId, projectPath, createSessionAndAssign]); + }, [projectPath, createSessionAndAssign, getOrCreateFocusedPane]); return ( <> @@ -254,7 +281,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen @@ -263,7 +290,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen @@ -275,6 +302,17 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen {/* Separator */}
+ {/* Session sidebar toggle */} + onToggleSessionSidebar?.()} + /> + + {/* Separator */} +
+ {/* Panel toggle buttons */} ))} + {/* Separator */} +
+ + {/* Fullscreen toggle */} + + {/* Right-aligned title */} {formatMessage({ id: 'terminalDashboard.page.title' })} diff --git a/ccw/frontend/src/components/terminal-dashboard/TerminalGrid.tsx b/ccw/frontend/src/components/terminal-dashboard/TerminalGrid.tsx index 1af06324..1e80c8d5 100644 --- a/ccw/frontend/src/components/terminal-dashboard/TerminalGrid.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/TerminalGrid.tsx @@ -23,19 +23,20 @@ import { TerminalPane } from './TerminalPane'; interface GridGroupRendererProps { group: AllotmentLayoutGroup; minSize: number; - onSizeChange: (sizes: number[]) => void; + depth?: number; } // ========== Recursive Group Renderer ========== -function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererProps) { +function GridGroupRenderer({ group, minSize, depth = 0 }: GridGroupRendererProps) { const panes = useTerminalGridStore(selectTerminalGridPanes); + const updateLayoutSizes = useTerminalGridStore((s) => s.updateLayoutSizes); const handleChange = useCallback( (sizes: number[]) => { - onSizeChange(sizes); + updateLayoutSizes(sizes); }, - [onSizeChange] + [updateLayoutSizes] ); const validChildren = useMemo(() => { @@ -51,22 +52,27 @@ function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererPr return null; } + // Generate stable key based on children + const groupKey = useMemo(() => { + return validChildren.map(c => isPaneId(c) ? c : 'group').join('-'); + }, [validChildren]); + return ( {validChildren.map((child, index) => ( - + {isPaneId(child) ? ( ) : ( )} @@ -80,14 +86,6 @@ function GridGroupRenderer({ group, minSize, onSizeChange }: GridGroupRendererPr export function TerminalGrid({ className }: { className?: string }) { const layout = useTerminalGridStore(selectTerminalGridLayout); const panes = useTerminalGridStore(selectTerminalGridPanes); - const updateLayoutSizes = useTerminalGridStore((s) => s.updateLayoutSizes); - - const handleSizeChange = useCallback( - (sizes: number[]) => { - updateLayoutSizes(sizes); - }, - [updateLayoutSizes] - ); const content = useMemo(() => { if (!layout.children || layout.children.length === 0) { @@ -105,10 +103,10 @@ export function TerminalGrid({ className }: { className?: string }) { ); - }, [layout, panes, handleSizeChange]); + }, [layout, panes]); return (
diff --git a/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx b/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx index c787f45b..db483500 100644 --- a/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx @@ -4,13 +4,13 @@ // Single terminal pane = PaneToolbar + content area. // Content can be terminal output or file preview based on displayMode. // Renders within the TerminalGrid recursive layout. +// File preview is triggered from right sidebar FileSidebarPanel. import { useCallback, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; import { SplitSquareHorizontal, SplitSquareVertical, - FolderOpen, Eraser, AlertTriangle, X, @@ -25,7 +25,6 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { TerminalInstance } from './TerminalInstance'; -import { FloatingFileBrowser } from './FloatingFileBrowser'; import { FilePreview } from '@/components/shared/FilePreview'; import { useTerminalGridStore, @@ -43,7 +42,6 @@ import { } from '@/stores/issueQueueIntegrationStore'; import { useCliSessionStore } from '@/stores/cliSessionStore'; import { getAllPaneIds } from '@/lib/layout-utils'; -import { sendCliSessionText } from '@/lib/api'; import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; import { useFileContent } from '@/hooks/useFileExplorer'; import type { PaneId } from '@/stores/viewerStore'; @@ -89,8 +87,6 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { const isFileMode = displayMode === 'file' && filePath; const projectPath = useWorkflowStore(selectProjectPath); - const [isFileBrowserOpen, setIsFileBrowserOpen] = useState(false); - const [initialFileBrowserPath, setInitialFileBrowserPath] = useState(null); // Session data const groups = useSessionManagerStore(selectGroups); @@ -168,25 +164,6 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { } }, [paneId, sessionId, assignSession]); - const handleOpenFileBrowser = useCallback(() => { - setInitialFileBrowserPath(null); - setIsFileBrowserOpen(true); - }, []); - - const handleRevealPath = useCallback((path: string) => { - setInitialFileBrowserPath(path); - setIsFileBrowserOpen(true); - }, []); - - const handleInsertPath = useCallback((path: string) => { - if (!sessionId) return; - sendCliSessionText( - sessionId, - { text: path, appendNewline: false }, - projectPath ?? undefined - ).catch((err) => console.error('[TerminalPane] insert path failed:', err)); - }, [sessionId, projectPath]); - const handleRestart = useCallback(async () => { if (!sessionId || isRestarting) return; setIsRestarting(true); @@ -229,9 +206,9 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { onClick={handleFocus} > {/* PaneToolbar */} -
+
{/* Left: Session selector + status (or file path in file mode) */} -
+
{isFileMode ? ( // File mode header <> @@ -255,13 +232,13 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { className={cn('w-2 h-2 rounded-full shrink-0', statusDotStyles[status])} /> )} -
+