helixevo 0.2.38 โ 0.2.40
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/CHANGELOG.md +31 -0
- package/dashboard/app/api/browse/route.ts +66 -0
- package/dashboard/app/changelog/page.tsx +179 -54
- package/dashboard/app/commands/page.tsx +232 -155
- package/dashboard/app/evolution/page.tsx +105 -102
- package/dashboard/app/frontier/page.tsx +103 -100
- package/dashboard/app/globals.css +1105 -403
- package/dashboard/app/guide/page.tsx +46 -2
- package/dashboard/app/layout.tsx +28 -57
- package/dashboard/app/network/client.tsx +453 -269
- package/dashboard/app/network/page.tsx +12 -2
- package/dashboard/app/page.tsx +166 -185
- package/dashboard/app/projects/client.tsx +923 -400
- package/dashboard/app/research/client.tsx +180 -248
- package/dashboard/components/SkillFlowNode.tsx +86 -128
- package/dashboard/components/console-panel.tsx +25 -0
- package/dashboard/components/metric-card.tsx +45 -0
- package/dashboard/components/overview-actions.tsx +29 -40
- package/dashboard/components/page-hero.tsx +44 -0
- package/dashboard/components/quick-actions.tsx +93 -167
- package/dashboard/components/section-frame.tsx +35 -0
- package/dashboard/components/sidebar-nav.tsx +75 -0
- package/dashboard/components/update-banner.tsx +101 -145
- package/dashboard/lib/data.ts +2 -2
- package/package.json +1 -1
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useRef, useState } from 'react'
|
|
4
|
+
import { ConsolePanel } from '@/components/console-panel'
|
|
5
|
+
import { MetricCard } from '@/components/metric-card'
|
|
6
|
+
import { PageHero } from '@/components/page-hero'
|
|
7
|
+
import { SectionFrame } from '@/components/section-frame'
|
|
4
8
|
|
|
5
9
|
interface Discovery {
|
|
6
|
-
id: string
|
|
10
|
+
id: string
|
|
11
|
+
title: string
|
|
12
|
+
summary: string
|
|
13
|
+
score: number
|
|
14
|
+
gap: string
|
|
15
|
+
lastAccessed: string
|
|
7
16
|
}
|
|
8
17
|
|
|
9
18
|
interface Draft {
|
|
10
|
-
id: string
|
|
19
|
+
id: string
|
|
20
|
+
skillName: string
|
|
21
|
+
avgScore: number
|
|
22
|
+
passRate: number
|
|
23
|
+
iteration: number
|
|
24
|
+
hypothesis: string
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
interface KnowledgeBuffer {
|
|
@@ -25,41 +39,48 @@ type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
|
25
39
|
const PIPELINE_STEPS = [
|
|
26
40
|
{
|
|
27
41
|
step: 1,
|
|
28
|
-
title: 'Identify
|
|
29
|
-
desc: 'Analyze
|
|
30
|
-
icon: '
|
|
42
|
+
title: 'Identify gaps',
|
|
43
|
+
desc: 'Analyze the existing network to detect missing capabilities or weak coverage areas.',
|
|
44
|
+
icon: 'โ',
|
|
31
45
|
color: 'var(--blue)',
|
|
32
|
-
|
|
46
|
+
tone: 'blue' as const,
|
|
33
47
|
},
|
|
34
48
|
{
|
|
35
49
|
step: 2,
|
|
36
|
-
title: '
|
|
37
|
-
desc: '
|
|
38
|
-
icon: '
|
|
50
|
+
title: 'Search the web',
|
|
51
|
+
desc: 'Pull in current techniques, patterns, and best practices that can close those gaps.',
|
|
52
|
+
icon: 'โ',
|
|
39
53
|
color: 'var(--purple)',
|
|
40
|
-
|
|
54
|
+
tone: 'purple' as const,
|
|
41
55
|
},
|
|
42
56
|
{
|
|
43
57
|
step: 3,
|
|
44
|
-
title: 'Draft
|
|
45
|
-
desc: '
|
|
46
|
-
icon: '
|
|
58
|
+
title: 'Draft skills',
|
|
59
|
+
desc: 'Translate discoveries into candidate SKILL.md drafts and experimental hypotheses.',
|
|
60
|
+
icon: 'โ',
|
|
47
61
|
color: 'var(--yellow)',
|
|
48
|
-
|
|
62
|
+
tone: 'yellow' as const,
|
|
49
63
|
},
|
|
50
64
|
{
|
|
51
65
|
step: 4,
|
|
52
|
-
title: 'Test
|
|
53
|
-
desc: '
|
|
66
|
+
title: 'Test and score',
|
|
67
|
+
desc: 'Run scoring and pass-rate checks before a draft can become a stable part of the system.',
|
|
54
68
|
icon: 'โ',
|
|
55
69
|
color: 'var(--green)',
|
|
56
|
-
|
|
70
|
+
tone: 'green' as const,
|
|
57
71
|
},
|
|
58
72
|
]
|
|
59
73
|
|
|
74
|
+
function consoleTone(state: RunState): 'neutral' | 'green' | 'red' | 'yellow' {
|
|
75
|
+
if (state === 'success') return 'green'
|
|
76
|
+
if (state === 'error') return 'red'
|
|
77
|
+
if (state === 'stopped') return 'yellow'
|
|
78
|
+
return 'neutral'
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
export default function ResearchClient({ buffer, projects }: Props) {
|
|
61
82
|
const [runState, setRunState] = useState<RunState>('idle')
|
|
62
|
-
const [output, setOutput] = useState
|
|
83
|
+
const [output, setOutput] = useState('')
|
|
63
84
|
const abortRef = useRef<AbortController | null>(null)
|
|
64
85
|
const outputRef = useRef<HTMLPreElement | null>(null)
|
|
65
86
|
|
|
@@ -120,7 +141,8 @@ export default function ResearchClient({ buffer, projects }: Props) {
|
|
|
120
141
|
}
|
|
121
142
|
}
|
|
122
143
|
}
|
|
123
|
-
|
|
144
|
+
|
|
145
|
+
setRunState(prev => prev === 'running' ? 'success' : prev)
|
|
124
146
|
} catch (err: unknown) {
|
|
125
147
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
126
148
|
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
@@ -140,257 +162,167 @@ export default function ResearchClient({ buffer, projects }: Props) {
|
|
|
140
162
|
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
141
163
|
}
|
|
142
164
|
|
|
143
|
-
const nearPass = buffer.drafts.filter(
|
|
165
|
+
const nearPass = buffer.drafts.filter((draft) => draft.passRate >= 0.67)
|
|
144
166
|
|
|
145
167
|
return (
|
|
146
|
-
<div>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
<div style={{ display: 'grid', gap: 22 }}>
|
|
169
|
+
<PageHero
|
|
170
|
+
eyebrow="Discovery lab"
|
|
171
|
+
title="Proactive Research"
|
|
172
|
+
description="Search for techniques your network still lacks, turn discoveries into draft skills, and score them before they enter the evolutionary loop."
|
|
173
|
+
chips={[
|
|
174
|
+
{ label: `${projects.length} tracked projects`, tone: projects.length > 0 ? 'blue' : 'neutral' },
|
|
175
|
+
{ label: `${buffer.discoveries.length} discoveries`, tone: 'purple' },
|
|
176
|
+
{ label: `${buffer.drafts.length} drafts`, tone: 'yellow' },
|
|
177
|
+
{ label: `${nearPass.length} near-pass drafts`, tone: 'green' },
|
|
178
|
+
]}
|
|
179
|
+
/>
|
|
153
180
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
</div>
|
|
172
|
-
<div>
|
|
173
|
-
<div style={{ fontSize: 9, fontWeight: 600, color: step.color, letterSpacing: 0.5 }}>STEP {step.step}</div>
|
|
174
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{step.title}</div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
<div style={{ fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.5, paddingRight: 12 }}>
|
|
178
|
-
{step.desc}
|
|
181
|
+
<SectionFrame
|
|
182
|
+
eyebrow="Pipeline"
|
|
183
|
+
title="How research works"
|
|
184
|
+
description="Research moves from network gaps to external evidence, then to draft skills, then through scoring and pass-rate thresholds."
|
|
185
|
+
actions={
|
|
186
|
+
runState === 'running' ? (
|
|
187
|
+
<button onClick={handleStop} className="badge badge-red" style={{ border: 'none', cursor: 'pointer' }}>Stop research</button>
|
|
188
|
+
) : (
|
|
189
|
+
<button onClick={handleRun} className="badge badge-purple" style={{ border: 'none', cursor: 'pointer' }}>Run research</button>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
>
|
|
193
|
+
<div className="grid-4" style={{ marginBottom: 18 }}>
|
|
194
|
+
{PIPELINE_STEPS.map((step) => (
|
|
195
|
+
<div key={step.step} className={`metric-card metric-card-${step.tone}`}>
|
|
196
|
+
<div className="metric-card-header">
|
|
197
|
+
<div>
|
|
198
|
+
<div className="metric-card-label">Step {step.step}</div>
|
|
199
|
+
<div className="metric-card-value" style={{ fontSize: 24 }}>{step.title}</div>
|
|
179
200
|
</div>
|
|
201
|
+
<div className="metric-card-icon" style={{ color: step.color }}>{step.icon}</div>
|
|
180
202
|
</div>
|
|
181
|
-
|
|
182
|
-
<svg width="20" height="60" viewBox="0 0 20 60" style={{ flexShrink: 0, marginTop: 8 }}>
|
|
183
|
-
<path d="M4 30h12M12 24l4 6-4 6" stroke="var(--border)" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
184
|
-
</svg>
|
|
185
|
-
)}
|
|
203
|
+
<div className="metric-card-sublabel">{step.desc}</div>
|
|
186
204
|
</div>
|
|
187
205
|
))}
|
|
188
206
|
</div>
|
|
189
207
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
style={{
|
|
197
|
-
display: 'flex', alignItems: 'center', gap: 6,
|
|
198
|
-
padding: '9px 20px',
|
|
199
|
-
background: 'var(--teal, var(--green))',
|
|
200
|
-
color: '#fff', border: 'none',
|
|
201
|
-
borderRadius: 'var(--radius)',
|
|
202
|
-
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
203
|
-
transition: 'opacity 0.15s',
|
|
204
|
-
}}
|
|
205
|
-
onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
|
|
206
|
-
onMouseOut={e => (e.currentTarget.style.opacity = '1')}
|
|
207
|
-
>
|
|
208
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
209
|
-
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
210
|
-
</svg>
|
|
211
|
-
Run Research
|
|
212
|
-
</button>
|
|
213
|
-
)}
|
|
214
|
-
{runState === 'running' && (
|
|
215
|
-
<button
|
|
216
|
-
onClick={handleStop}
|
|
217
|
-
style={{
|
|
218
|
-
display: 'flex', alignItems: 'center', gap: 6,
|
|
219
|
-
padding: '9px 20px',
|
|
220
|
-
background: 'var(--red)', color: '#fff', border: 'none',
|
|
221
|
-
borderRadius: 'var(--radius)',
|
|
222
|
-
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
223
|
-
}}
|
|
224
|
-
>
|
|
225
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
226
|
-
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
227
|
-
</svg>
|
|
228
|
-
Stop Research
|
|
229
|
-
</button>
|
|
230
|
-
)}
|
|
231
|
-
{(runState === 'success' || runState === 'error' || runState === 'stopped') && (
|
|
232
|
-
<div style={{ display: 'flex', gap: 8 }}>
|
|
233
|
-
<button onClick={handleRun} style={{
|
|
234
|
-
padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
235
|
-
borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
236
|
-
color: 'var(--text-secondary)',
|
|
237
|
-
}}>
|
|
238
|
-
Run Again
|
|
239
|
-
</button>
|
|
240
|
-
<button onClick={() => window.location.reload()} style={{
|
|
241
|
-
padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
242
|
-
borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
243
|
-
color: 'var(--text-secondary)',
|
|
244
|
-
}}>
|
|
245
|
-
Refresh Data
|
|
246
|
-
</button>
|
|
247
|
-
</div>
|
|
248
|
-
)}
|
|
249
|
-
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
250
|
-
{runState === 'running' && 'Searching the web and testing hypotheses...'}
|
|
251
|
-
{runState === 'success' && 'Research complete โ refresh to see new discoveries'}
|
|
252
|
-
{runState === 'error' && 'Research failed โ check output below'}
|
|
253
|
-
{runState === 'stopped' && 'Research stopped'}
|
|
254
|
-
{runState === 'idle' && 'Analyzes all your skills, searches for gaps, and drafts new skills'}
|
|
255
|
-
</span>
|
|
256
|
-
</div>
|
|
208
|
+
<div className="signal-text">
|
|
209
|
+
{runState === 'running' && 'The research engine is currently searching, drafting, and testing hypotheses.'}
|
|
210
|
+
{runState === 'success' && 'Research completed โ refresh the page if you want to reload persisted discoveries.'}
|
|
211
|
+
{runState === 'error' && 'Research failed โ check the run console below for details.'}
|
|
212
|
+
{runState === 'stopped' && 'Research was stopped before completion.'}
|
|
213
|
+
{runState === 'idle' && 'Run the pipeline to discover new capabilities, test draft skills, and surface the most promising additions to your network.'}
|
|
257
214
|
</div>
|
|
258
|
-
</
|
|
215
|
+
</SectionFrame>
|
|
259
216
|
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
217
|
+
{runState !== 'idle' ? (
|
|
218
|
+
<ConsolePanel
|
|
219
|
+
tone={consoleTone(runState)}
|
|
220
|
+
title={
|
|
221
|
+
<span>
|
|
222
|
+
{runState === 'running' ? 'Research pipeline runningโฆ' :
|
|
223
|
+
runState === 'success' ? 'Research completed' :
|
|
224
|
+
runState === 'stopped' ? 'Research stopped' :
|
|
225
|
+
'Research failed'}
|
|
226
|
+
</span>
|
|
227
|
+
}
|
|
228
|
+
actions={
|
|
229
|
+
runState !== 'running' ? (
|
|
230
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
231
|
+
<button onClick={handleRun} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Run again</button>
|
|
232
|
+
<button onClick={() => window.location.reload()} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Refresh data</button>
|
|
233
|
+
</div>
|
|
234
|
+
) : null
|
|
235
|
+
}
|
|
236
|
+
>
|
|
237
|
+
<pre
|
|
238
|
+
ref={outputRef}
|
|
239
|
+
style={{
|
|
240
|
+
margin: 0,
|
|
241
|
+
padding: '16px 18px',
|
|
242
|
+
fontSize: 11.5,
|
|
243
|
+
lineHeight: 1.62,
|
|
244
|
+
color: 'var(--text-secondary)',
|
|
245
|
+
whiteSpace: 'pre-wrap',
|
|
246
|
+
wordBreak: 'break-word',
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{output || 'Starting research pipelineโฆ'}
|
|
284
250
|
</pre>
|
|
285
|
-
</
|
|
286
|
-
)}
|
|
251
|
+
</ConsolePanel>
|
|
252
|
+
) : null}
|
|
287
253
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
<div className="stat-label">Discoveries</div>
|
|
293
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 50 capacity</div>
|
|
294
|
-
</div>
|
|
295
|
-
<div className="stat-card">
|
|
296
|
-
<div className="stat-value" style={{ color: 'var(--yellow)' }}>{buffer.drafts.length}</div>
|
|
297
|
-
<div className="stat-label">Drafts In Progress</div>
|
|
298
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 10 capacity</div>
|
|
299
|
-
</div>
|
|
300
|
-
<div className="stat-card">
|
|
301
|
-
<div className="stat-value" style={{ color: 'var(--green)' }}>{nearPass.length}</div>
|
|
302
|
-
<div className="stat-label">Near-Pass Drafts</div>
|
|
303
|
-
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>โฅ67% pass rate โ close to becoming skills</div>
|
|
304
|
-
</div>
|
|
254
|
+
<div className="grid-3">
|
|
255
|
+
<MetricCard label="Discoveries" value={buffer.discoveries.length} sublabel="Research findings currently in the knowledge buffer" tone="blue" icon="โ" />
|
|
256
|
+
<MetricCard label="Drafts in progress" value={buffer.drafts.length} sublabel="Experimental skills still being refined" tone="yellow" icon="โ" />
|
|
257
|
+
<MetricCard label="Near-pass drafts" value={nearPass.length} sublabel="Drafts already above the pass-rate threshold" tone="green" icon="โ" />
|
|
305
258
|
</div>
|
|
306
259
|
|
|
307
260
|
<div className="grid-2">
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
331
|
-
<span style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.3 }}>{d.title}</span>
|
|
332
|
-
<span className="score-pill" style={{ color: 'var(--blue)', flexShrink: 0, marginLeft: 8 }}>
|
|
333
|
-
{(d.score * 100).toFixed(0)}%
|
|
334
|
-
</span>
|
|
335
|
-
</div>
|
|
336
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
337
|
-
{d.summary.slice(0, 140)}
|
|
261
|
+
<SectionFrame
|
|
262
|
+
eyebrow="Knowledge buffer"
|
|
263
|
+
title={`Discoveries (${buffer.discoveries.length}/50)`}
|
|
264
|
+
description="External techniques, patterns, and signals the research system believes are worth turning into skills."
|
|
265
|
+
tone="blue"
|
|
266
|
+
>
|
|
267
|
+
{buffer.discoveries.length === 0 ? (
|
|
268
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
269
|
+
<div className="empty-state-title">No discoveries yet</div>
|
|
270
|
+
<div className="empty-state-desc">Run research to search the web for techniques your skill network does not cover yet.</div>
|
|
271
|
+
</div>
|
|
272
|
+
) : (
|
|
273
|
+
<div className="summary-list">
|
|
274
|
+
{buffer.discoveries.sort((a, b) => b.score - a.score).map((discovery) => (
|
|
275
|
+
<div key={discovery.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
276
|
+
<div className="summary-row-main">
|
|
277
|
+
<div className="summary-row-title">{discovery.title}</div>
|
|
278
|
+
<div className="summary-row-meta">{discovery.summary.slice(0, 150)}{discovery.summary.length > 150 ? 'โฆ' : ''}</div>
|
|
279
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
|
280
|
+
<span className="badge badge-blue">{discovery.gap}</span>
|
|
281
|
+
<span className="score-pill" style={{ color: 'var(--blue)' }}>{(discovery.score * 100).toFixed(0)}%</span>
|
|
282
|
+
</div>
|
|
338
283
|
</div>
|
|
339
|
-
<span className="badge badge-blue">{d.gap}</span>
|
|
340
284
|
</div>
|
|
341
|
-
))
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
</
|
|
285
|
+
))}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
</SectionFrame>
|
|
345
289
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
|
|
369
|
-
<span style={{ fontSize: 13, fontWeight: 600 }}>{d.skillName}</span>
|
|
370
|
-
<span className={`badge ${d.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>
|
|
371
|
-
{d.avgScore.toFixed(1)}/10 ยท iter {d.iteration}
|
|
372
|
-
</span>
|
|
373
|
-
</div>
|
|
374
|
-
<div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
|
|
375
|
-
{d.hypothesis.slice(0, 140)}
|
|
290
|
+
<SectionFrame
|
|
291
|
+
eyebrow="Draft pipeline"
|
|
292
|
+
title={`Skill drafts (${buffer.drafts.length}/10)`}
|
|
293
|
+
description="Drafts are candidates that are close to graduating into stable skills but still need evidence, quality, or pass-rate improvements."
|
|
294
|
+
tone="yellow"
|
|
295
|
+
>
|
|
296
|
+
{buffer.drafts.length === 0 ? (
|
|
297
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
298
|
+
<div className="empty-state-title">No drafts yet</div>
|
|
299
|
+
<div className="empty-state-desc">Run research to generate the first draft skills and evaluate them against the current thresholds.</div>
|
|
300
|
+
</div>
|
|
301
|
+
) : (
|
|
302
|
+
<div className="summary-list">
|
|
303
|
+
{buffer.drafts.sort((a, b) => b.avgScore - a.avgScore).map((draft) => (
|
|
304
|
+
<div key={draft.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
305
|
+
<div className="summary-row-main">
|
|
306
|
+
<div className="summary-row-title">{draft.skillName}</div>
|
|
307
|
+
<div className="summary-row-meta">{draft.hypothesis.slice(0, 150)}{draft.hypothesis.length > 150 ? 'โฆ' : ''}</div>
|
|
308
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 10 }}>
|
|
309
|
+
<span className={`badge ${draft.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>{draft.avgScore.toFixed(1)}/10</span>
|
|
310
|
+
<span className="badge badge-gray">iter {draft.iteration}</span>
|
|
311
|
+
</div>
|
|
376
312
|
</div>
|
|
377
|
-
<div style={{
|
|
378
|
-
<
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}} />
|
|
313
|
+
<div style={{ minWidth: 104 }}>
|
|
314
|
+
<div className="score-track" style={{ marginBottom: 6 }}>
|
|
315
|
+
<div className="score-fill" style={{ width: `${draft.passRate * 100}%`, background: draft.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }} />
|
|
316
|
+
</div>
|
|
317
|
+
<div style={{ textAlign: 'right', fontSize: 12, fontWeight: 800, color: draft.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
|
|
318
|
+
{(draft.passRate * 100).toFixed(0)}%
|
|
384
319
|
</div>
|
|
385
|
-
<b style={{ fontSize: 11, color: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
|
|
386
|
-
{(d.passRate * 100).toFixed(0)}%
|
|
387
|
-
</b>
|
|
388
320
|
</div>
|
|
389
321
|
</div>
|
|
390
|
-
))
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
</
|
|
322
|
+
))}
|
|
323
|
+
</div>
|
|
324
|
+
)}
|
|
325
|
+
</SectionFrame>
|
|
394
326
|
</div>
|
|
395
327
|
</div>
|
|
396
328
|
)
|