helixevo 0.2.21 → 0.2.23
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/dashboard/app/page.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDashboardSummary, loadFrontier, loadHistory, loadGraph, loadFailures } from '@/lib/data'
|
|
1
|
+
import { getDashboardSummary, loadFrontier, loadHistory, loadGraph, loadFailures, listProjects } from '@/lib/data'
|
|
2
2
|
import { OverviewActions } from '@/components/overview-actions'
|
|
3
3
|
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
@@ -50,6 +50,9 @@ export default function Overview() {
|
|
|
50
50
|
hasSkills={graph.nodes.length > 0}
|
|
51
51
|
hasFailures={failures.length > 0}
|
|
52
52
|
hasEdges={graph.edges.length > 0}
|
|
53
|
+
unresolvedCount={s.failures.unresolved}
|
|
54
|
+
skillCount={graph.nodes.length}
|
|
55
|
+
projectList={listProjects()}
|
|
53
56
|
/>
|
|
54
57
|
</div>
|
|
55
58
|
|
|
@@ -62,6 +65,49 @@ export default function Overview() {
|
|
|
62
65
|
<StatCard value={frontier.programs.length} label="Frontier" color="var(--text-secondary)" sub={`/${frontier.capacity} capacity`} accent="▲" />
|
|
63
66
|
</div>
|
|
64
67
|
|
|
68
|
+
{/* Unresolved Failures */}
|
|
69
|
+
{failures.filter(f => !f.resolved).length > 0 && (
|
|
70
|
+
<div className="card" style={{ marginBottom: 20 }}>
|
|
71
|
+
<div className="card-body">
|
|
72
|
+
<div className="card-header-label">
|
|
73
|
+
Unresolved Corrections ({failures.filter(f => !f.resolved).length})
|
|
74
|
+
</div>
|
|
75
|
+
<div style={{ fontSize: 12, color: 'var(--text-dim)', marginBottom: 14, lineHeight: 1.5 }}>
|
|
76
|
+
These are corrections you made that haven't been incorporated into skills yet.
|
|
77
|
+
Click <strong style={{ color: 'var(--text)' }}>Evolve</strong> above to improve skills based on these corrections.
|
|
78
|
+
</div>
|
|
79
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
80
|
+
{failures.filter(f => !f.resolved).slice(0, 5).map((f, i) => (
|
|
81
|
+
<div key={`${f.id}-${i}`} style={{
|
|
82
|
+
padding: '10px 14px', borderRadius: 'var(--radius)',
|
|
83
|
+
background: 'var(--bg-section)',
|
|
84
|
+
borderLeft: '3px solid var(--yellow)',
|
|
85
|
+
}}>
|
|
86
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)', marginBottom: 3 }}>
|
|
87
|
+
{f.userRequest.slice(0, 100)}{f.userRequest.length > 100 ? '...' : ''}
|
|
88
|
+
</div>
|
|
89
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.4 }}>
|
|
90
|
+
Correction: {f.correction.slice(0, 120)}{f.correction.length > 120 ? '...' : ''}
|
|
91
|
+
</div>
|
|
92
|
+
<div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
|
|
93
|
+
<span className="badge badge-yellow">{f.correctionType}</span>
|
|
94
|
+
{f.project && <span className="badge badge-gray">{f.project}</span>}
|
|
95
|
+
<span style={{ fontSize: 10, color: 'var(--text-muted)' }}>
|
|
96
|
+
{new Date(f.timestamp).toLocaleDateString()}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
{failures.filter(f => !f.resolved).length > 5 && (
|
|
102
|
+
<div style={{ fontSize: 11, color: 'var(--text-muted)', textAlign: 'center', padding: '6px 0' }}>
|
|
103
|
+
+{failures.filter(f => !f.resolved).length - 5} more corrections
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
65
111
|
<div className="grid-2">
|
|
66
112
|
{/* Frontier */}
|
|
67
113
|
<div className="card">
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef } from 'react'
|
|
4
|
+
|
|
5
|
+
interface Discovery {
|
|
6
|
+
id: string; title: string; summary: string; score: number; gap: string; lastAccessed: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Draft {
|
|
10
|
+
id: string; skillName: string; avgScore: number; passRate: number; iteration: number; hypothesis: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface KnowledgeBuffer {
|
|
14
|
+
discoveries: Discovery[]
|
|
15
|
+
drafts: Draft[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
buffer: KnowledgeBuffer
|
|
20
|
+
projects: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
24
|
+
|
|
25
|
+
const PIPELINE_STEPS = [
|
|
26
|
+
{
|
|
27
|
+
step: 1,
|
|
28
|
+
title: 'Identify Gaps',
|
|
29
|
+
desc: 'Analyze your existing skills and project context to find what capabilities are missing.',
|
|
30
|
+
icon: '🔍',
|
|
31
|
+
color: 'var(--blue)',
|
|
32
|
+
bgColor: 'var(--blue-light)',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
step: 2,
|
|
36
|
+
title: 'Web Search',
|
|
37
|
+
desc: 'Search the web for the latest best practices, tools, and techniques to fill each gap.',
|
|
38
|
+
icon: '🌐',
|
|
39
|
+
color: 'var(--purple)',
|
|
40
|
+
bgColor: 'var(--purple-light)',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
step: 3,
|
|
44
|
+
title: 'Draft Skills',
|
|
45
|
+
desc: 'Generate hypotheses and draft new SKILL.md files from the discoveries.',
|
|
46
|
+
icon: '📝',
|
|
47
|
+
color: 'var(--yellow)',
|
|
48
|
+
bgColor: 'var(--yellow-light)',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
step: 4,
|
|
52
|
+
title: 'Test & Score',
|
|
53
|
+
desc: 'Test each draft against scenarios. Skills scoring ≥7/10 with ≥67% pass rate are accepted.',
|
|
54
|
+
icon: '✓',
|
|
55
|
+
color: 'var(--green)',
|
|
56
|
+
bgColor: 'var(--green-light)',
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
export default function ResearchClient({ buffer, projects }: Props) {
|
|
61
|
+
const [runState, setRunState] = useState<RunState>('idle')
|
|
62
|
+
const [output, setOutput] = useState<string | null>(null)
|
|
63
|
+
const abortRef = useRef<AbortController | null>(null)
|
|
64
|
+
|
|
65
|
+
const handleRun = async () => {
|
|
66
|
+
setRunState('running')
|
|
67
|
+
setOutput(null)
|
|
68
|
+
const controller = new AbortController()
|
|
69
|
+
abortRef.current = controller
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch('/api/run', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify({ command: 'research' }),
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
})
|
|
78
|
+
const data = await res.json()
|
|
79
|
+
if (data.stopped) {
|
|
80
|
+
setOutput(data.output ?? 'Stopped')
|
|
81
|
+
setRunState('stopped')
|
|
82
|
+
} else {
|
|
83
|
+
setOutput(data.output ?? data.error ?? 'No output')
|
|
84
|
+
setRunState(data.success ? 'success' : 'error')
|
|
85
|
+
}
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
88
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
89
|
+
setOutput('Stopped by user')
|
|
90
|
+
setRunState('stopped')
|
|
91
|
+
} else {
|
|
92
|
+
setOutput('Network error')
|
|
93
|
+
setRunState('error')
|
|
94
|
+
}
|
|
95
|
+
} finally {
|
|
96
|
+
abortRef.current = null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleStop = async () => {
|
|
101
|
+
if (abortRef.current) abortRef.current.abort()
|
|
102
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const nearPass = buffer.drafts.filter(d => d.passRate >= 0.67)
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div>
|
|
109
|
+
<div className="page-header">
|
|
110
|
+
<h1 className="page-title">Proactive Research</h1>
|
|
111
|
+
<p className="page-desc">
|
|
112
|
+
Discover new skills by searching the web for techniques and best practices your skill set doesn't cover yet
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Pipeline Visualization */}
|
|
117
|
+
<div className="card" style={{ marginBottom: 24, padding: '20px 24px' }}>
|
|
118
|
+
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: 1.5, marginBottom: 16 }}>
|
|
119
|
+
How Research Works
|
|
120
|
+
</div>
|
|
121
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0 }}>
|
|
122
|
+
{PIPELINE_STEPS.map((step, i) => (
|
|
123
|
+
<div key={step.step} style={{ display: 'flex', alignItems: 'flex-start' }}>
|
|
124
|
+
<div style={{ flex: 1 }}>
|
|
125
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
|
126
|
+
<div style={{
|
|
127
|
+
width: 32, height: 32, borderRadius: 9,
|
|
128
|
+
background: step.bgColor,
|
|
129
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
130
|
+
fontSize: 14, flexShrink: 0,
|
|
131
|
+
}}>
|
|
132
|
+
{step.icon}
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<div style={{ fontSize: 9, fontWeight: 600, color: step.color, letterSpacing: 0.5 }}>STEP {step.step}</div>
|
|
136
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{step.title}</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.5, paddingRight: 12 }}>
|
|
140
|
+
{step.desc}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
{i < 3 && (
|
|
144
|
+
<svg width="20" height="60" viewBox="0 0 20 60" style={{ flexShrink: 0, marginTop: 8 }}>
|
|
145
|
+
<path d="M4 30h12M12 24l4 6-4 6" stroke="var(--border)" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
146
|
+
</svg>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Run button */}
|
|
153
|
+
<div style={{ marginTop: 20, paddingTop: 16, borderTop: '1px solid var(--border)' }}>
|
|
154
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
155
|
+
{runState === 'idle' && (
|
|
156
|
+
<button
|
|
157
|
+
onClick={handleRun}
|
|
158
|
+
style={{
|
|
159
|
+
display: 'flex', alignItems: 'center', gap: 6,
|
|
160
|
+
padding: '9px 20px',
|
|
161
|
+
background: 'var(--teal, var(--green))',
|
|
162
|
+
color: '#fff', border: 'none',
|
|
163
|
+
borderRadius: 'var(--radius)',
|
|
164
|
+
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
165
|
+
transition: 'opacity 0.15s',
|
|
166
|
+
}}
|
|
167
|
+
onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
|
|
168
|
+
onMouseOut={e => (e.currentTarget.style.opacity = '1')}
|
|
169
|
+
>
|
|
170
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
171
|
+
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
172
|
+
</svg>
|
|
173
|
+
Run Research
|
|
174
|
+
</button>
|
|
175
|
+
)}
|
|
176
|
+
{runState === 'running' && (
|
|
177
|
+
<button
|
|
178
|
+
onClick={handleStop}
|
|
179
|
+
style={{
|
|
180
|
+
display: 'flex', alignItems: 'center', gap: 6,
|
|
181
|
+
padding: '9px 20px',
|
|
182
|
+
background: 'var(--red)', color: '#fff', border: 'none',
|
|
183
|
+
borderRadius: 'var(--radius)',
|
|
184
|
+
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
188
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
189
|
+
</svg>
|
|
190
|
+
Stop Research
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
193
|
+
{(runState === 'success' || runState === 'error' || runState === 'stopped') && (
|
|
194
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
195
|
+
<button onClick={handleRun} style={{
|
|
196
|
+
padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
197
|
+
borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
198
|
+
color: 'var(--text-secondary)',
|
|
199
|
+
}}>
|
|
200
|
+
Run Again
|
|
201
|
+
</button>
|
|
202
|
+
<button onClick={() => window.location.reload()} style={{
|
|
203
|
+
padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
204
|
+
borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
205
|
+
color: 'var(--text-secondary)',
|
|
206
|
+
}}>
|
|
207
|
+
Refresh Data
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
212
|
+
{runState === 'running' && 'Searching the web and testing hypotheses...'}
|
|
213
|
+
{runState === 'success' && 'Research complete — refresh to see new discoveries'}
|
|
214
|
+
{runState === 'error' && 'Research failed — check output below'}
|
|
215
|
+
{runState === 'stopped' && 'Research stopped'}
|
|
216
|
+
{runState === 'idle' && 'Analyzes all your skills, searches for gaps, and drafts new skills'}
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Output panel */}
|
|
223
|
+
{runState !== 'idle' && output && (
|
|
224
|
+
<div className="card" style={{ marginBottom: 24, overflow: 'hidden' }}>
|
|
225
|
+
<div style={{
|
|
226
|
+
padding: '10px 16px',
|
|
227
|
+
background: runState === 'success' ? 'var(--green-light)' : runState === 'error' ? 'var(--red-light)' : 'var(--bg-section)',
|
|
228
|
+
borderBottom: '1px solid var(--border)',
|
|
229
|
+
fontSize: 12, fontWeight: 600,
|
|
230
|
+
color: runState === 'success' ? 'var(--green)' : runState === 'error' ? 'var(--red)' : 'var(--text-secondary)',
|
|
231
|
+
}}>
|
|
232
|
+
{runState === 'running' && '● Running research pipeline...'}
|
|
233
|
+
{runState === 'success' && '✓ Research completed'}
|
|
234
|
+
{runState === 'error' && '✗ Research failed'}
|
|
235
|
+
{runState === 'stopped' && '■ Research stopped'}
|
|
236
|
+
</div>
|
|
237
|
+
<pre style={{
|
|
238
|
+
padding: '14px 16px', margin: 0,
|
|
239
|
+
fontSize: 11, lineHeight: 1.55,
|
|
240
|
+
fontFamily: 'var(--font-mono)',
|
|
241
|
+
color: 'var(--text-secondary)',
|
|
242
|
+
maxHeight: 300, overflow: 'auto',
|
|
243
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
244
|
+
}}>
|
|
245
|
+
{output}
|
|
246
|
+
</pre>
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{/* Summary Stats */}
|
|
251
|
+
<div className="grid-3" style={{ marginBottom: 24 }}>
|
|
252
|
+
<div className="stat-card">
|
|
253
|
+
<div className="stat-value" style={{ color: 'var(--blue)' }}>{buffer.discoveries.length}</div>
|
|
254
|
+
<div className="stat-label">Discoveries</div>
|
|
255
|
+
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 50 capacity</div>
|
|
256
|
+
</div>
|
|
257
|
+
<div className="stat-card">
|
|
258
|
+
<div className="stat-value" style={{ color: 'var(--yellow)' }}>{buffer.drafts.length}</div>
|
|
259
|
+
<div className="stat-label">Drafts In Progress</div>
|
|
260
|
+
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 10 capacity</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="stat-card">
|
|
263
|
+
<div className="stat-value" style={{ color: 'var(--green)' }}>{nearPass.length}</div>
|
|
264
|
+
<div className="stat-label">Near-Pass Drafts</div>
|
|
265
|
+
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>≥67% pass rate — close to becoming skills</div>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div className="grid-2">
|
|
270
|
+
{/* Discoveries */}
|
|
271
|
+
<div className="card">
|
|
272
|
+
<div className="card-body">
|
|
273
|
+
<div className="card-header-label">Discoveries ({buffer.discoveries.length}/50)</div>
|
|
274
|
+
{buffer.discoveries.length === 0 ? (
|
|
275
|
+
<div className="empty-state" style={{ padding: 32 }}>
|
|
276
|
+
<div className="empty-state-icon">
|
|
277
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
278
|
+
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
279
|
+
</svg>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="empty-state-title">No discoveries yet</div>
|
|
282
|
+
<div className="empty-state-desc">
|
|
283
|
+
Click <strong>Run Research</strong> above to search the web for techniques your skills don't cover
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
) : (
|
|
287
|
+
buffer.discoveries.sort((a, b) => b.score - a.score).map(d => (
|
|
288
|
+
<div key={d.id} style={{
|
|
289
|
+
padding: '12px 0',
|
|
290
|
+
borderBottom: '1px solid var(--border-subtle)',
|
|
291
|
+
}}>
|
|
292
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
293
|
+
<span style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.3 }}>{d.title}</span>
|
|
294
|
+
<span className="score-pill" style={{ color: 'var(--blue)', flexShrink: 0, marginLeft: 8 }}>
|
|
295
|
+
{(d.score * 100).toFixed(0)}%
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
299
|
+
{d.summary.slice(0, 140)}
|
|
300
|
+
</div>
|
|
301
|
+
<span className="badge badge-blue">{d.gap}</span>
|
|
302
|
+
</div>
|
|
303
|
+
))
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* Drafts */}
|
|
309
|
+
<div className="card">
|
|
310
|
+
<div className="card-body">
|
|
311
|
+
<div className="card-header-label">Skill Drafts ({buffer.drafts.length}/10)</div>
|
|
312
|
+
{buffer.drafts.length === 0 ? (
|
|
313
|
+
<div className="empty-state" style={{ padding: 32 }}>
|
|
314
|
+
<div className="empty-state-icon">
|
|
315
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
316
|
+
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
317
|
+
</svg>
|
|
318
|
+
</div>
|
|
319
|
+
<div className="empty-state-title">No drafts yet</div>
|
|
320
|
+
<div className="empty-state-desc">
|
|
321
|
+
Drafts are skill prototypes that almost passed quality checks. Running research again improves them iteratively.
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
) : (
|
|
325
|
+
buffer.drafts.sort((a, b) => b.avgScore - a.avgScore).map(d => (
|
|
326
|
+
<div key={d.id} style={{
|
|
327
|
+
padding: '12px 0',
|
|
328
|
+
borderBottom: '1px solid var(--border-subtle)',
|
|
329
|
+
}}>
|
|
330
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
331
|
+
<span style={{ fontSize: 13, fontWeight: 600 }}>{d.skillName}</span>
|
|
332
|
+
<span className={`badge ${d.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>
|
|
333
|
+
{d.avgScore.toFixed(1)}/10 · iter {d.iteration}
|
|
334
|
+
</span>
|
|
335
|
+
</div>
|
|
336
|
+
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
337
|
+
{d.hypothesis.slice(0, 140)}
|
|
338
|
+
</div>
|
|
339
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
340
|
+
<span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Pass rate:</span>
|
|
341
|
+
<div className="score-track" style={{ width: 60, height: 4 }}>
|
|
342
|
+
<div className="score-fill" style={{
|
|
343
|
+
width: `${d.passRate * 100}%`,
|
|
344
|
+
background: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)',
|
|
345
|
+
}} />
|
|
346
|
+
</div>
|
|
347
|
+
<b style={{ fontSize: 11, color: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
|
|
348
|
+
{(d.passRate * 100).toFixed(0)}%
|
|
349
|
+
</b>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
))
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
@@ -1,124 +1,11 @@
|
|
|
1
|
-
import { loadBuffer } from '@/lib/data'
|
|
1
|
+
import { loadBuffer, listProjects } from '@/lib/data'
|
|
2
|
+
import ResearchClient from './client'
|
|
2
3
|
|
|
3
4
|
export const dynamic = 'force-dynamic'
|
|
4
5
|
|
|
5
6
|
export default function ResearchPage() {
|
|
6
7
|
const buffer = loadBuffer()
|
|
8
|
+
const projects = listProjects()
|
|
7
9
|
|
|
8
|
-
return
|
|
9
|
-
<div>
|
|
10
|
-
<div className="page-header">
|
|
11
|
-
<h1 className="page-title">Proactive Research</h1>
|
|
12
|
-
<p className="page-desc">
|
|
13
|
-
Web search discoveries and skill drafts from the knowledge buffer
|
|
14
|
-
</p>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
{/* Summary Stats */}
|
|
18
|
-
<div className="grid-3" style={{ marginBottom: 28 }}>
|
|
19
|
-
<div className="stat-card">
|
|
20
|
-
<div className="stat-value" style={{ color: 'var(--blue)' }}>{buffer.discoveries.length}</div>
|
|
21
|
-
<div className="stat-label">Discoveries</div>
|
|
22
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 50 capacity</div>
|
|
23
|
-
</div>
|
|
24
|
-
<div className="stat-card">
|
|
25
|
-
<div className="stat-value" style={{ color: 'var(--yellow)' }}>{buffer.drafts.length}</div>
|
|
26
|
-
<div className="stat-label">Drafts</div>
|
|
27
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 10 capacity</div>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="stat-card">
|
|
30
|
-
<div className="stat-value" style={{ color: 'var(--green)' }}>
|
|
31
|
-
{buffer.drafts.filter(d => d.passRate >= 0.67).length}
|
|
32
|
-
</div>
|
|
33
|
-
<div className="stat-label">Near-Pass Drafts</div>
|
|
34
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>≥67% pass rate</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<div className="grid-2">
|
|
39
|
-
{/* Discoveries */}
|
|
40
|
-
<div className="card">
|
|
41
|
-
<div className="card-body">
|
|
42
|
-
<div className="card-header-label">Discoveries ({buffer.discoveries.length}/50)</div>
|
|
43
|
-
{buffer.discoveries.length === 0 ? (
|
|
44
|
-
<div className="empty-state" style={{ padding: 32 }}>
|
|
45
|
-
<div className="empty-state-icon">
|
|
46
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
47
|
-
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
48
|
-
</svg>
|
|
49
|
-
</div>
|
|
50
|
-
<div className="empty-state-title">No discoveries yet</div>
|
|
51
|
-
<div className="empty-state-desc">Run <code>helixevo research</code> to start proactive web research</div>
|
|
52
|
-
</div>
|
|
53
|
-
) : (
|
|
54
|
-
buffer.discoveries.sort((a, b) => b.score - a.score).map(d => (
|
|
55
|
-
<div key={d.id} style={{
|
|
56
|
-
padding: '12px 0',
|
|
57
|
-
borderBottom: '1px solid var(--border-subtle)',
|
|
58
|
-
}}>
|
|
59
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
60
|
-
<span style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.3 }}>{d.title}</span>
|
|
61
|
-
<span className="score-pill" style={{ color: 'var(--blue)', flexShrink: 0, marginLeft: 8 }}>
|
|
62
|
-
{(d.score * 100).toFixed(0)}%
|
|
63
|
-
</span>
|
|
64
|
-
</div>
|
|
65
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
66
|
-
{d.summary.slice(0, 140)}
|
|
67
|
-
</div>
|
|
68
|
-
<span className="badge badge-blue">{d.gap}</span>
|
|
69
|
-
</div>
|
|
70
|
-
))
|
|
71
|
-
)}
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Drafts */}
|
|
76
|
-
<div className="card">
|
|
77
|
-
<div className="card-body">
|
|
78
|
-
<div className="card-header-label">Drafts ({buffer.drafts.length}/10)</div>
|
|
79
|
-
{buffer.drafts.length === 0 ? (
|
|
80
|
-
<div className="empty-state" style={{ padding: 32 }}>
|
|
81
|
-
<div className="empty-state-icon">
|
|
82
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
83
|
-
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
84
|
-
</svg>
|
|
85
|
-
</div>
|
|
86
|
-
<div className="empty-state-title">No drafts yet</div>
|
|
87
|
-
<div className="empty-state-desc">Near-pass proposals are automatically saved here for future iteration</div>
|
|
88
|
-
</div>
|
|
89
|
-
) : (
|
|
90
|
-
buffer.drafts.sort((a, b) => b.avgScore - a.avgScore).map(d => (
|
|
91
|
-
<div key={d.id} style={{
|
|
92
|
-
padding: '12px 0',
|
|
93
|
-
borderBottom: '1px solid var(--border-subtle)',
|
|
94
|
-
}}>
|
|
95
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
96
|
-
<span style={{ fontSize: 13, fontWeight: 600 }}>{d.skillName}</span>
|
|
97
|
-
<span className={`badge ${d.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>
|
|
98
|
-
{d.avgScore.toFixed(1)}/10 · iter {d.iteration}
|
|
99
|
-
</span>
|
|
100
|
-
</div>
|
|
101
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
102
|
-
{d.hypothesis.slice(0, 140)}
|
|
103
|
-
</div>
|
|
104
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
105
|
-
<span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Pass rate:</span>
|
|
106
|
-
<div className="score-track" style={{ width: 60, height: 4 }}>
|
|
107
|
-
<div className="score-fill" style={{
|
|
108
|
-
width: `${d.passRate * 100}%`,
|
|
109
|
-
background: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)',
|
|
110
|
-
}} />
|
|
111
|
-
</div>
|
|
112
|
-
<b style={{ fontSize: 11, color: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
|
|
113
|
-
{(d.passRate * 100).toFixed(0)}%
|
|
114
|
-
</b>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
))
|
|
118
|
-
)}
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
)
|
|
10
|
+
return <ResearchClient buffer={buffer} projects={projects} />
|
|
124
11
|
}
|
|
@@ -6,69 +6,114 @@ interface OverviewActionsProps {
|
|
|
6
6
|
hasFailures: boolean
|
|
7
7
|
hasSkills: boolean
|
|
8
8
|
hasEdges: boolean
|
|
9
|
+
unresolvedCount: number
|
|
10
|
+
skillCount: number
|
|
11
|
+
projectList: string[]
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
// Small inline SVG flow diagrams for each action
|
|
15
|
+
function FlowDiagram({ steps, color }: { steps: string[]; color: string }) {
|
|
16
|
+
return (
|
|
17
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 0, marginTop: 8, marginBottom: 2 }}>
|
|
18
|
+
{steps.map((step, i) => (
|
|
19
|
+
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
|
20
|
+
<div style={{
|
|
21
|
+
fontSize: 9, fontWeight: 600, padding: '2px 7px',
|
|
22
|
+
background: i === steps.length - 1 ? `${color}18` : 'var(--bg-section)',
|
|
23
|
+
color: i === steps.length - 1 ? color : 'var(--text-dim)',
|
|
24
|
+
borderRadius: 4, whiteSpace: 'nowrap',
|
|
25
|
+
border: i === steps.length - 1 ? `1px solid ${color}30` : '1px solid var(--border-subtle)',
|
|
26
|
+
}}>
|
|
27
|
+
{step}
|
|
28
|
+
</div>
|
|
29
|
+
{i < steps.length - 1 && (
|
|
30
|
+
<svg width="14" height="8" viewBox="0 0 14 8" style={{ flexShrink: 0 }}>
|
|
31
|
+
<path d="M0 4h10M8 1l3 3-3 3" stroke={color} strokeWidth="1.2" fill="none" strokeLinecap="round" strokeLinejoin="round" opacity="0.5" />
|
|
32
|
+
</svg>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function OverviewActions({ hasFailures, hasSkills, hasEdges, unresolvedCount, skillCount, projectList }: OverviewActionsProps) {
|
|
12
41
|
const actions = [
|
|
13
42
|
{
|
|
14
43
|
id: 'graph-rebuild',
|
|
15
44
|
label: 'Organize Skills',
|
|
45
|
+
subtitle: `Analyze ${skillCount} skills to discover how they relate — dependencies, enhancements, and conflicts.`,
|
|
16
46
|
command: 'graph-rebuild',
|
|
17
47
|
icon: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1',
|
|
18
48
|
color: 'var(--purple)',
|
|
19
|
-
description: 'Use LLM to infer relationships between skills
|
|
49
|
+
description: 'Use LLM to infer relationships between skills',
|
|
20
50
|
disabled: !hasSkills,
|
|
21
51
|
disabledReason: 'No skills imported yet — run helixevo init first',
|
|
52
|
+
flowSteps: [`${skillCount} skills`, 'LLM analysis', 'Skill graph'],
|
|
22
53
|
},
|
|
23
54
|
{
|
|
24
55
|
id: 'generalize',
|
|
25
56
|
label: 'Generalize',
|
|
57
|
+
subtitle: 'Find common patterns across your skills and create higher-level abstract skills.',
|
|
26
58
|
command: 'generalize',
|
|
27
59
|
icon: 'M5 10l7-7m0 0l7 7m-7-7v18',
|
|
28
60
|
color: 'var(--blue)',
|
|
29
|
-
description: '
|
|
61
|
+
description: 'Promote cross-skill patterns to abstract parent skills',
|
|
30
62
|
disabled: !hasSkills,
|
|
31
63
|
disabledReason: 'No skills imported yet',
|
|
64
|
+
flowSteps: ['All skills', 'Pattern detection', 'Abstract skills'],
|
|
32
65
|
},
|
|
33
66
|
{
|
|
34
67
|
id: 'evolve',
|
|
35
|
-
label:
|
|
68
|
+
label: `Evolve${unresolvedCount > 0 ? ` (${unresolvedCount})` : ''}`,
|
|
69
|
+
subtitle: hasFailures
|
|
70
|
+
? `Use ${unresolvedCount} correction${unresolvedCount !== 1 ? 's' : ''} to propose and test skill improvements.`
|
|
71
|
+
: 'Use your corrections to improve skills. Corrections are captured automatically when you use Craft Agent.',
|
|
36
72
|
command: 'evolve',
|
|
37
73
|
icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6',
|
|
38
74
|
color: 'var(--green)',
|
|
39
|
-
description: 'Evolve skills based on captured
|
|
75
|
+
description: 'Evolve skills based on captured corrections',
|
|
40
76
|
disabled: !hasFailures,
|
|
41
|
-
disabledReason: 'No
|
|
77
|
+
disabledReason: 'No corrections yet — use Craft Agent normally. When you correct its output, those corrections are captured automatically.',
|
|
78
|
+
flowSteps: hasFailures
|
|
79
|
+
? [`${unresolvedCount} corrections`, 'Propose changes', 'Judge & test', 'Apply']
|
|
80
|
+
: ['Corrections', 'Propose', 'Judge', 'Apply'],
|
|
42
81
|
},
|
|
43
82
|
{
|
|
44
83
|
id: 'health',
|
|
45
84
|
label: 'Health Check',
|
|
85
|
+
subtitle: 'Assess your skill network for coverage gaps, cohesion issues, and imbalances.',
|
|
46
86
|
command: 'health',
|
|
47
87
|
icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
48
88
|
color: 'var(--yellow)',
|
|
49
|
-
description: 'Assess network cohesion
|
|
89
|
+
description: 'Assess network cohesion and coverage',
|
|
50
90
|
disabled: !hasSkills,
|
|
51
91
|
disabledReason: 'No skills imported yet',
|
|
92
|
+
flowSteps: ['Skill network', 'Coverage analysis', 'Health report'],
|
|
52
93
|
},
|
|
53
94
|
{
|
|
54
95
|
id: 'graph-optimize',
|
|
55
|
-
label: 'Optimize',
|
|
96
|
+
label: 'Optimize Network',
|
|
97
|
+
subtitle: 'Find skills that should be merged, split, or that conflict with each other.',
|
|
56
98
|
command: 'graph-optimize',
|
|
57
99
|
icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
|
|
58
100
|
color: 'var(--text-secondary)',
|
|
59
|
-
description: 'Detect merge/split
|
|
101
|
+
description: 'Detect merge/split opportunities',
|
|
60
102
|
disabled: !hasEdges,
|
|
61
|
-
disabledReason: 'Run "Organize Skills" first to
|
|
103
|
+
disabledReason: 'Run "Organize Skills" first to discover relationships between skills.',
|
|
104
|
+
flowSteps: ['Relationships', 'Detect overlaps', 'Merge/split/flag'],
|
|
62
105
|
},
|
|
63
106
|
{
|
|
64
107
|
id: 'research',
|
|
65
|
-
label: '
|
|
108
|
+
label: 'Discover New Skills',
|
|
109
|
+
subtitle: `Search the web for techniques your ${skillCount} skills don't cover yet, then draft new skills.`,
|
|
66
110
|
command: 'research',
|
|
67
111
|
icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
|
|
68
112
|
color: 'var(--purple)',
|
|
69
|
-
description: 'Proactive web research
|
|
113
|
+
description: 'Proactive web research → discover → draft → test',
|
|
114
|
+
flowSteps: ['Find gaps', 'Web search', 'Draft skills', 'Test & score'],
|
|
70
115
|
},
|
|
71
116
|
]
|
|
72
117
|
|
|
73
|
-
return <QuickActions actions={actions} />
|
|
118
|
+
return <QuickActions actions={actions} flowDiagram={FlowDiagram} />
|
|
74
119
|
}
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useRef } from 'react'
|
|
3
|
+
import { useState, useRef, type ReactNode, type ComponentType } from 'react'
|
|
4
4
|
|
|
5
5
|
interface Action {
|
|
6
6
|
id: string
|
|
7
7
|
label: string
|
|
8
|
+
subtitle?: string
|
|
8
9
|
command: string
|
|
9
10
|
icon: string
|
|
10
11
|
color: string
|
|
11
12
|
description: string
|
|
12
13
|
disabled?: boolean
|
|
13
14
|
disabledReason?: string
|
|
15
|
+
flowSteps?: string[]
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
interface QuickActionsProps {
|
|
17
19
|
actions: Action[]
|
|
20
|
+
flowDiagram?: ComponentType<{ steps: string[]; color: string }>
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
21
24
|
|
|
22
|
-
export function QuickActions({ actions }: QuickActionsProps) {
|
|
25
|
+
export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActionsProps) {
|
|
23
26
|
const [running, setRunning] = useState<string | null>(null)
|
|
24
27
|
const [state, setState] = useState<RunState>('idle')
|
|
25
28
|
const [output, setOutput] = useState<string | null>(null)
|
|
@@ -51,7 +54,6 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
51
54
|
}
|
|
52
55
|
} catch (err: unknown) {
|
|
53
56
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
54
|
-
// Client-side abort — also kill server-side process
|
|
55
57
|
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
56
58
|
setOutput('Stopped by user')
|
|
57
59
|
setState('stopped')
|
|
@@ -65,14 +67,8 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const handleStop = async () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
abortRef.current.abort()
|
|
71
|
-
}
|
|
72
|
-
// Also kill server-side process
|
|
73
|
-
try {
|
|
74
|
-
await fetch('/api/run', { method: 'DELETE' })
|
|
75
|
-
} catch {}
|
|
70
|
+
if (abortRef.current) abortRef.current.abort()
|
|
71
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
const handleClose = () => {
|
|
@@ -98,7 +94,7 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
98
94
|
|
|
99
95
|
return (
|
|
100
96
|
<>
|
|
101
|
-
<div style={{ display: '
|
|
97
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 10 }}>
|
|
102
98
|
{actions.map(action => (
|
|
103
99
|
<button
|
|
104
100
|
key={action.id}
|
|
@@ -106,34 +102,60 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
106
102
|
disabled={action.disabled || (running !== null && running !== action.id)}
|
|
107
103
|
title={action.disabled ? action.disabledReason : action.description}
|
|
108
104
|
style={{
|
|
109
|
-
display: 'flex', alignItems: '
|
|
110
|
-
padding: '
|
|
105
|
+
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 4,
|
|
106
|
+
padding: '14px 16px',
|
|
111
107
|
background: action.disabled ? 'var(--bg-section)' : 'var(--bg-card)',
|
|
112
108
|
border: `1px solid ${running === action.id && state === 'running' ? action.color : 'var(--border)'}`,
|
|
113
|
-
borderRadius: 'var(--radius)',
|
|
109
|
+
borderRadius: 'var(--radius-lg)',
|
|
114
110
|
fontSize: 12,
|
|
115
111
|
fontWeight: 600,
|
|
116
112
|
color: action.disabled ? 'var(--text-muted)' : 'var(--text-secondary)',
|
|
117
113
|
cursor: action.disabled ? 'not-allowed' : 'pointer',
|
|
118
|
-
opacity: (running !== null && running !== action.id) ? 0.
|
|
119
|
-
transition: 'all 0.
|
|
114
|
+
opacity: (running !== null && running !== action.id) ? 0.4 : 1,
|
|
115
|
+
transition: 'all 0.2s',
|
|
116
|
+
textAlign: 'left',
|
|
120
117
|
}}
|
|
121
118
|
>
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
119
|
+
{/* Header: icon + label */}
|
|
120
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 7, width: '100%' }}>
|
|
121
|
+
{running === action.id && state === 'running' ? (
|
|
122
|
+
<span style={{
|
|
123
|
+
width: 16, height: 16, border: '2px solid var(--border)',
|
|
124
|
+
borderTopColor: action.color, borderRadius: '50%',
|
|
125
|
+
animation: 'actionSpin 0.8s linear infinite',
|
|
126
|
+
flexShrink: 0,
|
|
127
|
+
}} />
|
|
128
|
+
) : (
|
|
129
|
+
<div style={{
|
|
130
|
+
width: 26, height: 26, borderRadius: 7, flexShrink: 0,
|
|
131
|
+
background: action.disabled ? 'var(--bg-hover)' : `${action.color}12`,
|
|
132
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
133
|
+
}}>
|
|
134
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
|
135
|
+
stroke={action.disabled ? 'var(--text-muted)' : action.color}
|
|
136
|
+
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
137
|
+
<path d={action.icon} />
|
|
138
|
+
</svg>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
<span style={{ fontSize: 13 }}>{action.label}</span>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Subtitle */}
|
|
145
|
+
{action.subtitle && (
|
|
146
|
+
<div style={{
|
|
147
|
+
fontSize: 11, fontWeight: 400, lineHeight: 1.45,
|
|
148
|
+
color: action.disabled ? 'var(--text-muted)' : 'var(--text-dim)',
|
|
149
|
+
marginTop: 2,
|
|
150
|
+
}}>
|
|
151
|
+
{action.disabled ? action.disabledReason : action.subtitle}
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
{/* Flow diagram */}
|
|
156
|
+
{FlowDiagram && action.flowSteps && !action.disabled && (
|
|
157
|
+
<FlowDiagram steps={action.flowSteps} color={action.color} />
|
|
135
158
|
)}
|
|
136
|
-
{action.label}
|
|
137
159
|
</button>
|
|
138
160
|
))}
|
|
139
161
|
</div>
|
|
@@ -141,13 +163,12 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
141
163
|
{/* Output panel */}
|
|
142
164
|
{running && state !== 'idle' && (
|
|
143
165
|
<div style={{
|
|
144
|
-
marginTop:
|
|
166
|
+
marginTop: 14,
|
|
145
167
|
background: 'var(--bg-card)',
|
|
146
168
|
border: `1px solid ${stateBorderColor}`,
|
|
147
169
|
borderRadius: 'var(--radius-lg)',
|
|
148
170
|
overflow: 'hidden',
|
|
149
171
|
}}>
|
|
150
|
-
{/* Header */}
|
|
151
172
|
<div style={{
|
|
152
173
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
153
174
|
padding: '10px 14px',
|
|
@@ -206,18 +227,13 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
206
227
|
</div>
|
|
207
228
|
</div>
|
|
208
229
|
|
|
209
|
-
{/* Output */}
|
|
210
230
|
<pre style={{
|
|
211
|
-
padding: '12px 14px',
|
|
212
|
-
|
|
213
|
-
fontSize: 11,
|
|
214
|
-
lineHeight: 1.5,
|
|
231
|
+
padding: '12px 14px', margin: 0,
|
|
232
|
+
fontSize: 11, lineHeight: 1.5,
|
|
215
233
|
fontFamily: 'var(--font-mono)',
|
|
216
234
|
color: 'var(--text-secondary)',
|
|
217
|
-
maxHeight: 400,
|
|
218
|
-
|
|
219
|
-
whiteSpace: 'pre-wrap',
|
|
220
|
-
wordBreak: 'break-word',
|
|
235
|
+
maxHeight: 400, overflow: 'auto',
|
|
236
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
221
237
|
}}>
|
|
222
238
|
{output ?? 'Waiting for output...'}
|
|
223
239
|
</pre>
|
package/package.json
CHANGED