mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Add comprehensive tests for ast-grep and tree-sitter relationship extraction
- Introduced test suite for AstGrepPythonProcessor covering pattern definitions, parsing, and relationship extraction. - Added comparison tests between tree-sitter and ast-grep for consistency in relationship extraction. - Implemented tests for ast-grep binding module to verify functionality and availability. - Ensured tests cover various scenarios including inheritance, function calls, and imports.
This commit is contained in:
@@ -27,6 +27,11 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '@/components/ui/Dropdown';
|
||||
@@ -37,6 +42,8 @@ import {
|
||||
import { useIssues, useIssueQueue } from '@/hooks/useIssues';
|
||||
import { useTerminalGridStore, selectTerminalGridFocusedPaneId } from '@/stores/terminalGridStore';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import { sendCliSessionText } from '@/lib/api';
|
||||
import { CliConfigModal, type CliSessionConfig } from './CliConfigModal';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -56,6 +63,19 @@ const LAYOUT_PRESETS = [
|
||||
{ id: 'grid-2x2' as const, icon: LayoutGrid, labelId: 'terminalDashboard.toolbar.layoutGrid' },
|
||||
];
|
||||
|
||||
type LaunchMode = 'default' | 'yolo';
|
||||
|
||||
const CLI_TOOLS = ['claude', 'gemini', 'qwen', 'codex', 'opencode'] as const;
|
||||
type CliTool = (typeof CLI_TOOLS)[number];
|
||||
|
||||
const LAUNCH_COMMANDS: Record<CliTool, Record<LaunchMode, string>> = {
|
||||
claude: { default: 'claude', yolo: 'claude --permission-mode bypassPermissions' },
|
||||
gemini: { default: 'gemini', yolo: 'gemini --approval-mode yolo' },
|
||||
qwen: { default: 'qwen', yolo: 'qwen --approval-mode yolo' },
|
||||
codex: { default: 'codex', yolo: 'codex --full-auto' },
|
||||
opencode: { default: 'opencode', yolo: 'opencode' },
|
||||
};
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function DashboardToolbar({ activePanel, onTogglePanel }: DashboardToolbarProps) {
|
||||
@@ -94,117 +114,216 @@ export function DashboardToolbar({ activePanel, onTogglePanel }: DashboardToolba
|
||||
const focusedPaneId = useTerminalGridStore(selectTerminalGridFocusedPaneId);
|
||||
const createSessionAndAssign = useTerminalGridStore((s) => s.createSessionAndAssign);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [selectedTool, setSelectedTool] = useState<CliTool>('gemini');
|
||||
const [launchMode, setLaunchMode] = useState<LaunchMode>('yolo');
|
||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||
|
||||
const handleQuickCreate = useCallback(async () => {
|
||||
if (!focusedPaneId || !projectPath) return;
|
||||
setIsCreating(true);
|
||||
try {
|
||||
await createSessionAndAssign(focusedPaneId, {
|
||||
const created = await createSessionAndAssign(focusedPaneId, {
|
||||
workingDir: projectPath,
|
||||
preferredShell: 'bash',
|
||||
tool: selectedTool,
|
||||
}, projectPath);
|
||||
|
||||
if (created?.session?.sessionKey) {
|
||||
const command = LAUNCH_COMMANDS[selectedTool]?.[launchMode] ?? selectedTool;
|
||||
setTimeout(() => {
|
||||
sendCliSessionText(
|
||||
created.session.sessionKey,
|
||||
{ text: command, appendNewline: true },
|
||||
projectPath
|
||||
).catch((err) => console.error('[DashboardToolbar] auto-launch failed:', err));
|
||||
}, 300);
|
||||
}
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [focusedPaneId, projectPath, createSessionAndAssign, selectedTool, launchMode]);
|
||||
|
||||
const handleConfigure = useCallback(() => {
|
||||
setIsConfigOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleCreateConfiguredSession = useCallback(async (config: CliSessionConfig) => {
|
||||
if (!focusedPaneId || !projectPath) throw new Error('No focused pane or project path');
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const created = await createSessionAndAssign(
|
||||
focusedPaneId,
|
||||
{
|
||||
workingDir: config.workingDir || projectPath,
|
||||
preferredShell: config.preferredShell,
|
||||
tool: config.tool,
|
||||
model: config.model,
|
||||
},
|
||||
projectPath
|
||||
);
|
||||
|
||||
if (!created?.session?.sessionKey) throw new Error('createSessionAndAssign failed');
|
||||
|
||||
const tool = config.tool as CliTool;
|
||||
const mode = config.launchMode as LaunchMode;
|
||||
const command = LAUNCH_COMMANDS[tool]?.[mode] ?? tool;
|
||||
setTimeout(() => {
|
||||
sendCliSessionText(
|
||||
created.session.sessionKey,
|
||||
{ text: command, appendNewline: true },
|
||||
projectPath
|
||||
).catch((err) => console.error('[DashboardToolbar] auto-launch failed:', err));
|
||||
}, 300);
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [focusedPaneId, projectPath, createSessionAndAssign]);
|
||||
|
||||
const handleConfigure = useCallback(() => {
|
||||
// TODO: Open configuration modal (future implementation)
|
||||
console.log('Configure CLI session - modal to be implemented');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 px-2 h-[40px] border-b border-border bg-muted/30 shrink-0">
|
||||
{/* Launch CLI dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<>
|
||||
<div className="flex items-center gap-1 px-2 h-[40px] border-b border-border bg-muted/30 shrink-0">
|
||||
{/* Launch CLI dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs transition-colors',
|
||||
'text-muted-foreground hover:text-foreground hover:bg-muted',
|
||||
isCreating && 'opacity-50 cursor-wait'
|
||||
)}
|
||||
disabled={isCreating || !projectPath}
|
||||
>
|
||||
{isCreating ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<Terminal className="w-3.5 h-3.5" />
|
||||
)}
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.launchCli' })}</span>
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={4}>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="gap-2">
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.tool' })}</span>
|
||||
<span className="text-xs text-muted-foreground">({selectedTool})</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={selectedTool}
|
||||
onValueChange={(v) => setSelectedTool(v as CliTool)}
|
||||
>
|
||||
{CLI_TOOLS.map((tool) => (
|
||||
<DropdownMenuRadioItem key={tool} value={tool}>
|
||||
{tool}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="gap-2">
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.mode' })}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{launchMode === 'default'
|
||||
? formatMessage({ id: 'terminalDashboard.toolbar.modeDefault' })
|
||||
: formatMessage({ id: 'terminalDashboard.toolbar.modeYolo' })}
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={launchMode}
|
||||
onValueChange={(v) => setLaunchMode(v as LaunchMode)}
|
||||
>
|
||||
<DropdownMenuRadioItem value="default">
|
||||
{formatMessage({ id: 'terminalDashboard.toolbar.modeDefault' })}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="yolo">
|
||||
{formatMessage({ id: 'terminalDashboard.toolbar.modeYolo' })}
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={handleQuickCreate}
|
||||
disabled={isCreating || !projectPath || !focusedPaneId}
|
||||
className="gap-2"
|
||||
>
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.quickCreate' })}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={handleConfigure}
|
||||
disabled={isCreating || !projectPath || !focusedPaneId}
|
||||
className="gap-2"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.configure' })}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-px h-5 bg-border mx-1" />
|
||||
|
||||
{/* Panel toggle buttons */}
|
||||
<ToolbarButton
|
||||
icon={AlertCircle}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.issues' })}
|
||||
isActive={activePanel === 'issues'}
|
||||
onClick={() => onTogglePanel('issues')}
|
||||
badge={openCount > 0 ? openCount : undefined}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ListChecks}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.queue' })}
|
||||
isActive={activePanel === 'queue'}
|
||||
onClick={() => onTogglePanel('queue')}
|
||||
badge={queueCount > 0 ? queueCount : undefined}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={Info}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.inspector' })}
|
||||
isActive={activePanel === 'inspector'}
|
||||
onClick={() => onTogglePanel('inspector')}
|
||||
dot={hasChain}
|
||||
/>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-px h-5 bg-border mx-1" />
|
||||
|
||||
{/* Layout presets */}
|
||||
{LAYOUT_PRESETS.map((preset) => (
|
||||
<button
|
||||
key={preset.id}
|
||||
onClick={() => handlePreset(preset.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs transition-colors',
|
||||
'text-muted-foreground hover:text-foreground hover:bg-muted',
|
||||
isCreating && 'opacity-50 cursor-wait'
|
||||
'p-1.5 rounded transition-colors',
|
||||
'text-muted-foreground hover:text-foreground hover:bg-muted'
|
||||
)}
|
||||
disabled={isCreating || !projectPath}
|
||||
title={formatMessage({ id: preset.labelId })}
|
||||
>
|
||||
{isCreating ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<Terminal className="w-3.5 h-3.5" />
|
||||
)}
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.launchCli' })}</span>
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
<preset.icon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={4}>
|
||||
<DropdownMenuItem
|
||||
onClick={handleQuickCreate}
|
||||
disabled={isCreating || !projectPath || !focusedPaneId}
|
||||
className="gap-2"
|
||||
>
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.quickCreate' })}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={handleConfigure}
|
||||
disabled={isCreating}
|
||||
className="gap-2"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.configure' })}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
))}
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-px h-5 bg-border mx-1" />
|
||||
{/* Right-aligned title */}
|
||||
<span className="ml-auto text-xs text-muted-foreground font-medium">
|
||||
{formatMessage({ id: 'terminalDashboard.page.title' })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Panel toggle buttons */}
|
||||
<ToolbarButton
|
||||
icon={AlertCircle}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.issues' })}
|
||||
isActive={activePanel === 'issues'}
|
||||
onClick={() => onTogglePanel('issues')}
|
||||
badge={openCount > 0 ? openCount : undefined}
|
||||
<CliConfigModal
|
||||
isOpen={isConfigOpen}
|
||||
onClose={() => setIsConfigOpen(false)}
|
||||
defaultWorkingDir={projectPath}
|
||||
onCreateSession={handleCreateConfiguredSession}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ListChecks}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.queue' })}
|
||||
isActive={activePanel === 'queue'}
|
||||
onClick={() => onTogglePanel('queue')}
|
||||
badge={queueCount > 0 ? queueCount : undefined}
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={Info}
|
||||
label={formatMessage({ id: 'terminalDashboard.toolbar.inspector' })}
|
||||
isActive={activePanel === 'inspector'}
|
||||
onClick={() => onTogglePanel('inspector')}
|
||||
dot={hasChain}
|
||||
/>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-px h-5 bg-border mx-1" />
|
||||
|
||||
{/* Layout presets */}
|
||||
{LAYOUT_PRESETS.map((preset) => (
|
||||
<button
|
||||
key={preset.id}
|
||||
onClick={() => handlePreset(preset.id)}
|
||||
className={cn(
|
||||
'p-1.5 rounded transition-colors',
|
||||
'text-muted-foreground hover:text-foreground hover:bg-muted'
|
||||
)}
|
||||
title={formatMessage({ id: preset.labelId })}
|
||||
>
|
||||
<preset.icon className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* Right-aligned title */}
|
||||
<span className="ml-auto text-xs text-muted-foreground font-medium">
|
||||
{formatMessage({ id: 'terminalDashboard.page.title' })}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user