Add tests and implement functionality for staged cascade search and LSP expansion

- Introduced a new JSON file for verbose output of the Codex Lens search results.
- Added unit tests for binary search functionality in `test_stage1_binary_search_uses_chunk_lines.py`.
- Implemented regression tests for staged cascade Stage 2 expansion depth in `test_staged_cascade_lsp_depth.py`.
- Created unit tests for staged cascade Stage 2 realtime LSP graph expansion in `test_staged_cascade_realtime_lsp.py`.
- Enhanced the ChainSearchEngine to respect configuration settings for staged LSP depth and improve search accuracy.
This commit is contained in:
catlog22
2026-02-08 21:54:42 +08:00
parent 166211dcd4
commit b9b2932f50
20 changed files with 1882 additions and 283 deletions

View File

@@ -35,13 +35,6 @@ import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Badge } from '@/components/ui/Badge';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/Dialog';
import { ThemeSelector } from '@/components/shared/ThemeSelector';
import { useTheme } from '@/hooks';
import { toast } from 'sonner';
@@ -63,146 +56,43 @@ import {
useUpgradeCcwInstallation,
} from '@/hooks/useSystemSettings';
// ========== File Path Input with Browse Dialog ==========
interface BrowseItem {
name: string;
path: string;
isDirectory: boolean;
isFile: boolean;
}
// ========== File Path Input with Native File Picker ==========
interface FilePathInputProps {
value: string;
onChange: (value: string) => void;
placeholder: string;
showHidden?: boolean;
}
function FilePathInput({ value, onChange, placeholder, showHidden = true }: FilePathInputProps) {
const [dialogOpen, setDialogOpen] = useState(false);
const [browseItems, setBrowseItems] = useState<BrowseItem[]>([]);
const [currentBrowsePath, setCurrentBrowsePath] = useState('');
const [parentPath, setParentPath] = useState('');
const [loading, setLoading] = useState(false);
const browseDirectory = async (dirPath?: string) => {
setLoading(true);
try {
const res = await fetch('/api/dialog/browse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: dirPath || '~', showHidden }),
});
if (!res.ok) return;
const data = await res.json();
setBrowseItems(data.items || []);
setCurrentBrowsePath(data.currentPath || '');
setParentPath(data.parentPath || '');
} catch {
// silently fail
} finally {
setLoading(false);
function FilePathInput({ value, onChange, placeholder }: FilePathInputProps) {
const handleBrowse = async () => {
const { selectFile } = await import('@/lib/nativeDialog');
const initialDir = value ? value.replace(/[/\\][^/\\]*$/, '') : undefined;
const selected = await selectFile(initialDir);
if (selected) {
onChange(selected);
}
};
const handleOpen = () => {
setDialogOpen(true);
// If value is set, browse its parent directory; otherwise browse home
const startPath = value ? value.replace(/[/\\][^/\\]*$/, '') : undefined;
browseDirectory(startPath);
};
const handleSelectFile = (filePath: string) => {
onChange(filePath);
setDialogOpen(false);
};
return (
<>
<div className="flex gap-2">
<Input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="flex-1"
/>
<Button
type="button"
variant="outline"
size="sm"
className="shrink-0 h-9"
onClick={handleOpen}
title="Browse"
>
<FolderOpen className="w-4 h-4" />
</Button>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderOpen className="w-5 h-5" />
Browse Files
</DialogTitle>
<DialogDescription className="font-mono text-xs truncate" title={currentBrowsePath}>
{currentBrowsePath}
</DialogDescription>
</DialogHeader>
<div className="border border-border rounded-lg overflow-hidden">
{loading ? (
<div className="flex items-center justify-center py-8">
<RefreshCw className="w-4 h-4 animate-spin text-muted-foreground" />
</div>
) : (
<div className="overflow-y-auto max-h-[350px]">
{/* Parent directory */}
{parentPath && parentPath !== currentBrowsePath && (
<button
type="button"
className="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-muted/50 transition-colors text-left border-b border-border"
onClick={() => browseDirectory(parentPath)}
>
<Folder className="w-4 h-4 text-primary shrink-0" />
<span className="text-muted-foreground font-medium">..</span>
</button>
)}
{browseItems.map((item) => (
<button
key={item.path}
type="button"
className="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-muted/50 transition-colors text-left"
onClick={() => {
if (item.isDirectory) {
browseDirectory(item.path);
} else {
handleSelectFile(item.path);
}
}}
>
{item.isDirectory ? (
<Folder className="w-4 h-4 text-primary shrink-0" />
) : (
<File className="w-4 h-4 text-muted-foreground shrink-0" />
)}
<span className={cn('truncate', item.isFile && 'text-foreground font-medium')}>
{item.name}
</span>
</button>
))}
{browseItems.length === 0 && (
<div className="px-3 py-8 text-sm text-muted-foreground text-center">
Empty directory
</div>
)}
</div>
)}
</div>
</DialogContent>
</Dialog>
</>
<div className="flex gap-2">
<Input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="flex-1"
/>
<Button
type="button"
variant="outline"
size="sm"
className="shrink-0 h-9"
onClick={handleBrowse}
title="Browse"
>
<FolderOpen className="w-4 h-4" />
</Button>
</div>
);
}