helixevo 0.2.22 → 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'
|
|
@@ -51,6 +51,8 @@ export default function Overview() {
|
|
|
51
51
|
hasFailures={failures.length > 0}
|
|
52
52
|
hasEdges={graph.edges.length > 0}
|
|
53
53
|
unresolvedCount={s.failures.unresolved}
|
|
54
|
+
skillCount={graph.nodes.length}
|
|
55
|
+
projectList={listProjects()}
|
|
54
56
|
/>
|
|
55
57
|
</div>
|
|
56
58
|
|
|
@@ -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
|
}
|
|
@@ -7,77 +7,113 @@ interface OverviewActionsProps {
|
|
|
7
7
|
hasSkills: boolean
|
|
8
8
|
hasEdges: boolean
|
|
9
9
|
unresolvedCount: number
|
|
10
|
+
skillCount: number
|
|
11
|
+
projectList: string[]
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
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) {
|
|
13
41
|
const actions = [
|
|
14
42
|
{
|
|
15
43
|
id: 'graph-rebuild',
|
|
16
44
|
label: 'Organize Skills',
|
|
17
|
-
subtitle:
|
|
45
|
+
subtitle: `Analyze ${skillCount} skills to discover how they relate — dependencies, enhancements, and conflicts.`,
|
|
18
46
|
command: 'graph-rebuild',
|
|
19
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',
|
|
20
48
|
color: 'var(--purple)',
|
|
21
|
-
description: 'Use LLM to infer relationships between skills
|
|
49
|
+
description: 'Use LLM to infer relationships between skills',
|
|
22
50
|
disabled: !hasSkills,
|
|
23
51
|
disabledReason: 'No skills imported yet — run helixevo init first',
|
|
52
|
+
flowSteps: [`${skillCount} skills`, 'LLM analysis', 'Skill graph'],
|
|
24
53
|
},
|
|
25
54
|
{
|
|
26
55
|
id: 'generalize',
|
|
27
56
|
label: 'Generalize',
|
|
28
|
-
subtitle: 'Find common patterns across
|
|
57
|
+
subtitle: 'Find common patterns across your skills and create higher-level abstract skills.',
|
|
29
58
|
command: 'generalize',
|
|
30
59
|
icon: 'M5 10l7-7m0 0l7 7m-7-7v18',
|
|
31
60
|
color: 'var(--blue)',
|
|
32
|
-
description: '
|
|
61
|
+
description: 'Promote cross-skill patterns to abstract parent skills',
|
|
33
62
|
disabled: !hasSkills,
|
|
34
63
|
disabledReason: 'No skills imported yet',
|
|
64
|
+
flowSteps: ['All skills', 'Pattern detection', 'Abstract skills'],
|
|
35
65
|
},
|
|
36
66
|
{
|
|
37
67
|
id: 'evolve',
|
|
38
|
-
label: `Evolve${unresolvedCount > 0 ? ` (${unresolvedCount}
|
|
68
|
+
label: `Evolve${unresolvedCount > 0 ? ` (${unresolvedCount})` : ''}`,
|
|
39
69
|
subtitle: hasFailures
|
|
40
|
-
? `
|
|
41
|
-
: '
|
|
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.',
|
|
42
72
|
command: 'evolve',
|
|
43
73
|
icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6',
|
|
44
74
|
color: 'var(--green)',
|
|
45
|
-
description: 'Evolve skills based on captured
|
|
75
|
+
description: 'Evolve skills based on captured corrections',
|
|
46
76
|
disabled: !hasFailures,
|
|
47
|
-
disabledReason: 'No corrections
|
|
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'],
|
|
48
81
|
},
|
|
49
82
|
{
|
|
50
83
|
id: 'health',
|
|
51
84
|
label: 'Health Check',
|
|
52
|
-
subtitle: '
|
|
85
|
+
subtitle: 'Assess your skill network for coverage gaps, cohesion issues, and imbalances.',
|
|
53
86
|
command: 'health',
|
|
54
87
|
icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
55
88
|
color: 'var(--yellow)',
|
|
56
|
-
description: 'Assess network cohesion
|
|
89
|
+
description: 'Assess network cohesion and coverage',
|
|
57
90
|
disabled: !hasSkills,
|
|
58
91
|
disabledReason: 'No skills imported yet',
|
|
92
|
+
flowSteps: ['Skill network', 'Coverage analysis', 'Health report'],
|
|
59
93
|
},
|
|
60
94
|
{
|
|
61
95
|
id: 'graph-optimize',
|
|
62
|
-
label: 'Optimize',
|
|
63
|
-
subtitle: 'Find skills that should be merged, split, or
|
|
96
|
+
label: 'Optimize Network',
|
|
97
|
+
subtitle: 'Find skills that should be merged, split, or that conflict with each other.',
|
|
64
98
|
command: 'graph-optimize',
|
|
65
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',
|
|
66
100
|
color: 'var(--text-secondary)',
|
|
67
|
-
description: 'Detect merge/split
|
|
101
|
+
description: 'Detect merge/split opportunities',
|
|
68
102
|
disabled: !hasEdges,
|
|
69
|
-
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'],
|
|
70
105
|
},
|
|
71
106
|
{
|
|
72
107
|
id: 'research',
|
|
73
|
-
label: '
|
|
74
|
-
subtitle:
|
|
108
|
+
label: 'Discover New Skills',
|
|
109
|
+
subtitle: `Search the web for techniques your ${skillCount} skills don't cover yet, then draft new skills.`,
|
|
75
110
|
command: 'research',
|
|
76
111
|
icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
|
|
77
112
|
color: 'var(--purple)',
|
|
78
|
-
description: 'Proactive web research
|
|
113
|
+
description: 'Proactive web research → discover → draft → test',
|
|
114
|
+
flowSteps: ['Find gaps', 'Web search', 'Draft skills', 'Test & score'],
|
|
79
115
|
},
|
|
80
116
|
]
|
|
81
117
|
|
|
82
|
-
return <QuickActions actions={actions} />
|
|
118
|
+
return <QuickActions actions={actions} flowDiagram={FlowDiagram} />
|
|
83
119
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
@@ -12,15 +12,17 @@ interface Action {
|
|
|
12
12
|
description: string
|
|
13
13
|
disabled?: boolean
|
|
14
14
|
disabledReason?: string
|
|
15
|
+
flowSteps?: string[]
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
interface QuickActionsProps {
|
|
18
19
|
actions: Action[]
|
|
20
|
+
flowDiagram?: ComponentType<{ steps: string[]; color: string }>
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
22
24
|
|
|
23
|
-
export function QuickActions({ actions }: QuickActionsProps) {
|
|
25
|
+
export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActionsProps) {
|
|
24
26
|
const [running, setRunning] = useState<string | null>(null)
|
|
25
27
|
const [state, setState] = useState<RunState>('idle')
|
|
26
28
|
const [output, setOutput] = useState<string | null>(null)
|
|
@@ -52,7 +54,6 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
52
54
|
}
|
|
53
55
|
} catch (err: unknown) {
|
|
54
56
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
55
|
-
// Client-side abort — also kill server-side process
|
|
56
57
|
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
57
58
|
setOutput('Stopped by user')
|
|
58
59
|
setState('stopped')
|
|
@@ -66,14 +67,8 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const handleStop = async () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
abortRef.current.abort()
|
|
72
|
-
}
|
|
73
|
-
// Also kill server-side process
|
|
74
|
-
try {
|
|
75
|
-
await fetch('/api/run', { method: 'DELETE' })
|
|
76
|
-
} catch {}
|
|
70
|
+
if (abortRef.current) abortRef.current.abort()
|
|
71
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
const handleClose = () => {
|
|
@@ -99,7 +94,7 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
99
94
|
|
|
100
95
|
return (
|
|
101
96
|
<>
|
|
102
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(
|
|
97
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 10 }}>
|
|
103
98
|
{actions.map(action => (
|
|
104
99
|
<button
|
|
105
100
|
key={action.id}
|
|
@@ -107,45 +102,60 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
107
102
|
disabled={action.disabled || (running !== null && running !== action.id)}
|
|
108
103
|
title={action.disabled ? action.disabledReason : action.description}
|
|
109
104
|
style={{
|
|
110
|
-
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap:
|
|
111
|
-
padding: '
|
|
105
|
+
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 4,
|
|
106
|
+
padding: '14px 16px',
|
|
112
107
|
background: action.disabled ? 'var(--bg-section)' : 'var(--bg-card)',
|
|
113
108
|
border: `1px solid ${running === action.id && state === 'running' ? action.color : 'var(--border)'}`,
|
|
114
|
-
borderRadius: 'var(--radius)',
|
|
109
|
+
borderRadius: 'var(--radius-lg)',
|
|
115
110
|
fontSize: 12,
|
|
116
111
|
fontWeight: 600,
|
|
117
112
|
color: action.disabled ? 'var(--text-muted)' : 'var(--text-secondary)',
|
|
118
113
|
cursor: action.disabled ? 'not-allowed' : 'pointer',
|
|
119
|
-
opacity: (running !== null && running !== action.id) ? 0.
|
|
120
|
-
transition: 'all 0.
|
|
114
|
+
opacity: (running !== null && running !== action.id) ? 0.4 : 1,
|
|
115
|
+
transition: 'all 0.2s',
|
|
121
116
|
textAlign: 'left',
|
|
122
117
|
}}
|
|
123
118
|
>
|
|
124
|
-
|
|
119
|
+
{/* Header: icon + label */}
|
|
120
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 7, width: '100%' }}>
|
|
125
121
|
{running === action.id && state === 'running' ? (
|
|
126
122
|
<span style={{
|
|
127
|
-
width:
|
|
123
|
+
width: 16, height: 16, border: '2px solid var(--border)',
|
|
128
124
|
borderTopColor: action.color, borderRadius: '50%',
|
|
129
125
|
animation: 'actionSpin 0.8s linear infinite',
|
|
130
126
|
flexShrink: 0,
|
|
131
127
|
}} />
|
|
132
128
|
) : (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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>
|
|
138
140
|
)}
|
|
139
|
-
{action.label}
|
|
141
|
+
<span style={{ fontSize: 13 }}>{action.label}</span>
|
|
140
142
|
</div>
|
|
143
|
+
|
|
144
|
+
{/* Subtitle */}
|
|
141
145
|
{action.subtitle && (
|
|
142
146
|
<div style={{
|
|
143
147
|
fontSize: 11, fontWeight: 400, lineHeight: 1.45,
|
|
144
148
|
color: action.disabled ? 'var(--text-muted)' : 'var(--text-dim)',
|
|
149
|
+
marginTop: 2,
|
|
145
150
|
}}>
|
|
146
151
|
{action.disabled ? action.disabledReason : action.subtitle}
|
|
147
152
|
</div>
|
|
148
153
|
)}
|
|
154
|
+
|
|
155
|
+
{/* Flow diagram */}
|
|
156
|
+
{FlowDiagram && action.flowSteps && !action.disabled && (
|
|
157
|
+
<FlowDiagram steps={action.flowSteps} color={action.color} />
|
|
158
|
+
)}
|
|
149
159
|
</button>
|
|
150
160
|
))}
|
|
151
161
|
</div>
|
|
@@ -153,13 +163,12 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
153
163
|
{/* Output panel */}
|
|
154
164
|
{running && state !== 'idle' && (
|
|
155
165
|
<div style={{
|
|
156
|
-
marginTop:
|
|
166
|
+
marginTop: 14,
|
|
157
167
|
background: 'var(--bg-card)',
|
|
158
168
|
border: `1px solid ${stateBorderColor}`,
|
|
159
169
|
borderRadius: 'var(--radius-lg)',
|
|
160
170
|
overflow: 'hidden',
|
|
161
171
|
}}>
|
|
162
|
-
{/* Header */}
|
|
163
172
|
<div style={{
|
|
164
173
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
165
174
|
padding: '10px 14px',
|
|
@@ -218,18 +227,13 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
218
227
|
</div>
|
|
219
228
|
</div>
|
|
220
229
|
|
|
221
|
-
{/* Output */}
|
|
222
230
|
<pre style={{
|
|
223
|
-
padding: '12px 14px',
|
|
224
|
-
|
|
225
|
-
fontSize: 11,
|
|
226
|
-
lineHeight: 1.5,
|
|
231
|
+
padding: '12px 14px', margin: 0,
|
|
232
|
+
fontSize: 11, lineHeight: 1.5,
|
|
227
233
|
fontFamily: 'var(--font-mono)',
|
|
228
234
|
color: 'var(--text-secondary)',
|
|
229
|
-
maxHeight: 400,
|
|
230
|
-
|
|
231
|
-
whiteSpace: 'pre-wrap',
|
|
232
|
-
wordBreak: 'break-word',
|
|
235
|
+
maxHeight: 400, overflow: 'auto',
|
|
236
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
233
237
|
}}>
|
|
234
238
|
{output ?? 'Waiting for output...'}
|
|
235
239
|
</pre>
|
package/package.json
CHANGED