mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: add quick template functionality to NodePalette and enhance node creation experience
This commit is contained in:
@@ -65,8 +65,8 @@ export function NavGroup({
|
|||||||
to={item.path}
|
to={item.path}
|
||||||
onClick={onNavClick}
|
onClick={onNavClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center gap-3 px-2 py-2.5 rounded-md text-sm transition-colors',
|
'flex items-center justify-center gap-3 px-2 py-2.5 rounded-md text-sm transition-all duration-200',
|
||||||
'hover:bg-hover hover:text-foreground',
|
'hover:bg-hover hover:text-foreground hover-glow',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary/10 text-primary font-medium'
|
? 'bg-primary/10 text-primary font-medium'
|
||||||
: 'text-muted-foreground'
|
: 'text-muted-foreground'
|
||||||
@@ -107,8 +107,8 @@ export function NavGroup({
|
|||||||
to={item.path}
|
to={item.path}
|
||||||
onClick={onNavClick}
|
onClick={onNavClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors pl-6',
|
'flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-all duration-200 pl-6',
|
||||||
'hover:bg-hover hover:text-foreground',
|
'hover:bg-hover hover:text-foreground hover-glow',
|
||||||
(isActive && !searchParams) || isQueryParamActive
|
(isActive && !searchParams) || isQueryParamActive
|
||||||
? 'bg-primary/10 text-primary font-medium'
|
? 'bg-primary/10 text-primary font-medium'
|
||||||
: 'text-muted-foreground'
|
: 'text-muted-foreground'
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ const buttonVariants = cva(
|
|||||||
"hover:bg-accent hover:text-accent-foreground",
|
"hover:bg-accent hover:text-accent-foreground",
|
||||||
link:
|
link:
|
||||||
"text-primary underline-offset-4 hover:underline",
|
"text-primary underline-offset-4 hover:underline",
|
||||||
|
gradient:
|
||||||
|
"bg-gradient-brand text-primary-foreground hover-glow",
|
||||||
|
gradientPrimary:
|
||||||
|
"bg-gradient-primary text-primary-foreground hover-glow-primary",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
|
|||||||
@@ -75,6 +75,21 @@ const CardFooter = React.forwardRef<
|
|||||||
));
|
));
|
||||||
CardFooter.displayName = "CardFooter";
|
CardFooter.displayName = "CardFooter";
|
||||||
|
|
||||||
|
const CardGradientBorder = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg bg-card text-card-foreground shadow-sm border-gradient-brand",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CardGradientBorder.displayName = "CardGradientBorder";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
@@ -82,4 +97,5 @@ export {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
CardGradientBorder,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -535,3 +535,49 @@
|
|||||||
animation: none;
|
animation: none;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Global Ambient Gradient Background
|
||||||
|
Always visible behind content
|
||||||
|
=========================== */
|
||||||
|
|
||||||
|
/* Standard ambient gradient - subtle */
|
||||||
|
[data-gradient="standard"] body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: radial-gradient(circle at 30% 30%, hsla(var(--accent), 0.03) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 70% 70%, hsla(var(--primary), 0.03) 0%, transparent 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced ambient gradient - more vibrant with animation */
|
||||||
|
[data-gradient="enhanced"] body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: radial-gradient(circle at 20% 20%, hsla(var(--accent), 0.08) 0%, transparent 40%),
|
||||||
|
radial-gradient(circle at 80% 80%, hsla(var(--primary), 0.08) 0%, transparent 40%),
|
||||||
|
radial-gradient(circle at 50% 50%, hsla(var(--secondary), 0.05) 0%, transparent 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
animation: ambient-shift 20s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable ambient gradient when off */
|
||||||
|
[data-gradient="off"] body::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ambient shift animation for enhanced gradient */
|
||||||
|
@keyframes ambient-shift {
|
||||||
|
0%, 100% { transform: translate(0, 0); }
|
||||||
|
50% { transform: translate(-2%, -2%); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function FlowCanvasInner({ className }: FlowCanvasProps) {
|
|||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const setEdges = useFlowStore((state) => state.setEdges);
|
||||||
const addNode = useFlowStore((state) => state.addNode);
|
const addNode = useFlowStore((state) => state.addNode);
|
||||||
|
const addNodeFromTemplate = useFlowStore((state) => state.addNodeFromTemplate);
|
||||||
const setSelectedNodeId = useFlowStore((state) => state.setSelectedNodeId);
|
const setSelectedNodeId = useFlowStore((state) => state.setSelectedNodeId);
|
||||||
const setSelectedEdgeId = useFlowStore((state) => state.setSelectedEdgeId);
|
const setSelectedEdgeId = useFlowStore((state) => state.setSelectedEdgeId);
|
||||||
const markModified = useFlowStore((state) => state.markModified);
|
const markModified = useFlowStore((state) => state.markModified);
|
||||||
@@ -127,10 +128,17 @@ function FlowCanvasInner({ className }: FlowCanvasProps) {
|
|||||||
y: event.clientY,
|
y: event.clientY,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add prompt-template node at drop position
|
// Check if a template ID is provided
|
||||||
addNode(position);
|
const templateId = event.dataTransfer.getData('application/reactflow-template-id');
|
||||||
|
if (templateId) {
|
||||||
|
// Use quick template
|
||||||
|
addNodeFromTemplate(templateId, position);
|
||||||
|
} else {
|
||||||
|
// Use basic empty node
|
||||||
|
addNode(position);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[screenToFlowPosition, addNode]
|
[screenToFlowPosition, addNode, addNodeFromTemplate]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,44 +1,113 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Node Palette Component
|
// Node Palette Component
|
||||||
// ========================================
|
// ========================================
|
||||||
// Draggable node palette for creating new nodes
|
// Draggable node palette with quick templates for creating nodes
|
||||||
|
|
||||||
import { DragEvent, useState } from 'react';
|
import { DragEvent, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { MessageSquare, ChevronDown, ChevronRight, GripVertical } from 'lucide-react';
|
import {
|
||||||
|
MessageSquare, ChevronDown, ChevronRight, GripVertical,
|
||||||
|
Search, Code, FileOutput, GitBranch, GitFork, GitMerge, Plus, Terminal
|
||||||
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { useFlowStore } from '@/stores';
|
import { useFlowStore } from '@/stores';
|
||||||
import { NODE_TYPE_CONFIGS } from '@/types/flow';
|
import { NODE_TYPE_CONFIGS, QUICK_TEMPLATES } from '@/types/flow';
|
||||||
|
|
||||||
interface NodePaletteProps {
|
interface NodePaletteProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draggable card for the unified Prompt Template node type
|
* Icon mapping for quick templates
|
||||||
*/
|
*/
|
||||||
function PromptTemplateCard() {
|
const TEMPLATE_ICONS: Record<string, React.ElementType> = {
|
||||||
const config = NODE_TYPE_CONFIGS['prompt-template'];
|
'slash-command-main': Terminal,
|
||||||
|
'slash-command-async': Terminal,
|
||||||
|
analysis: Search,
|
||||||
|
implementation: Code,
|
||||||
|
'file-operation': FileOutput,
|
||||||
|
conditional: GitBranch,
|
||||||
|
parallel: GitFork,
|
||||||
|
merge: GitMerge,
|
||||||
|
};
|
||||||
|
|
||||||
// Handle drag start
|
/**
|
||||||
|
* Draggable card for a quick template
|
||||||
|
*/
|
||||||
|
function QuickTemplateCard({
|
||||||
|
template,
|
||||||
|
}: {
|
||||||
|
template: typeof QUICK_TEMPLATES[number];
|
||||||
|
}) {
|
||||||
|
const Icon = TEMPLATE_ICONS[template.id] || MessageSquare;
|
||||||
|
|
||||||
|
// Handle drag start - store template ID
|
||||||
const onDragStart = (event: DragEvent<HTMLDivElement>) => {
|
const onDragStart = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.dataTransfer.setData('application/reactflow-node-type', 'prompt-template');
|
event.dataTransfer.setData('application/reactflow-node-type', 'prompt-template');
|
||||||
|
event.dataTransfer.setData('application/reactflow-template-id', template.id);
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle double-click to add node at default position
|
||||||
|
const onDoubleClick = () => {
|
||||||
|
const position = { x: 100 + Math.random() * 200, y: 100 + Math.random() * 200 };
|
||||||
|
useFlowStore.getState().addNodeFromTemplate(template.id, position);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
draggable
|
draggable
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
|
onDoubleClick={onDoubleClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group flex items-center gap-3 p-3 rounded-lg border-2 bg-card cursor-grab transition-all',
|
'group flex items-center gap-3 p-3 rounded-lg border-2 bg-card cursor-grab transition-all',
|
||||||
'hover:shadow-md hover:scale-[1.02] active:cursor-grabbing active:scale-[0.98]',
|
'hover:shadow-md hover:scale-[1.02] active:cursor-grabbing active:scale-[0.98]',
|
||||||
|
`border-${template.color.replace('bg-', '')}`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={cn('p-2 rounded-md text-white', template.color, `hover:${template.color}`)}>
|
||||||
|
<Icon className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="text-sm font-medium text-foreground">{template.label}</div>
|
||||||
|
<div className="text-xs text-muted-foreground truncate">{template.description}</div>
|
||||||
|
</div>
|
||||||
|
<GripVertical className="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic empty prompt template card
|
||||||
|
*/
|
||||||
|
function BasicTemplateCard() {
|
||||||
|
const config = NODE_TYPE_CONFIGS['prompt-template'];
|
||||||
|
|
||||||
|
const onDragStart = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.dataTransfer.setData('application/reactflow-node-type', 'prompt-template');
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDoubleClick = () => {
|
||||||
|
const position = { x: 100 + Math.random() * 200, y: 100 + Math.random() * 200 };
|
||||||
|
useFlowStore.getState().addNode(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDoubleClick={onDoubleClick}
|
||||||
|
className={cn(
|
||||||
|
'group flex items-center gap-3 p-3 rounded-lg border-2 bg-card cursor-grab transition-all',
|
||||||
|
'hover:shadow-md hover:scale-[1.02] active:cursor-grabbing active:scale-[0.98]',
|
||||||
|
'border-dashed border-muted-foreground/50 hover:border-primary',
|
||||||
'border-blue-500'
|
'border-blue-500'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="p-2 rounded-md text-white bg-blue-500 hover:bg-blue-600">
|
<div className="p-2 rounded-md text-white bg-blue-500 hover:bg-blue-600">
|
||||||
<MessageSquare className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm font-medium text-foreground">{config.label}</div>
|
<div className="text-sm font-medium text-foreground">{config.label}</div>
|
||||||
@@ -49,9 +118,41 @@ function PromptTemplateCard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category section with expand/collapse
|
||||||
|
*/
|
||||||
|
function TemplateCategory({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
defaultExpanded = true,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
defaultExpanded?: boolean;
|
||||||
|
}) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="flex items-center gap-2 w-full text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronDown className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
{title}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isExpanded && <div className="space-y-2">{children}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function NodePalette({ className }: NodePaletteProps) {
|
export function NodePalette({ className }: NodePaletteProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
|
||||||
const isPaletteOpen = useFlowStore((state) => state.isPaletteOpen);
|
const isPaletteOpen = useFlowStore((state) => state.isPaletteOpen);
|
||||||
const setIsPaletteOpen = useFlowStore((state) => state.setIsPaletteOpen);
|
const setIsPaletteOpen = useFlowStore((state) => state.setIsPaletteOpen);
|
||||||
|
|
||||||
@@ -91,34 +192,39 @@ export function NodePalette({ className }: NodePaletteProps) {
|
|||||||
{formatMessage({ id: 'orchestrator.palette.instructions' })}
|
{formatMessage({ id: 'orchestrator.palette.instructions' })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Node Type Categories */}
|
{/* Template Categories */}
|
||||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||||
{/* Execution Nodes */}
|
{/* Basic / Empty Template */}
|
||||||
<div>
|
<TemplateCategory title="Basic" defaultExpanded={false}>
|
||||||
<button
|
<BasicTemplateCard />
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
</TemplateCategory>
|
||||||
className="flex items-center gap-2 w-full text-left text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2"
|
|
||||||
>
|
|
||||||
{isExpanded ? (
|
|
||||||
<ChevronDown className="w-3 h-3" />
|
|
||||||
) : (
|
|
||||||
<ChevronRight className="w-3 h-3" />
|
|
||||||
)}
|
|
||||||
{formatMessage({ id: 'orchestrator.palette.nodeTypes' })}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isExpanded && (
|
{/* Slash Commands */}
|
||||||
<div className="space-y-2">
|
<TemplateCategory title="Slash Commands" defaultExpanded={true}>
|
||||||
<PromptTemplateCard />
|
{QUICK_TEMPLATES.filter(t => t.id.startsWith('slash-command')).map((template) => (
|
||||||
</div>
|
<QuickTemplateCard key={template.id} template={template} />
|
||||||
)}
|
))}
|
||||||
</div>
|
</TemplateCategory>
|
||||||
|
|
||||||
|
{/* CLI Tools */}
|
||||||
|
<TemplateCategory title="CLI Tools" defaultExpanded={true}>
|
||||||
|
{QUICK_TEMPLATES.filter(t => ['analysis', 'implementation'].includes(t.id)).map((template) => (
|
||||||
|
<QuickTemplateCard key={template.id} template={template} />
|
||||||
|
))}
|
||||||
|
</TemplateCategory>
|
||||||
|
|
||||||
|
{/* Flow Control */}
|
||||||
|
<TemplateCategory title="Flow Control" defaultExpanded={true}>
|
||||||
|
{QUICK_TEMPLATES.filter(t => ['file-operation', 'conditional', 'parallel', 'merge'].includes(t.id)).map((template) => (
|
||||||
|
<QuickTemplateCard key={template.id} template={template} />
|
||||||
|
))}
|
||||||
|
</TemplateCategory>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="px-4 py-3 border-t border-border bg-muted/30">
|
<div className="px-4 py-3 border-t border-border bg-muted/30">
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
<span className="font-medium">{formatMessage({ id: 'orchestrator.palette.tipLabel' })}</span> {formatMessage({ id: 'orchestrator.palette.tip' })}
|
<span className="font-medium">Tip:</span> Drag to canvas or double-click to add
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -364,6 +364,18 @@ if (typeof window !== 'undefined') {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply initial theme immediately (before localStorage rehydration)
|
||||||
|
// This ensures gradient attributes are set from the start
|
||||||
|
const state = useAppStore.getState();
|
||||||
|
applyThemeToDocument(
|
||||||
|
state.resolvedTheme,
|
||||||
|
state.colorScheme,
|
||||||
|
state.customHue,
|
||||||
|
state.gradientLevel,
|
||||||
|
state.enableHoverGlow,
|
||||||
|
state.enableBackgroundAnimation
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selectors for common access patterns
|
// Selectors for common access patterns
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
NodeData,
|
NodeData,
|
||||||
FlowEdgeData,
|
FlowEdgeData,
|
||||||
} from '../types/flow';
|
} from '../types/flow';
|
||||||
import { NODE_TYPE_CONFIGS as nodeConfigs } from '../types/flow';
|
import { NODE_TYPE_CONFIGS as nodeConfigs, QUICK_TEMPLATES } from '../types/flow';
|
||||||
|
|
||||||
// Helper to generate unique IDs
|
// Helper to generate unique IDs
|
||||||
const generateId = (prefix: string): string => {
|
const generateId = (prefix: string): string => {
|
||||||
@@ -257,6 +257,44 @@ export const useFlowStore = create<FlowStore>()(
|
|||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addNodeFromTemplate: (templateId: string, position: { x: number; y: number }): string => {
|
||||||
|
const template = QUICK_TEMPLATES.find((t) => t.id === templateId);
|
||||||
|
if (!template) {
|
||||||
|
console.error(`Template not found: ${templateId}`);
|
||||||
|
return get().addNode(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = generateId('node');
|
||||||
|
const config = nodeConfigs['prompt-template'];
|
||||||
|
|
||||||
|
// Merge template data with default data
|
||||||
|
const nodeData: NodeData = {
|
||||||
|
...config.defaultData,
|
||||||
|
...template.data,
|
||||||
|
label: template.data.label || template.label,
|
||||||
|
contextRefs: template.data.contextRefs || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const newNode: FlowNode = {
|
||||||
|
id,
|
||||||
|
type: 'prompt-template',
|
||||||
|
position,
|
||||||
|
data: nodeData,
|
||||||
|
};
|
||||||
|
|
||||||
|
set(
|
||||||
|
(state) => ({
|
||||||
|
nodes: [...state.nodes, newNode],
|
||||||
|
isModified: true,
|
||||||
|
selectedNodeId: id,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'addNodeFromTemplate'
|
||||||
|
);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
|
||||||
updateNode: (id: string, data: Partial<NodeData>) => {
|
updateNode: (id: string, data: Partial<NodeData>) => {
|
||||||
set(
|
set(
|
||||||
(state) => ({
|
(state) => ({
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ export interface FlowActions {
|
|||||||
|
|
||||||
// Node operations
|
// Node operations
|
||||||
addNode: (position: { x: number; y: number }) => string;
|
addNode: (position: { x: number; y: number }) => string;
|
||||||
|
addNodeFromTemplate: (templateId: string, position: { x: number; y: number }) => string;
|
||||||
updateNode: (id: string, data: Partial<NodeData>) => void;
|
updateNode: (id: string, data: Partial<NodeData>) => void;
|
||||||
removeNode: (id: string) => void;
|
removeNode: (id: string) => void;
|
||||||
setNodes: (nodes: FlowNode[]) => void;
|
setNodes: (nodes: FlowNode[]) => void;
|
||||||
@@ -286,6 +287,30 @@ export interface QuickTemplate {
|
|||||||
* All use 'prompt-template' type with preset configurations
|
* All use 'prompt-template' type with preset configurations
|
||||||
*/
|
*/
|
||||||
export const QUICK_TEMPLATES: QuickTemplate[] = [
|
export const QUICK_TEMPLATES: QuickTemplate[] = [
|
||||||
|
{
|
||||||
|
id: 'slash-command-main',
|
||||||
|
label: 'Slash Command',
|
||||||
|
description: 'Execute /workflow commands (main thread)',
|
||||||
|
icon: 'Terminal',
|
||||||
|
color: 'bg-rose-500',
|
||||||
|
data: {
|
||||||
|
label: 'Slash Command',
|
||||||
|
instruction: 'Execute slash command:\n\n/workflow:plan "Implement [feature]"\n\nAvailable commands:\n- /workflow:plan\n- /workflow:lite-plan\n- /workflow:execute\n- /workflow:analyze-with-file\n- /workflow:brainstorm-with-file\n- /workflow:test-fix-gen',
|
||||||
|
mode: 'mainprocess',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'slash-command-async',
|
||||||
|
label: 'Slash Command (Async)',
|
||||||
|
description: 'Execute /workflow commands (background)',
|
||||||
|
icon: 'Terminal',
|
||||||
|
color: 'bg-rose-400',
|
||||||
|
data: {
|
||||||
|
label: 'Slash Command (Async)',
|
||||||
|
instruction: 'Execute slash command in background:\n\n/workflow:execute --in-memory\n\nThe workflow will run asynchronously via CLI. Continue to next step without waiting.',
|
||||||
|
mode: 'async',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'analysis',
|
id: 'analysis',
|
||||||
label: 'Analysis',
|
label: 'Analysis',
|
||||||
|
|||||||
Reference in New Issue
Block a user