diff --git a/ccw/frontend/src/pages/orchestrator/PropertyPanel.tsx b/ccw/frontend/src/pages/orchestrator/PropertyPanel.tsx
index 2963245f..5dfafc29 100644
--- a/ccw/frontend/src/pages/orchestrator/PropertyPanel.tsx
+++ b/ccw/frontend/src/pages/orchestrator/PropertyPanel.tsx
@@ -5,7 +5,7 @@
import { useCallback, useMemo, useState, useEffect, useRef, KeyboardEvent } from 'react';
import { useIntl } from 'react-intl';
-import { Settings, X, MessageSquare, Trash2, AlertCircle, CheckCircle2, Plus, Save } from 'lucide-react';
+import { Settings, X, MessageSquare, Trash2, AlertCircle, CheckCircle2, Plus, Save, Copy, ChevronDown, ChevronRight } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
@@ -786,6 +786,176 @@ function SlashCommandSection({ data, onChange, availableVariables }: SlashComman
);
}
+// ========== Collapsible Section ==========
+
+function CollapsibleSection({
+ title,
+ defaultExpanded = false,
+ children,
+}: {
+ title: string;
+ defaultExpanded?: boolean;
+ children: React.ReactNode;
+}) {
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
+ return (
+
+
+ {isExpanded &&
{children}
}
+
+ );
+}
+
+// ========== Tags Input ==========
+
+function TagsInput({ tags, onChange }: { tags: string[]; onChange: (tags: string[]) => void }) {
+ const [input, setInput] = useState('');
+
+ const handleAdd = () => {
+ if (input.trim() && !tags.includes(input.trim())) {
+ onChange([...tags, input.trim()]);
+ setInput('');
+ }
+ };
+
+ const handleRemove = (tag: string) => {
+ onChange(tags.filter(t => t !== tag));
+ };
+
+ const handleKeyDown = (e: KeyboardEvent
) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleAdd();
+ }
+ };
+
+ return (
+
+
+ {tags.map((tag) => (
+
+ {tag}
+
+
+ ))}
+
+
+
setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ placeholder="添加标签..."
+ className="h-7 text-xs"
+ />
+
+
+
+ );
+}
+
+// ========== Artifacts List ==========
+
+function ArtifactsList({ artifacts, onChange }: { artifacts: string[]; onChange: (artifacts: string[]) => void }) {
+ const [input, setInput] = useState('');
+
+ const handleAdd = () => {
+ if (input.trim()) {
+ onChange([...artifacts, input.trim()]);
+ setInput('');
+ }
+ };
+
+ const handleRemove = (index: number) => {
+ onChange(artifacts.filter((_, i) => i !== index));
+ };
+
+ return (
+
+ {artifacts.map((artifact, i) => (
+
+ {'->'}
+ {artifact}
+
+
+ ))}
+
+
setInput(e.target.value)}
+ onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAdd(); } }}
+ placeholder="output-file.json"
+ className="h-7 text-xs font-mono"
+ />
+
+
+
+ );
+}
+
+// ========== Script Preview ==========
+
+function ScriptPreview({ data }: { data: PromptTemplateNodeData }) {
+ const script = useMemo(() => {
+ // Slash command mode
+ if (data.slashCommand) {
+ const args = data.slashArgs ? ` ${data.slashArgs}` : '';
+ return `/${data.slashCommand}${args}`;
+ }
+
+ // CLI tool mode
+ if (data.tool && (data.mode === 'analysis' || data.mode === 'write')) {
+ const parts = ['ccw cli'];
+ parts.push(`--tool ${data.tool}`);
+ parts.push(`--mode ${data.mode}`);
+ if (data.instruction) {
+ const snippet = data.instruction.slice(0, 80).replace(/\n/g, ' ');
+ parts.push(`-p "${snippet}..."`);
+ }
+ return parts.join(' \\\n ');
+ }
+
+ // Plain instruction
+ if (data.instruction) {
+ return `# ${data.instruction.slice(0, 100)}`;
+ }
+
+ return '# 未配置命令';
+ }, [data.slashCommand, data.slashArgs, data.tool, data.mode, data.instruction]);
+
+ const handleCopy = useCallback(() => {
+ navigator.clipboard.writeText(script);
+ }, [script]);
+
+ return (
+
+ );
+}
+
// ========== Unified PromptTemplate Property Editor ==========
interface PromptTemplatePropertiesProps {
@@ -867,6 +1037,75 @@ function PromptTemplateProperties({ data, onChange }: PromptTemplatePropertiesPr
/>
+ {/* Description */}
+
+
+
+
+ {/* Phase */}
+
+
+
+
+
+ {/* Tags */}
+
+
+ onChange({ tags })}
+ />
+
+