Skip to content

Commit 67dbaf7

Browse files
Gemini CLIclaude
andcommitted
feat: AI Workflow Generator UI + fix Skill Creator toolset validation
- WorkflowsTab: ✨ AI生成 button → POST /api/workflows/generate → preview DAG → save/edit - CapabilitiesTab: fix data.config vs data.skill response field mismatch - skill_generator.py: fix invalid toolset names (OperatorToolset → AnalysisToolset, WorldModelToolset → AdvancedAnalysisToolset) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 68e2751 commit 67dbaf7

3 files changed

Lines changed: 125 additions & 6 deletions

File tree

data_agent/skill_generator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
# Advanced
6767
"因果": [("CausalInferenceToolset", "因果推断")],
6868
"causal": [("CausalInferenceToolset", "causal inference")],
69-
"预测": [("WorldModelToolset", "时空预测")],
70-
"world_model": [("WorldModelToolset", "world model prediction")],
69+
"预测": [("AdvancedAnalysisToolset", "时空预测")],
70+
"world_model": [("AdvancedAnalysisToolset", "world model prediction")],
7171
"优化": [("AnalysisToolset", "DRL优化")],
7272
"drl": [("AnalysisToolset", "DRL optimization")],
7373
# Fusion
@@ -84,8 +84,8 @@
8484
"质检": [("GovernanceToolset", "质量检查"), ("DataCleaningToolset", "数据清洗")],
8585
"quality": [("GovernanceToolset", "quality check")],
8686
# Operators
87-
"分析": [("OperatorToolset", "语义分析算子")],
88-
"analyze": [("OperatorToolset", "semantic analysis")],
87+
"分析": [("AnalysisToolset", "数据分析")],
88+
"analyze": [("AnalysisToolset", "data analysis")],
8989
# File
9090
"文件": [("FileToolset", "文件管理")],
9191
"file": [("FileToolset", "file management")],

