mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: 添加左侧面板和节点库组件,整合模板和节点功能
feat: 实现可折叠的模板面板,支持搜索和安装模板 feat: 更新流程工具栏,增加导入模板和模拟运行功能 feat: 增强属性面板,支持标签和产物管理 feat: 优化提示模板节点,增加执行状态和阶段显示
This commit is contained in:
164
ccw/frontend/src/pages/orchestrator/InlineTemplatePanel.tsx
Normal file
164
ccw/frontend/src/pages/orchestrator/InlineTemplatePanel.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
// ========================================
|
||||
// Inline Template Panel Component
|
||||
// ========================================
|
||||
// Compact template list for the left sidebar, uses useTemplates hook
|
||||
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { Search, Loader2, FileText, Download, GitBranch } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useTemplates, useInstallTemplate } from '@/hooks/useTemplates';
|
||||
import { useFlowStore } from '@/stores';
|
||||
import type { FlowTemplate } from '@/types/execution';
|
||||
|
||||
// ========== Sub-Components ==========
|
||||
|
||||
interface TemplateItemProps {
|
||||
template: FlowTemplate;
|
||||
onInstall: (template: FlowTemplate) => void;
|
||||
isInstalling: boolean;
|
||||
}
|
||||
|
||||
function TemplateItem({ template, onInstall, isInstalling }: TemplateItemProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => onInstall(template)}
|
||||
disabled={isInstalling}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-md text-left transition-colors',
|
||||
'hover:bg-muted/60 active:bg-muted',
|
||||
isInstalling && 'opacity-50 cursor-wait'
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-foreground truncate">
|
||||
{template.name}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<GitBranch className="w-3 h-3" />
|
||||
{template.nodeCount} nodes
|
||||
</span>
|
||||
{template.category && (
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
|
||||
{template.category}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isInstalling ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground shrink-0" />
|
||||
) : (
|
||||
<Download className="w-4 h-4 text-muted-foreground shrink-0 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Main Component ==========
|
||||
|
||||
interface InlineTemplatePanelProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact template browser for the left sidebar.
|
||||
* Loads templates via the useTemplates API hook and displays them in a searchable list.
|
||||
* Clicking a template installs it as the current flow.
|
||||
*/
|
||||
export function InlineTemplatePanel({ className }: InlineTemplatePanelProps) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [installingId, setInstallingId] = useState<string | null>(null);
|
||||
|
||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||
|
||||
const { data, isLoading, error } = useTemplates();
|
||||
const installTemplate = useInstallTemplate();
|
||||
|
||||
// Filter templates by search query
|
||||
const filteredTemplates = useMemo(() => {
|
||||
if (!data?.templates) return [];
|
||||
if (!searchQuery.trim()) return data.templates;
|
||||
|
||||
const query = searchQuery.toLowerCase();
|
||||
return data.templates.filter(
|
||||
(t) =>
|
||||
t.name.toLowerCase().includes(query) ||
|
||||
t.description?.toLowerCase().includes(query) ||
|
||||
t.category?.toLowerCase().includes(query) ||
|
||||
t.tags?.some((tag) => tag.toLowerCase().includes(query))
|
||||
);
|
||||
}, [data?.templates, searchQuery]);
|
||||
|
||||
// Handle install - load template as current flow
|
||||
const handleInstall = useCallback(
|
||||
async (template: FlowTemplate) => {
|
||||
setInstallingId(template.id);
|
||||
try {
|
||||
const result = await installTemplate.mutateAsync({
|
||||
templateId: template.id,
|
||||
});
|
||||
setCurrentFlow(result.flow);
|
||||
} catch (err) {
|
||||
console.error('Failed to install template:', err);
|
||||
} finally {
|
||||
setInstallingId(null);
|
||||
}
|
||||
},
|
||||
[installTemplate, setCurrentFlow]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn('flex-1 flex flex-col overflow-hidden', className)}>
|
||||
{/* Search */}
|
||||
<div className="px-3 py-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="搜索模板..."
|
||||
className="pl-8 h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Template List */}
|
||||
<div className="flex-1 overflow-y-auto px-1">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground px-4">
|
||||
<FileText className="h-8 w-8 mb-2 opacity-50" />
|
||||
<p className="text-xs text-center">
|
||||
无法加载模板库,请确认 API 服务可用
|
||||
</p>
|
||||
</div>
|
||||
) : filteredTemplates.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground px-4">
|
||||
<FileText className="h-8 w-8 mb-2 opacity-50" />
|
||||
<p className="text-xs text-center">
|
||||
{searchQuery ? '未找到匹配的模板' : '暂无可用模板'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0.5">
|
||||
{filteredTemplates.map((template) => (
|
||||
<TemplateItem
|
||||
key={template.id}
|
||||
template={template}
|
||||
onInstall={handleInstall}
|
||||
isInstalling={installingId === template.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InlineTemplatePanel;
|
||||
Reference in New Issue
Block a user