@geminilight/mindos 0.6.28 → 0.6.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/app/app/api/a2a/agents/route.ts +9 -0
- package/app/app/api/a2a/delegations/route.ts +9 -0
- package/app/app/api/a2a/discover/route.ts +2 -0
- package/app/app/api/a2a/route.ts +6 -6
- package/app/app/api/acp/config/route.ts +82 -0
- package/app/app/api/acp/detect/route.ts +114 -0
- package/app/app/api/acp/install/route.ts +51 -0
- package/app/app/api/acp/registry/route.ts +31 -0
- package/app/app/api/acp/session/route.ts +185 -0
- package/app/app/api/ask/route.ts +116 -13
- package/app/app/api/workflows/route.ts +156 -0
- package/app/app/layout.tsx +2 -0
- package/app/app/page.tsx +7 -2
- package/app/components/ActivityBar.tsx +12 -4
- package/app/components/AskModal.tsx +4 -1
- package/app/components/DirView.tsx +64 -2
- package/app/components/FileTree.tsx +40 -10
- package/app/components/GuideCard.tsx +7 -17
- package/app/components/HomeContent.tsx +1 -0
- package/app/components/MarkdownView.tsx +2 -0
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAskPanel.tsx +5 -1
- package/app/components/SearchModal.tsx +234 -80
- package/app/components/SidebarLayout.tsx +6 -0
- package/app/components/agents/AgentDetailContent.tsx +266 -52
- package/app/components/agents/AgentsContentPage.tsx +32 -6
- package/app/components/agents/AgentsPanelA2aTab.tsx +684 -0
- package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
- package/app/components/agents/SkillDetailPopover.tsx +4 -9
- package/app/components/agents/agents-content-model.ts +2 -2
- package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
- package/app/components/ask/AskContent.tsx +197 -239
- package/app/components/ask/FileChip.tsx +82 -17
- package/app/components/ask/MentionPopover.tsx +21 -3
- package/app/components/ask/MessageList.tsx +30 -9
- package/app/components/ask/SlashCommandPopover.tsx +21 -3
- package/app/components/help/HelpContent.tsx +9 -9
- package/app/components/panels/AgentsPanel.tsx +2 -0
- package/app/components/panels/AgentsPanelAgentDetail.tsx +5 -8
- package/app/components/panels/AgentsPanelHubNav.tsx +16 -2
- package/app/components/panels/EchoPanel.tsx +5 -1
- package/app/components/panels/EchoSidebarStats.tsx +136 -0
- package/app/components/panels/WorkflowsPanel.tsx +206 -0
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +157 -0
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +201 -0
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +226 -0
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
- package/app/components/renderers/workflow-yaml/execution.ts +177 -0
- package/app/components/renderers/workflow-yaml/index.ts +6 -0
- package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
- package/app/components/renderers/workflow-yaml/parser.ts +172 -0
- package/app/components/renderers/workflow-yaml/selectors.tsx +522 -0
- package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
- package/app/components/renderers/workflow-yaml/types.ts +46 -0
- package/app/components/settings/KnowledgeTab.tsx +3 -6
- package/app/components/settings/McpSkillsSection.tsx +4 -5
- package/app/components/settings/McpTab.tsx +6 -8
- package/app/components/setup/StepSecurity.tsx +4 -5
- package/app/components/setup/index.tsx +5 -11
- package/app/components/ui/Toaster.tsx +39 -0
- package/app/hooks/useA2aRegistry.ts +6 -1
- package/app/hooks/useAcpConfig.ts +96 -0
- package/app/hooks/useAcpDetection.ts +120 -0
- package/app/hooks/useAcpRegistry.ts +86 -0
- package/app/hooks/useAskModal.ts +12 -5
- package/app/hooks/useAskPanel.ts +8 -5
- package/app/hooks/useAskSession.ts +19 -2
- package/app/hooks/useDelegationHistory.ts +49 -0
- package/app/hooks/useImageUpload.ts +152 -0
- package/app/lib/a2a/client.ts +49 -5
- package/app/lib/a2a/orchestrator.ts +0 -1
- package/app/lib/a2a/task-handler.ts +4 -4
- package/app/lib/a2a/types.ts +15 -0
- package/app/lib/acp/acp-tools.ts +95 -0
- package/app/lib/acp/agent-descriptors.ts +274 -0
- package/app/lib/acp/bridge.ts +144 -0
- package/app/lib/acp/index.ts +40 -0
- package/app/lib/acp/registry.ts +202 -0
- package/app/lib/acp/session.ts +717 -0
- package/app/lib/acp/subprocess.ts +495 -0
- package/app/lib/acp/types.ts +274 -0
- package/app/lib/agent/model.ts +18 -3
- package/app/lib/agent/to-agent-messages.ts +25 -2
- package/app/lib/agent/tools.ts +2 -1
- package/app/lib/i18n/_core.ts +22 -0
- package/app/lib/i18n/index.ts +35 -0
- package/app/lib/i18n/modules/ai-chat.ts +215 -0
- package/app/lib/i18n/modules/common.ts +71 -0
- package/app/lib/i18n/modules/features.ts +153 -0
- package/app/lib/i18n/modules/knowledge.ts +429 -0
- package/app/lib/i18n/modules/navigation.ts +153 -0
- package/app/lib/i18n/modules/onboarding.ts +523 -0
- package/app/lib/i18n/modules/panels.ts +1196 -0
- package/app/lib/i18n/modules/settings.ts +585 -0
- package/app/lib/i18n-en.ts +2 -1518
- package/app/lib/i18n-zh.ts +2 -1542
- package/app/lib/i18n.ts +3 -6
- package/app/lib/pi-integration/skills.ts +21 -6
- package/app/lib/renderers/index.ts +2 -2
- package/app/lib/settings.ts +10 -0
- package/app/lib/toast.ts +79 -0
- package/app/lib/types.ts +12 -1
- package/app/next-env.d.ts +1 -1
- package/app/package.json +3 -1
- package/bin/cli.js +25 -25
- package/bin/commands/file.js +29 -2
- package/bin/commands/space.js +249 -91
- package/package.json +1 -1
- package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
- package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
- package/app/components/renderers/workflow/manifest.ts +0 -14
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useMemo, useState, useRef, useCallback } from 'react';
|
|
4
|
-
import { Play, SkipForward, RotateCcw, CheckCircle2, Circle, Loader2, AlertCircle, ChevronDown, Sparkles } from 'lucide-react';
|
|
5
|
-
import type { RendererContext } from '@/lib/renderers/registry';
|
|
6
|
-
import { useLocale } from '@/lib/LocaleContext';
|
|
7
|
-
|
|
8
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
type StepStatus = 'pending' | 'running' | 'done' | 'skipped' | 'error';
|
|
11
|
-
|
|
12
|
-
interface WorkflowStep {
|
|
13
|
-
index: number;
|
|
14
|
-
heading: string; // full heading text e.g. "Step 1: Gather requirements"
|
|
15
|
-
body: string; // body text below heading
|
|
16
|
-
status: StepStatus;
|
|
17
|
-
output: string; // AI output for this step
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface WorkflowMeta {
|
|
21
|
-
title: string;
|
|
22
|
-
description: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
function parseWorkflow(content: string): { meta: WorkflowMeta; steps: WorkflowStep[] } {
|
|
28
|
-
const lines = content.split('\n');
|
|
29
|
-
let title = '';
|
|
30
|
-
let description = '';
|
|
31
|
-
const steps: WorkflowStep[] = [];
|
|
32
|
-
let currentStep: { heading: string; bodyLines: string[] } | null = null;
|
|
33
|
-
let inMeta = true;
|
|
34
|
-
const metaLines: string[] = [];
|
|
35
|
-
|
|
36
|
-
const flushStep = () => {
|
|
37
|
-
if (!currentStep) return;
|
|
38
|
-
steps.push({
|
|
39
|
-
index: steps.length,
|
|
40
|
-
heading: currentStep.heading,
|
|
41
|
-
body: currentStep.bodyLines.join('\n').trim(),
|
|
42
|
-
status: 'pending',
|
|
43
|
-
output: '',
|
|
44
|
-
});
|
|
45
|
-
currentStep = null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
for (const line of lines) {
|
|
49
|
-
if (/^# /.test(line)) {
|
|
50
|
-
title = line.slice(2).trim();
|
|
51
|
-
inMeta = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
// H2 = step
|
|
55
|
-
if (/^## /.test(line)) {
|
|
56
|
-
flushStep();
|
|
57
|
-
inMeta = false;
|
|
58
|
-
currentStep = { heading: line.slice(3).trim(), bodyLines: [] };
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (currentStep) {
|
|
62
|
-
currentStep.bodyLines.push(line);
|
|
63
|
-
} else if (inMeta) {
|
|
64
|
-
metaLines.push(line);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
flushStep();
|
|
68
|
-
|
|
69
|
-
description = metaLines.filter(l => l.trim() && !/^#/.test(l)).join(' ').trim().slice(0, 200);
|
|
70
|
-
|
|
71
|
-
return { meta: { title, description }, steps };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ─── Inline markdown renderer ─────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
function renderInline(text: string): string {
|
|
77
|
-
return text
|
|
78
|
-
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
79
|
-
.replace(/`(.+?)`/g, `<code class="font-display" style="font-size:.82em;padding:1px 5px;border-radius:4px;background:var(--muted)">$1</code>`)
|
|
80
|
-
.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function renderBody(body: string): string {
|
|
84
|
-
return body.split('\n').map(line => {
|
|
85
|
-
if (!line.trim()) return '';
|
|
86
|
-
if (/^- /.test(line)) return `<li style="margin:.2em 0;font-size:.82rem;color:var(--muted-foreground)">${renderInline(line.slice(2))}</li>`;
|
|
87
|
-
return `<p style="margin:.3em 0;font-size:.82rem;line-height:1.6;color:var(--muted-foreground)">${renderInline(line)}</p>`;
|
|
88
|
-
}).join('');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── Status icon ─────────────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
function StatusIcon({ status }: { status: StepStatus }) {
|
|
94
|
-
if (status === 'pending') return <Circle size={15} style={{ color: 'var(--border)' }} />;
|
|
95
|
-
if (status === 'running') return <Loader2 size={15} style={{ color: 'var(--amber)', animation: 'spin 1s linear infinite' }} />;
|
|
96
|
-
if (status === 'done') return <CheckCircle2 size={15} style={{ color: 'var(--success)' }} />;
|
|
97
|
-
if (status === 'skipped') return <SkipForward size={15} style={{ color: 'var(--muted-foreground)', opacity: .5 }} />;
|
|
98
|
-
return <AlertCircle size={15} style={{ color: 'var(--error)' }} />;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const STATUS_BORDER: Record<StepStatus, string> = {
|
|
102
|
-
pending: 'var(--border)',
|
|
103
|
-
running: 'rgba(200,135,58,0.5)',
|
|
104
|
-
done: 'rgba(122,173,128,0.4)',
|
|
105
|
-
skipped: 'var(--border)',
|
|
106
|
-
error: 'rgba(200,80,80,0.4)',
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// ─── AI execution ─────────────────────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
async function runStepWithAI(
|
|
112
|
-
step: WorkflowStep,
|
|
113
|
-
filePath: string,
|
|
114
|
-
allStepsSummary: string,
|
|
115
|
-
onChunk: (chunk: string) => void,
|
|
116
|
-
signal: AbortSignal,
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
const prompt = `You are executing step ${step.index + 1} of a SOP/Workflow: "${step.heading}".
|
|
119
|
-
|
|
120
|
-
Context of the full workflow:
|
|
121
|
-
${allStepsSummary}
|
|
122
|
-
|
|
123
|
-
Current step instructions:
|
|
124
|
-
${step.body || '(No specific instructions — use common sense for this step.)'}
|
|
125
|
-
|
|
126
|
-
Execute this step concisely. Provide:
|
|
127
|
-
1. What you did / what the output is
|
|
128
|
-
2. Any decisions made
|
|
129
|
-
3. What the next step should watch out for
|
|
130
|
-
|
|
131
|
-
Be specific and actionable. Format in Markdown.`;
|
|
132
|
-
|
|
133
|
-
const res = await fetch('/api/ask', {
|
|
134
|
-
method: 'POST',
|
|
135
|
-
headers: { 'Content-Type': 'application/json' },
|
|
136
|
-
body: JSON.stringify({
|
|
137
|
-
messages: [{ role: 'user', content: prompt }],
|
|
138
|
-
currentFile: filePath,
|
|
139
|
-
}),
|
|
140
|
-
signal,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
144
|
-
if (!res.body) throw new Error('No response body');
|
|
145
|
-
|
|
146
|
-
const reader = res.body.getReader();
|
|
147
|
-
const decoder = new TextDecoder();
|
|
148
|
-
let acc = '';
|
|
149
|
-
|
|
150
|
-
while (true) {
|
|
151
|
-
const { done, value } = await reader.read();
|
|
152
|
-
if (done) break;
|
|
153
|
-
const raw = decoder.decode(value, { stream: true });
|
|
154
|
-
for (const line of raw.split('\n')) {
|
|
155
|
-
const m = line.match(/^0:"((?:[^"\\]|\\.)*)"$/);
|
|
156
|
-
if (m) {
|
|
157
|
-
acc += m[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
158
|
-
onChunk(acc);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ─── Step card ────────────────────────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
function StepCard({
|
|
167
|
-
step, isActive, onRun, onSkip, canRun,
|
|
168
|
-
}: {
|
|
169
|
-
step: WorkflowStep;
|
|
170
|
-
isActive: boolean;
|
|
171
|
-
onRun: () => void;
|
|
172
|
-
onSkip: () => void;
|
|
173
|
-
canRun: boolean;
|
|
174
|
-
}) {
|
|
175
|
-
const { t } = useLocale();
|
|
176
|
-
const [expanded, setExpanded] = useState(false);
|
|
177
|
-
const hasBody = step.body.trim().length > 0;
|
|
178
|
-
const hasOutput = step.output.length > 0;
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<div style={{
|
|
182
|
-
border: `1px solid ${STATUS_BORDER[step.status]}`,
|
|
183
|
-
borderRadius: 10,
|
|
184
|
-
overflow: 'hidden',
|
|
185
|
-
background: 'var(--card)',
|
|
186
|
-
opacity: step.status === 'skipped' ? 0.6 : 1,
|
|
187
|
-
transition: 'border-color .2s, opacity .2s',
|
|
188
|
-
}}>
|
|
189
|
-
{/* header */}
|
|
190
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 14px' }}>
|
|
191
|
-
<StatusIcon status={step.status} />
|
|
192
|
-
<span
|
|
193
|
-
style={{ flex: 1, fontWeight: 600, fontSize: '.88rem', color: 'var(--foreground)', cursor: hasBody || hasOutput ? 'pointer' : 'default' }}
|
|
194
|
-
onClick={() => (hasBody || hasOutput) && setExpanded(v => !v)}
|
|
195
|
-
>
|
|
196
|
-
{step.heading}
|
|
197
|
-
</span>
|
|
198
|
-
|
|
199
|
-
{/* action buttons */}
|
|
200
|
-
<div style={{ display: 'flex', gap: 5, flexShrink: 0 }}>
|
|
201
|
-
{step.status === 'pending' && (
|
|
202
|
-
<>
|
|
203
|
-
<button
|
|
204
|
-
onClick={onRun}
|
|
205
|
-
disabled={!canRun}
|
|
206
|
-
title={!canRun ? t.hints.workflowStepRunning : undefined}
|
|
207
|
-
style={{
|
|
208
|
-
display: 'flex', alignItems: 'center', gap: 4,
|
|
209
|
-
padding: '3px 10px', borderRadius: 6, fontSize: '0.72rem',
|
|
210
|
-
cursor: canRun ? 'pointer' : 'not-allowed',
|
|
211
|
-
border: 'none', background: canRun ? 'var(--amber)' : 'var(--muted)',
|
|
212
|
-
color: canRun ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
|
|
213
|
-
opacity: canRun ? 1 : 0.5,
|
|
214
|
-
}}
|
|
215
|
-
>
|
|
216
|
-
<Play size={10} /> Run
|
|
217
|
-
</button>
|
|
218
|
-
<button
|
|
219
|
-
onClick={onSkip}
|
|
220
|
-
style={{
|
|
221
|
-
padding: '3px 8px', borderRadius: 6, fontSize: '0.72rem',
|
|
222
|
-
cursor: 'pointer',
|
|
223
|
-
border: '1px solid var(--border)', background: 'transparent',
|
|
224
|
-
color: 'var(--muted-foreground)',
|
|
225
|
-
}}
|
|
226
|
-
>
|
|
227
|
-
Skip
|
|
228
|
-
</button>
|
|
229
|
-
</>
|
|
230
|
-
)}
|
|
231
|
-
{step.status === 'running' && (
|
|
232
|
-
<span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--amber)' }}>executing…</span>
|
|
233
|
-
)}
|
|
234
|
-
{(step.status === 'done' || step.status === 'error') && (
|
|
235
|
-
<button
|
|
236
|
-
onClick={() => setExpanded(v => !v)}
|
|
237
|
-
style={{ padding: '3px 8px', borderRadius: 6, fontSize: '0.72rem', cursor: 'pointer', border: '1px solid var(--border)', background: 'transparent', color: 'var(--muted-foreground)' }}
|
|
238
|
-
>
|
|
239
|
-
<ChevronDown size={11} style={{ display: 'inline', transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
|
|
240
|
-
</button>
|
|
241
|
-
)}
|
|
242
|
-
</div>
|
|
243
|
-
</div>
|
|
244
|
-
|
|
245
|
-
{/* body / output */}
|
|
246
|
-
{(expanded || step.status === 'running') && (hasBody || hasOutput) && (
|
|
247
|
-
<div style={{ borderTop: '1px solid var(--border)' }}>
|
|
248
|
-
{hasBody && (
|
|
249
|
-
<div style={{ padding: '10px 14px', borderBottom: hasOutput ? '1px solid var(--border)' : 'none' }}>
|
|
250
|
-
<div dangerouslySetInnerHTML={{ __html: renderBody(step.body) }} />
|
|
251
|
-
</div>
|
|
252
|
-
)}
|
|
253
|
-
{hasOutput && (
|
|
254
|
-
<div style={{ padding: '10px 14px', background: 'var(--background)', position: 'relative' }}>
|
|
255
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 6 }}>
|
|
256
|
-
<Sparkles size={11} style={{ color: 'var(--amber)' }} />
|
|
257
|
-
<span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', textTransform: 'uppercase', letterSpacing: '.06em' }}>AI Output</span>
|
|
258
|
-
{step.status === 'running' && <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--amber)', animation: 'pulse 1.2s ease-in-out infinite', marginLeft: 4 }} />}
|
|
259
|
-
</div>
|
|
260
|
-
<div style={{ fontSize: '.82rem', lineHeight: 1.7, color: 'var(--foreground)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
261
|
-
{step.output}
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ─── Main renderer ────────────────────────────────────────────────────────────
|
|
272
|
-
|
|
273
|
-
export function WorkflowRenderer({ filePath, content }: RendererContext) {
|
|
274
|
-
const { t } = useLocale();
|
|
275
|
-
const parsed = useMemo(() => parseWorkflow(content), [content]);
|
|
276
|
-
const [steps, setSteps] = useState<WorkflowStep[]>(() => parsed.steps);
|
|
277
|
-
const [running, setRunning] = useState(false);
|
|
278
|
-
const abortRef = useRef<AbortController | null>(null);
|
|
279
|
-
|
|
280
|
-
// Reset when content changes externally
|
|
281
|
-
useMemo(() => { setSteps(parsed.steps.map(s => ({ ...s, status: 'pending' as StepStatus, output: '' }))); }, [parsed]);
|
|
282
|
-
|
|
283
|
-
const allStepsSummary = useMemo(() =>
|
|
284
|
-
parsed.steps.map((s, i) => `${i + 1}. ${s.heading}`).join('\n'),
|
|
285
|
-
[parsed]);
|
|
286
|
-
|
|
287
|
-
const runStep = useCallback(async (idx: number) => {
|
|
288
|
-
if (running) return;
|
|
289
|
-
abortRef.current?.abort();
|
|
290
|
-
const ctrl = new AbortController();
|
|
291
|
-
abortRef.current = ctrl;
|
|
292
|
-
setRunning(true);
|
|
293
|
-
|
|
294
|
-
setSteps(prev => prev.map((s, i) =>
|
|
295
|
-
i === idx ? { ...s, status: 'running', output: '' } : s));
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
await runStepWithAI(
|
|
299
|
-
steps[idx], filePath, allStepsSummary,
|
|
300
|
-
(chunk) => setSteps(prev => prev.map((s, i) =>
|
|
301
|
-
i === idx ? { ...s, output: chunk } : s)),
|
|
302
|
-
ctrl.signal,
|
|
303
|
-
);
|
|
304
|
-
setSteps(prev => prev.map((s, i) =>
|
|
305
|
-
i === idx ? { ...s, status: 'done' } : s));
|
|
306
|
-
} catch (err: unknown) {
|
|
307
|
-
if (err instanceof Error && err.name === 'AbortError') return;
|
|
308
|
-
setSteps(prev => prev.map((s, i) =>
|
|
309
|
-
i === idx ? { ...s, status: 'error', output: (err instanceof Error ? err.message : String(err)) } : s));
|
|
310
|
-
} finally {
|
|
311
|
-
setRunning(false);
|
|
312
|
-
}
|
|
313
|
-
}, [running, steps, filePath, allStepsSummary]);
|
|
314
|
-
|
|
315
|
-
const skipStep = useCallback((idx: number) => {
|
|
316
|
-
setSteps(prev => prev.map((s, i) =>
|
|
317
|
-
i === idx ? { ...s, status: 'skipped' } : s));
|
|
318
|
-
}, []);
|
|
319
|
-
|
|
320
|
-
const reset = useCallback(() => {
|
|
321
|
-
abortRef.current?.abort();
|
|
322
|
-
setRunning(false);
|
|
323
|
-
setSteps(parsed.steps.map(s => ({ ...s, status: 'pending' as StepStatus, output: '' })));
|
|
324
|
-
}, [parsed]);
|
|
325
|
-
|
|
326
|
-
// Next runnable step = first pending step
|
|
327
|
-
const nextPendingIdx = steps.findIndex(s => s.status === 'pending');
|
|
328
|
-
const doneCount = steps.filter(s => s.status === 'done').length;
|
|
329
|
-
const progress = steps.length > 0 ? Math.round((doneCount / steps.length) * 100) : 0;
|
|
330
|
-
|
|
331
|
-
if (steps.length === 0) {
|
|
332
|
-
return (
|
|
333
|
-
<div className="font-display" style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontSize: 12 }}>
|
|
334
|
-
No steps found. Add <code style={{ background: 'var(--muted)', padding: '1px 5px', borderRadius: 4 }}>## Step N: …</code> headings to define workflow steps.
|
|
335
|
-
</div>
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return (
|
|
340
|
-
<div style={{ maxWidth: 720, margin: '0 auto', padding: '1.5rem 0' }}>
|
|
341
|
-
{/* header */}
|
|
342
|
-
<div style={{ marginBottom: '1.2rem' }}>
|
|
343
|
-
{parsed.meta.description && (
|
|
344
|
-
<p style={{ fontSize: '.82rem', color: 'var(--muted-foreground)', lineHeight: 1.6, marginBottom: 12 }}>
|
|
345
|
-
{parsed.meta.description}
|
|
346
|
-
</p>
|
|
347
|
-
)}
|
|
348
|
-
|
|
349
|
-
{/* progress + actions */}
|
|
350
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
|
351
|
-
{/* progress bar */}
|
|
352
|
-
<div style={{ flex: 1, minWidth: 120, height: 4, borderRadius: 999, background: 'var(--border)', overflow: 'hidden' }}>
|
|
353
|
-
<div style={{ height: '100%', width: `${progress}%`, background: 'var(--amber)', borderRadius: 999, transition: 'width .3s' }} />
|
|
354
|
-
</div>
|
|
355
|
-
<span className="font-display" style={{ fontSize: '0.7rem', color: 'var(--muted-foreground)', flexShrink: 0 }}>
|
|
356
|
-
{doneCount}/{steps.length} done
|
|
357
|
-
</span>
|
|
358
|
-
|
|
359
|
-
{/* run next */}
|
|
360
|
-
{nextPendingIdx >= 0 && (
|
|
361
|
-
<button
|
|
362
|
-
onClick={() => runStep(nextPendingIdx)}
|
|
363
|
-
disabled={running}
|
|
364
|
-
title={running ? t.hints.workflowRunning : undefined}
|
|
365
|
-
style={{
|
|
366
|
-
display: 'flex', alignItems: 'center', gap: 5,
|
|
367
|
-
padding: '4px 12px', borderRadius: 7, fontSize: '0.75rem',
|
|
368
|
-
cursor: running ? 'not-allowed' : 'pointer',
|
|
369
|
-
border: 'none', background: running ? 'var(--muted)' : 'var(--amber)',
|
|
370
|
-
color: running ? 'var(--muted-foreground)' : 'var(--amber-foreground)',
|
|
371
|
-
opacity: running ? 0.7 : 1,
|
|
372
|
-
}}
|
|
373
|
-
>
|
|
374
|
-
{running ? <Loader2 size={11} style={{ animation: 'spin 1s linear infinite' }} /> : <Play size={11} />}
|
|
375
|
-
Run next
|
|
376
|
-
</button>
|
|
377
|
-
)}
|
|
378
|
-
|
|
379
|
-
{/* reset */}
|
|
380
|
-
<button
|
|
381
|
-
onClick={reset}
|
|
382
|
-
style={{ padding: '4px 10px', borderRadius: 7, fontSize: '0.75rem', cursor: 'pointer', border: '1px solid var(--border)', background: 'transparent', color: 'var(--muted-foreground)', display: 'flex', alignItems: 'center', gap: 4 }}
|
|
383
|
-
>
|
|
384
|
-
<RotateCcw size={11} /> Reset
|
|
385
|
-
</button>
|
|
386
|
-
</div>
|
|
387
|
-
</div>
|
|
388
|
-
|
|
389
|
-
{/* step list */}
|
|
390
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
391
|
-
{steps.map((step, i) => (
|
|
392
|
-
<StepCard
|
|
393
|
-
key={i}
|
|
394
|
-
step={step}
|
|
395
|
-
isActive={i === nextPendingIdx}
|
|
396
|
-
canRun={!running}
|
|
397
|
-
onRun={() => runStep(i)}
|
|
398
|
-
onSkip={() => skipStep(i)}
|
|
399
|
-
/>
|
|
400
|
-
))}
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
<style>{`
|
|
404
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
405
|
-
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.3; } }
|
|
406
|
-
`}</style>
|
|
407
|
-
</div>
|
|
408
|
-
);
|
|
409
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { RendererDefinition } from '@/lib/renderers/registry';
|
|
2
|
-
|
|
3
|
-
export const manifest: RendererDefinition = {
|
|
4
|
-
id: 'workflow',
|
|
5
|
-
name: 'Workflow Runner',
|
|
6
|
-
description: 'Parses step-by-step workflow markdown into an interactive runner. Execute steps sequentially with AI assistance.',
|
|
7
|
-
author: 'MindOS',
|
|
8
|
-
icon: '⚡',
|
|
9
|
-
tags: ['workflow', 'automation', 'steps', 'ai'],
|
|
10
|
-
builtin: true,
|
|
11
|
-
entryPath: 'Workflow.md',
|
|
12
|
-
match: ({ filePath }) => /\b(Workflow|workflow|WORKFLOW)\b.*\.md$/i.test(filePath),
|
|
13
|
-
load: () => import('./WorkflowRenderer').then(m => ({ default: m.WorkflowRenderer })),
|
|
14
|
-
};
|