frontend/src/components/datapanel/CapabilitiesTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ export default function CapabilitiesTab({ userRole }: { userRole?: string }) {
243243
body: JSON.stringify({ description: aiDesc.trim() }),
244244
});
245245
const data = await resp.json();
246-
if (resp.ok && data.skill) {
247-
setAiPreview(data.skill);
246+
if (resp.ok && (data.config || data.skill)) {
247+
setAiPreview(data.config || data.skill);
248248
} else {
249249
setAiError(data.error || 'AI 生成失败,请重试');
250250
}

frontend/src/components/datapanel/WorkflowsTab.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export default function WorkflowsTab() {
5252
const [showFilePicker, setShowFilePicker] = useState(false);
5353
const [executeTarget, setExecuteTarget] = useState<number | null>(null);
5454

55+
// AI Workflow Generator state (v23.0)
56+
const [showAiGen, setShowAiGen] = useState(false);
57+
const [aiDesc, setAiDesc] = useState('');
58+
const [aiGenerating, setAiGenerating] = useState(false);
59+
const [aiPreview, setAiPreview] = useState<any>(null);
60+
const [aiError, setAiError] = useState('');
61+
const [aiSaving, setAiSaving] = useState(false);
62+
5563
const fetchWorkflows = async () => {
5664
setLoading(true);
5765
try {
@@ -69,6 +77,54 @@ export default function WorkflowsTab() {
6977
const handleCreate = () => {
7078
setEditWorkflow(null);
7179
setEditing(true);
80+
setShowAiGen(false);
81+
};
82+
83+
// --- AI Workflow Generator handlers (v23.0) ---
84+
const handleAiGenerate = async () => {
85+
if (!aiDesc.trim()) { setAiError('请描述你想创建的工作流'); return; }
86+
setAiGenerating(true); setAiError(''); setAiPreview(null);
87+
try {
88+
const resp = await fetch('/api/workflows/generate', {
89+
method: 'POST', credentials: 'include',
90+
headers: { 'Content-Type': 'application/json' },
91+
body: JSON.stringify({ description: aiDesc.trim() }),
92+
});
93+
const data = await resp.json();
94+
if (resp.ok && data.workflow) {
95+
setAiPreview(data);
96+
} else {
97+
setAiError(data.error || data.message || 'AI 生成失败,请重试');
98+
}
99+
} catch { setAiError('网络错误'); }
100+
finally { setAiGenerating(false); }
101+
};
102+
103+
const handleAiSave = async () => {
104+
if (!aiPreview?.workflow) return;
105+
setAiSaving(true); setAiError('');
106+
try {
107+
const resp = await fetch('/api/workflows', {
108+
method: 'POST', credentials: 'include',
109+
headers: { 'Content-Type': 'application/json' },
110+
body: JSON.stringify(aiPreview.workflow),
111+
});
112+
if (resp.ok) {
113+
setShowAiGen(false); setAiPreview(null); setAiDesc('');
114+
fetchWorkflows();
115+
} else {
116+
const data = await resp.json();
117+
setAiError(data.error || '保存失败');
118+
}
119+
} catch { setAiError('网络错误'); }
120+
finally { setAiSaving(false); }
121+
};
122+
123+
const handleAiEdit = () => {
124+
if (!aiPreview?.workflow) return;
125+
setEditWorkflow(aiPreview.workflow);
126+
setShowAiGen(false); setAiPreview(null);
127+
setEditing(true);
72128
};
73129

74130
const handleEdit = async (id: number) => {
@@ -285,7 +341,70 @@ export default function WorkflowsTab() {
285341
<div className="workflows-view">
286342
<div className="workflows-header">
287343
<button className="btn-primary btn-sm" onClick={handleCreate}>+ 新建工作流</button>
344+
<button className="btn-primary btn-sm" onClick={() => { setShowAiGen(!showAiGen); setAiPreview(null); setAiError(''); }}
345+
style={{ background: '#8b5cf6', marginLeft: 6 }}>✨ AI生成</button>
288346
</div>
347+
348+
{/* AI Workflow Generator Panel (v23.0) */}
349+
{showAiGen && (
350+
<div style={{ margin: '8px 0', padding: 12, background: '#faf5ff', borderRadius: 8, border: '1px solid #c4b5fd' }}>
351+
<div style={{ fontSize: 13, fontWeight: 600, color: '#8b5cf6', marginBottom: 8 }}>✨ AI 辅助创建工作流</div>
352+
{!aiPreview ? (
353+
<>
354+
<textarea
355+
placeholder={"用自然语言描述工作流,例如:\n• 先做数据画像,再检查拓扑,最后生成专题图\n• 并行执行生态评估和耕地合规检查,然后合并结果做用地优化\n• 加载遥感影像,计算NDVI,检测植被变化,生成报告"}
356+
rows={4} maxLength={2000}
357+
value={aiDesc} onChange={e => setAiDesc(e.target.value)}
358+
style={{ width: '100%', fontSize: 12, lineHeight: 1.6, padding: 8, borderRadius: 6, border: '1px solid #d1d5db', resize: 'vertical' }}
359+
/>
360+
{aiError && <div style={{ color: '#ef4444', fontSize: 12, marginTop: 4 }}>{aiError}</div>}
361+
<div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
362+
<button className="btn-secondary btn-sm" onClick={() => setShowAiGen(false)}>取消</button>
363+
<button className="btn-primary btn-sm" disabled={aiGenerating || !aiDesc.trim()}
364+
onClick={handleAiGenerate} style={{ background: '#8b5cf6' }}>
365+
{aiGenerating ? '生成中...' : '生成工作流'}
366+
</button>
367+
</div>
368+
</>
369+
) : (
370+
<>
371+
<div style={{ fontSize: 12, color: '#6b7280', marginBottom: 6 }}>AI 已生成以下工作流,请确认或编辑后保存:</div>
372+
<div style={{ background: '#fff', borderRadius: 6, padding: 10, fontSize: 12, lineHeight: 1.7, border: '1px solid #e5e7eb' }}>
373+
<div><b>名称:</b> {aiPreview.workflow?.workflow_name}</div>
374+
<div><b>描述:</b> {aiPreview.workflow?.description}</div>
375+
<div style={{ marginTop: 6 }}><b>步骤 ({aiPreview.workflow?.steps?.length || 0}):</b></div>
376+
{(aiPreview.workflow?.steps || []).map((step: any, i: number) => (
377+
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 0', fontSize: 11 }}>
378+
<span style={{ width: 20, height: 20, borderRadius: '50%', background: '#8b5cf6', color: '#fff',
379+
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, flexShrink: 0 }}>
380+
{i + 1}
381+
</span>
382+
<span style={{ fontWeight: 500 }}>{step.label || step.step_id}</span>
383+
<span style={{ color: '#6b7280' }}>({step.pipeline_type}{step.skill_name ? ` / ${step.skill_name}` : ''})</span>
384+
{step.depends_on?.length > 0 && (
385+
<span style={{ color: '#9ca3af', fontSize: 10 }}>{step.depends_on.join(', ')}</span>
386+
)}
387+
</div>
388+
))}
389+
{aiPreview.explanation && (
390+
<div style={{ marginTop: 8, padding: 6, background: '#f8fafc', borderRadius: 4, fontSize: 11, color: '#374151' }}>
391+
{aiPreview.explanation}
392+
</div>
393+
)}
394+
</div>
395+
{aiError && <div style={{ color: '#ef4444', fontSize: 12, marginTop: 4 }}>{aiError}</div>}
396+
<div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
397+
<button className="btn-secondary btn-sm" onClick={() => { setAiPreview(null); setAiDesc(''); }}>重新生成</button>
398+
<button className="btn-secondary btn-sm" onClick={handleAiEdit}>编辑后保存</button>
399+
<button className="btn-primary btn-sm" disabled={aiSaving} onClick={handleAiSave}
400+
style={{ background: '#8b5cf6' }}>
401+
{aiSaving ? '保存中...' : '直接保存'}
402+
</button>
403+
</div>
404+
</>
405+
)}
406+
</div>
407+
)}
289408
{loading && workflows.length === 0 ? (
290409
<div className="empty-state">加载中...</div>
291410
) : workflows.length === 0 ? (

0 commit comments

Comments
 (0)