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
|
@@ -242,19 +242,24 @@ export default function NetworkClient({
|
|
|
242
242
|
<div>
|
|
243
243
|
{/* Page Header */}
|
|
244
244
|
<div className="page-header">
|
|
245
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
245
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 18, flexWrap: 'wrap' }}>
|
|
246
246
|
<div>
|
|
247
247
|
<h1 className="page-title">Skill Network</h1>
|
|
248
248
|
<p className="page-desc">
|
|
249
|
-
|
|
250
|
-
{stats.generalized > 0 && ` · ${stats.generalized} generalized`}
|
|
249
|
+
Visualize how skills generalize, specialize, and interact across the living HelixEvo network.
|
|
251
250
|
</p>
|
|
251
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 16 }}>
|
|
252
|
+
<span className="hero-chip hero-chip-purple">{stats.nodes} skills</span>
|
|
253
|
+
<span className="hero-chip hero-chip-blue">{stats.edges} relationships</span>
|
|
254
|
+
<span className="hero-chip hero-chip-neutral">{stats.clusters} clusters</span>
|
|
255
|
+
{stats.generalized > 0 ? <span className="hero-chip hero-chip-green">{stats.generalized} generalized</span> : null}
|
|
256
|
+
</div>
|
|
252
257
|
</div>
|
|
253
258
|
<button onClick={() => setCreating(!creating)} style={{
|
|
254
|
-
background: creating ? '
|
|
259
|
+
background: creating ? 'rgba(97,93,86,0.08)' : 'linear-gradient(135deg, var(--purple), #4f46e5)',
|
|
255
260
|
color: creating ? 'var(--text-dim)' : '#fff',
|
|
256
|
-
border: 'none', borderRadius:
|
|
257
|
-
cursor: 'pointer', display: 'flex', alignItems: 'center', gap:
|
|
261
|
+
border: creating ? '1px solid var(--border)' : 'none', borderRadius: 999, padding: '11px 18px', fontSize: 13, fontWeight: 700,
|
|
262
|
+
cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'var(--font)', boxShadow: creating ? 'none' : 'var(--shadow-md)',
|
|
258
263
|
}}>{creating ? '✕ Cancel' : '+ New Skill'}</button>
|
|
259
264
|
</div>
|
|
260
265
|
</div>
|
|
@@ -314,8 +319,12 @@ export default function NetworkClient({
|
|
|
314
319
|
key={sv.key}
|
|
315
320
|
className={`tab-item ${view === sv.key ? 'active' : ''}`}
|
|
316
321
|
onClick={() => { setView(sv.key) }}
|
|
322
|
+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 2, minWidth: 0 }}
|
|
317
323
|
>
|
|
318
|
-
{sv.label}
|
|
324
|
+
<span style={{ fontSize: 12.5, fontWeight: 700 }}>{sv.label}</span>
|
|
325
|
+
<span style={{ fontSize: 10.5, color: view === sv.key ? 'rgba(62,53,46,0.74)' : 'var(--text-muted)', fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%' }}>
|
|
326
|
+
{sv.desc}
|
|
327
|
+
</span>
|
|
319
328
|
</button>
|
|
320
329
|
))}
|
|
321
330
|
</div>
|
|
@@ -346,256 +355,82 @@ export default function NetworkClient({
|
|
|
346
355
|
|
|
347
356
|
{/* Detail Panel */}
|
|
348
357
|
{selectedSkill && selectedNode && (
|
|
349
|
-
<
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
padding: '8px 12px', marginBottom: 6, borderRadius: 8,
|
|
426
|
-
background: 'var(--bg-section)',
|
|
427
|
-
borderLeft: `3px solid ${e.outcome === 'accepted' ? 'var(--green)' : 'var(--red)'}`,
|
|
428
|
-
}}>
|
|
429
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, marginBottom: 3 }}>
|
|
430
|
-
<span className={`badge ${e.outcome === 'accepted' ? 'badge-green' : 'badge-red'}`}>{e.outcome}</span>
|
|
431
|
-
<span style={{ color: 'var(--text-muted)' }}>{new Date(e.timestamp).toLocaleDateString()}</span>
|
|
432
|
-
</div>
|
|
433
|
-
<div style={{ fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 4 }}>{e.description.slice(0, 150)}</div>
|
|
434
|
-
<div style={{ display: 'flex', gap: 8, fontSize: 10 }}>
|
|
435
|
-
<span className="score-pill">T:{e.task}</span>
|
|
436
|
-
<span className="score-pill">A:{e.align}</span>
|
|
437
|
-
<span className="score-pill">S:{e.sideEffect}</span>
|
|
438
|
-
</div>
|
|
439
|
-
</div>
|
|
440
|
-
))}
|
|
441
|
-
</div>
|
|
442
|
-
)}
|
|
443
|
-
|
|
444
|
-
{/* ─── Cross-Links ─── */}
|
|
445
|
-
<div style={{ marginBottom: 16 }}>
|
|
446
|
-
<div className="section-label">Navigate</div>
|
|
447
|
-
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
448
|
-
{view !== 'graph' && (
|
|
449
|
-
<button onClick={() => setView('graph')} style={actionBtnStyle('var(--blue)')}>
|
|
450
|
-
◎ View in Graph
|
|
451
|
-
</button>
|
|
452
|
-
)}
|
|
453
|
-
{view !== 'general' && (
|
|
454
|
-
<button onClick={() => setView('general')} style={actionBtnStyle()}>
|
|
455
|
-
▤ View in Skills
|
|
456
|
-
</button>
|
|
457
|
-
)}
|
|
458
|
-
{view !== 'coevolution' && (
|
|
459
|
-
<button onClick={() => setView('coevolution')} style={actionBtnStyle()}>
|
|
460
|
-
↕ Co-Evolution
|
|
461
|
-
</button>
|
|
462
|
-
)}
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
|
|
466
|
-
{/* ─── Management Actions ─── */}
|
|
467
|
-
<div style={{ marginBottom: 20 }}>
|
|
468
|
-
<div className="section-label">Actions</div>
|
|
469
|
-
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
470
|
-
<button onClick={() => { setEditing(!editing); setEditContent(skillContents[selectedSkill!] ?? ''); setAdaptation(null) }}
|
|
471
|
-
style={actionBtnStyle(editing ? 'var(--purple)' : undefined)}>
|
|
472
|
-
{editing ? '✕ Cancel' : '✎ Edit'}
|
|
473
|
-
</button>
|
|
474
|
-
<button onClick={async () => {
|
|
475
|
-
if (!selectedNode) return
|
|
476
|
-
const layers = ['project', 'domain', 'system'] as const
|
|
477
|
-
const currentIdx = layers.indexOf(selectedNode.layer as typeof layers[number])
|
|
478
|
-
const nextLayer = layers[Math.min(currentIdx + 1, 2)]
|
|
479
|
-
if (nextLayer === selectedNode.layer) return
|
|
480
|
-
setActionLoading(true)
|
|
481
|
-
const res = await fetch(`/api/skills`, { method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
482
|
-
body: JSON.stringify({ slug: selectedSkill, layer: nextLayer }) })
|
|
483
|
-
const data = await res.json()
|
|
484
|
-
setAdaptation(data.adaptation)
|
|
485
|
-
setActionLoading(false)
|
|
486
|
-
}} style={actionBtnStyle('var(--blue)')}>
|
|
487
|
-
↑ Promote
|
|
488
|
-
</button>
|
|
489
|
-
<button onClick={async () => {
|
|
490
|
-
if (!selectedNode) return
|
|
491
|
-
const layers = ['project', 'domain', 'system'] as const
|
|
492
|
-
const currentIdx = layers.indexOf(selectedNode.layer as typeof layers[number])
|
|
493
|
-
const nextLayer = layers[Math.max(currentIdx - 1, 0)]
|
|
494
|
-
if (nextLayer === selectedNode.layer) return
|
|
495
|
-
setActionLoading(true)
|
|
496
|
-
const res = await fetch(`/api/skills`, { method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
497
|
-
body: JSON.stringify({ slug: selectedSkill, layer: nextLayer }) })
|
|
498
|
-
const data = await res.json()
|
|
499
|
-
setAdaptation(data.adaptation)
|
|
500
|
-
setActionLoading(false)
|
|
501
|
-
}} style={actionBtnStyle('var(--yellow)')}>
|
|
502
|
-
↓ Demote
|
|
503
|
-
</button>
|
|
504
|
-
<button onClick={async () => {
|
|
505
|
-
if (!confirm(`Delete "${selectedNode?.name}"? This cannot be undone.`)) return
|
|
506
|
-
setActionLoading(true)
|
|
507
|
-
const res = await fetch(`/api/skills?slug=${selectedSkill}`, { method: 'DELETE' })
|
|
508
|
-
const data = await res.json()
|
|
509
|
-
setAdaptation(data.adaptation)
|
|
510
|
-
setActionLoading(false)
|
|
511
|
-
if (data.success) setTimeout(() => window.location.reload(), 1500)
|
|
512
|
-
}} style={actionBtnStyle('var(--red)')}>
|
|
513
|
-
✕ Delete
|
|
514
|
-
</button>
|
|
515
|
-
</div>
|
|
516
|
-
</div>
|
|
517
|
-
|
|
518
|
-
{/* ─── Edit Mode ─── */}
|
|
519
|
-
{editing && (
|
|
520
|
-
<div style={{ marginBottom: 20 }}>
|
|
521
|
-
<div className="section-label">Edit Skill Content</div>
|
|
522
|
-
<textarea
|
|
523
|
-
value={editContent}
|
|
524
|
-
onChange={e => setEditContent(e.target.value)}
|
|
525
|
-
style={{
|
|
526
|
-
width: '100%', height: 250, fontFamily: 'var(--font-mono)', fontSize: 11,
|
|
527
|
-
background: '#1e1e2e', color: '#cdd6f4', border: '1px solid var(--border)',
|
|
528
|
-
borderRadius: 8, padding: 12, resize: 'vertical', lineHeight: 1.6,
|
|
529
|
-
}}
|
|
530
|
-
/>
|
|
531
|
-
<button onClick={async () => {
|
|
532
|
-
setActionLoading(true)
|
|
533
|
-
const res = await fetch(`/api/skills`, { method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
534
|
-
body: JSON.stringify({ slug: selectedSkill, content: `---\n${Object.entries(selectedNode ? { name: selectedNode.name, description: '', layer: selectedNode.layer } : {}).map(([k,v]) => `${k}: ${v}`).join('\n')}\n---\n\n${editContent}` }) })
|
|
535
|
-
const data = await res.json()
|
|
536
|
-
setAdaptation(data.adaptation)
|
|
537
|
-
setEditing(false)
|
|
538
|
-
setActionLoading(false)
|
|
539
|
-
setTimeout(() => window.location.reload(), 1000)
|
|
540
|
-
}} disabled={actionLoading}
|
|
541
|
-
style={{ ...actionBtnStyle('var(--green)'), width: '100%', marginTop: 8 }}>
|
|
542
|
-
{actionLoading ? 'Saving...' : '✓ Save Changes'}
|
|
543
|
-
</button>
|
|
544
|
-
</div>
|
|
545
|
-
)}
|
|
546
|
-
|
|
547
|
-
{/* ─── Network Adaptation Feedback ─── */}
|
|
548
|
-
{adaptation && (
|
|
549
|
-
<div style={{ marginBottom: 20 }}>
|
|
550
|
-
<div className="section-label">
|
|
551
|
-
Network Adaptation
|
|
552
|
-
<span style={{
|
|
553
|
-
marginLeft: 8, fontSize: 10, fontWeight: 600, padding: '2px 8px', borderRadius: 6,
|
|
554
|
-
background: adaptation.status === 'ok' ? 'var(--green-light)' : adaptation.status === 'warning' ? 'var(--yellow-light)' : 'var(--red-light)',
|
|
555
|
-
color: adaptation.status === 'ok' ? 'var(--green)' : adaptation.status === 'warning' ? 'var(--yellow)' : 'var(--red)',
|
|
556
|
-
textTransform: 'none', letterSpacing: 0,
|
|
557
|
-
}}>{adaptation.status}</span>
|
|
558
|
-
</div>
|
|
559
|
-
{adaptation.messages.map((m, i) => (
|
|
560
|
-
<div key={i} style={{
|
|
561
|
-
padding: '8px 12px', marginBottom: 4, borderRadius: 6, fontSize: 12, lineHeight: 1.5,
|
|
562
|
-
background: m.type === 'warning' ? 'var(--yellow-light)' : m.type === 'action' ? 'var(--red-light)' : 'var(--bg-section)',
|
|
563
|
-
color: m.type === 'warning' ? 'var(--yellow)' : m.type === 'action' ? 'var(--red)' : 'var(--text-secondary)',
|
|
564
|
-
border: `1px solid ${m.type === 'warning' ? 'var(--yellow-border)' : m.type === 'action' ? 'var(--red-border)' : 'var(--border)'}`,
|
|
565
|
-
}}>{m.type === 'warning' ? '⚠ ' : m.type === 'action' ? '⚡ ' : 'ℹ '}{m.text}</div>
|
|
566
|
-
))}
|
|
567
|
-
{adaptation.suggestions.length > 0 && (
|
|
568
|
-
<div style={{ marginTop: 8 }}>
|
|
569
|
-
<div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 4 }}>SUGGESTIONS</div>
|
|
570
|
-
{adaptation.suggestions.map((s, i) => (
|
|
571
|
-
<div key={i} style={{
|
|
572
|
-
padding: '6px 10px', fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.5,
|
|
573
|
-
borderLeft: '2px solid var(--purple)', marginBottom: 4, paddingLeft: 10,
|
|
574
|
-
}}>
|
|
575
|
-
<span className="badge badge-purple" style={{ marginRight: 6 }}>{s.type}</span>
|
|
576
|
-
{s.description}
|
|
577
|
-
</div>
|
|
578
|
-
))}
|
|
579
|
-
</div>
|
|
580
|
-
)}
|
|
581
|
-
</div>
|
|
582
|
-
)}
|
|
583
|
-
|
|
584
|
-
{/* Skill Content (read-only when not editing) */}
|
|
585
|
-
{selectedContent && !editing && (
|
|
586
|
-
<div>
|
|
587
|
-
<div className="section-label">Skill Content</div>
|
|
588
|
-
<pre style={{
|
|
589
|
-
background: 'var(--bg-section)', borderRadius: 8, padding: 12,
|
|
590
|
-
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
591
|
-
color: 'var(--text-secondary)', lineHeight: 1.6, maxHeight: 300, overflow: 'auto',
|
|
592
|
-
fontSize: 11, border: '1px solid var(--border)',
|
|
593
|
-
}}>
|
|
594
|
-
{selectedContent.slice(0, 1500)}
|
|
595
|
-
</pre>
|
|
596
|
-
</div>
|
|
597
|
-
)}
|
|
598
|
-
</div>
|
|
358
|
+
<InspectorPanel
|
|
359
|
+
selectedSkill={selectedSkill}
|
|
360
|
+
selectedNode={selectedNode}
|
|
361
|
+
selectedEdges={selectedEdges}
|
|
362
|
+
selectedEvo={selectedEvo}
|
|
363
|
+
selectedContent={selectedContent}
|
|
364
|
+
view={view}
|
|
365
|
+
editing={editing}
|
|
366
|
+
editContent={editContent}
|
|
367
|
+
adaptation={adaptation}
|
|
368
|
+
actionLoading={actionLoading}
|
|
369
|
+
onClose={() => setSelectedSkill(null)}
|
|
370
|
+
onSelectSkill={setSelectedSkill}
|
|
371
|
+
onViewChange={setView}
|
|
372
|
+
onToggleEdit={() => {
|
|
373
|
+
setEditing(!editing)
|
|
374
|
+
setEditContent(skillContents[selectedSkill] ?? '')
|
|
375
|
+
setAdaptation(null)
|
|
376
|
+
}}
|
|
377
|
+
onEditContentChange={setEditContent}
|
|
378
|
+
onPromote={async () => {
|
|
379
|
+
const layers = ['project', 'domain', 'system'] as const
|
|
380
|
+
const currentIdx = layers.indexOf(selectedNode.layer as typeof layers[number])
|
|
381
|
+
const nextLayer = layers[Math.min(currentIdx + 1, 2)]
|
|
382
|
+
if (nextLayer === selectedNode.layer) return
|
|
383
|
+
setActionLoading(true)
|
|
384
|
+
const res = await fetch(`/api/skills`, {
|
|
385
|
+
method: 'PUT',
|
|
386
|
+
headers: { 'Content-Type': 'application/json' },
|
|
387
|
+
body: JSON.stringify({ slug: selectedSkill, layer: nextLayer }),
|
|
388
|
+
})
|
|
389
|
+
const data = await res.json()
|
|
390
|
+
setAdaptation(data.adaptation)
|
|
391
|
+
setActionLoading(false)
|
|
392
|
+
}}
|
|
393
|
+
onDemote={async () => {
|
|
394
|
+
const layers = ['project', 'domain', 'system'] as const
|
|
395
|
+
const currentIdx = layers.indexOf(selectedNode.layer as typeof layers[number])
|
|
396
|
+
const nextLayer = layers[Math.max(currentIdx - 1, 0)]
|
|
397
|
+
if (nextLayer === selectedNode.layer) return
|
|
398
|
+
setActionLoading(true)
|
|
399
|
+
const res = await fetch(`/api/skills`, {
|
|
400
|
+
method: 'PUT',
|
|
401
|
+
headers: { 'Content-Type': 'application/json' },
|
|
402
|
+
body: JSON.stringify({ slug: selectedSkill, layer: nextLayer }),
|
|
403
|
+
})
|
|
404
|
+
const data = await res.json()
|
|
405
|
+
setAdaptation(data.adaptation)
|
|
406
|
+
setActionLoading(false)
|
|
407
|
+
}}
|
|
408
|
+
onDelete={async () => {
|
|
409
|
+
if (!confirm(`Delete "${selectedNode.name}"? This cannot be undone.`)) return
|
|
410
|
+
setActionLoading(true)
|
|
411
|
+
const res = await fetch(`/api/skills?slug=${selectedSkill}`, { method: 'DELETE' })
|
|
412
|
+
const data = await res.json()
|
|
413
|
+
setAdaptation(data.adaptation)
|
|
414
|
+
setActionLoading(false)
|
|
415
|
+
if (data.success) setTimeout(() => window.location.reload(), 1500)
|
|
416
|
+
}}
|
|
417
|
+
onSave={async () => {
|
|
418
|
+
setActionLoading(true)
|
|
419
|
+
const res = await fetch(`/api/skills`, {
|
|
420
|
+
method: 'PUT',
|
|
421
|
+
headers: { 'Content-Type': 'application/json' },
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
slug: selectedSkill,
|
|
424
|
+
content: `---\n${Object.entries({ name: selectedNode.name, description: '', layer: selectedNode.layer }).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n\n${editContent}`,
|
|
425
|
+
}),
|
|
426
|
+
})
|
|
427
|
+
const data = await res.json()
|
|
428
|
+
setAdaptation(data.adaptation)
|
|
429
|
+
setEditing(false)
|
|
430
|
+
setActionLoading(false)
|
|
431
|
+
setTimeout(() => window.location.reload(), 1000)
|
|
432
|
+
}}
|
|
433
|
+
/>
|
|
599
434
|
)}
|
|
600
435
|
</div>
|
|
601
436
|
</div>
|
|
@@ -608,7 +443,44 @@ export default function NetworkClient({
|
|
|
608
443
|
|
|
609
444
|
function GraphView({ flowNodes, flowEdges, stats, onNodeClick }: { flowNodes: Node[]; flowEdges: Edge[]; stats: Props['stats']; onNodeClick: (id: string) => void }) {
|
|
610
445
|
return (
|
|
611
|
-
<div style={{
|
|
446
|
+
<div style={{
|
|
447
|
+
position: 'relative',
|
|
448
|
+
height: 'calc(100vh - 200px)',
|
|
449
|
+
width: '100%',
|
|
450
|
+
borderRadius: 22,
|
|
451
|
+
overflow: 'hidden',
|
|
452
|
+
border: '1px solid var(--border)',
|
|
453
|
+
background: 'radial-gradient(circle at top right, rgba(107,73,223,0.08), transparent 24%), radial-gradient(circle at left center, rgba(18,121,109,0.08), transparent 22%), linear-gradient(180deg, rgba(255,255,255,0.92), rgba(247,244,239,0.96))',
|
|
454
|
+
boxShadow: 'var(--shadow-lg)',
|
|
455
|
+
}}>
|
|
456
|
+
<div style={{ position: 'absolute', top: 14, left: 16, zIndex: 8, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
457
|
+
<span className="hero-chip hero-chip-purple">Generalized</span>
|
|
458
|
+
<span className="hero-chip hero-chip-green">Evolved</span>
|
|
459
|
+
<span className="hero-chip hero-chip-neutral">Original</span>
|
|
460
|
+
<span className="hero-chip hero-chip-yellow">Projects</span>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<div style={{
|
|
464
|
+
position: 'absolute',
|
|
465
|
+
top: 14,
|
|
466
|
+
right: 16,
|
|
467
|
+
zIndex: 8,
|
|
468
|
+
padding: '10px 12px',
|
|
469
|
+
borderRadius: 18,
|
|
470
|
+
background: 'rgba(255,255,255,0.88)',
|
|
471
|
+
border: '1px solid var(--border)',
|
|
472
|
+
boxShadow: 'var(--shadow-sm)',
|
|
473
|
+
display: 'grid',
|
|
474
|
+
gap: 4,
|
|
475
|
+
minWidth: 180,
|
|
476
|
+
}}>
|
|
477
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase', color: 'var(--text-muted)' }}>Live network</div>
|
|
478
|
+
<div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--text)' }}>{stats.nodes} active skill nodes</div>
|
|
479
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.5 }}>
|
|
480
|
+
{stats.edges} relationships spanning {stats.clusters} clusters{stats.generalized > 0 ? ` • ${stats.generalized} generalized anchors` : ''}
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
|
|
612
484
|
<ReactFlow
|
|
613
485
|
nodes={flowNodes}
|
|
614
486
|
edges={flowEdges}
|
|
@@ -618,26 +490,338 @@ function GraphView({ flowNodes, flowEdges, stats, onNodeClick }: { flowNodes: No
|
|
|
618
490
|
fitView
|
|
619
491
|
fitViewOptions={{ padding: 0.2 }}
|
|
620
492
|
proOptions={{ hideAttribution: true }}
|
|
621
|
-
style={{ background: '
|
|
493
|
+
style={{ background: 'transparent' }}
|
|
622
494
|
minZoom={0.3}
|
|
623
495
|
maxZoom={1.5}
|
|
624
496
|
>
|
|
625
|
-
<Background color="
|
|
497
|
+
<Background color="rgba(112,104,94,0.22)" gap={20} size={1} />
|
|
626
498
|
<Controls position="bottom-left" />
|
|
627
499
|
</ReactFlow>
|
|
628
500
|
|
|
629
501
|
{/* Legend */}
|
|
630
502
|
<div style={{
|
|
631
|
-
position: 'absolute', bottom:
|
|
632
|
-
background: '
|
|
633
|
-
padding: '
|
|
634
|
-
boxShadow: 'var(--shadow-
|
|
503
|
+
position: 'absolute', bottom: 14, right: 14, zIndex: 10,
|
|
504
|
+
background: 'rgba(255,255,255,0.9)', border: '1px solid var(--border)', borderRadius: 18,
|
|
505
|
+
padding: '10px 14px', display: 'flex', gap: 14, fontSize: 10.5, color: 'var(--text-dim)',
|
|
506
|
+
boxShadow: 'var(--shadow-md)', flexWrap: 'wrap', maxWidth: 'min(720px, calc(100% - 28px))',
|
|
635
507
|
}}>
|
|
636
|
-
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#818cf8', marginRight:
|
|
637
|
-
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#22c55e', marginRight:
|
|
638
|
-
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#ef4444', marginRight:
|
|
639
|
-
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#fbbf24', marginRight:
|
|
640
|
-
<span style={{ color: '
|
|
508
|
+
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#818cf8', marginRight: 5, verticalAlign: 'middle' }} /> inherits</span>
|
|
509
|
+
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#22c55e', marginRight: 5, verticalAlign: 'middle' }} /> enhances</span>
|
|
510
|
+
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#ef4444', marginRight: 5, verticalAlign: 'middle', borderTop: '1px dashed #ef4444' }} /> conflicts</span>
|
|
511
|
+
<span><span style={{ display: 'inline-block', width: 12, height: 2, background: '#fbbf24', marginRight: 5, verticalAlign: 'middle' }} /> project link</span>
|
|
512
|
+
<span style={{ color: 'var(--text-muted)' }}>Scroll to zoom · Drag to pan · Click a node to inspect it</span>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function InspectorPanel({
|
|
519
|
+
selectedSkill,
|
|
520
|
+
selectedNode,
|
|
521
|
+
selectedEdges,
|
|
522
|
+
selectedEvo,
|
|
523
|
+
selectedContent,
|
|
524
|
+
view,
|
|
525
|
+
editing,
|
|
526
|
+
editContent,
|
|
527
|
+
adaptation,
|
|
528
|
+
actionLoading,
|
|
529
|
+
onClose,
|
|
530
|
+
onSelectSkill,
|
|
531
|
+
onViewChange,
|
|
532
|
+
onToggleEdit,
|
|
533
|
+
onEditContentChange,
|
|
534
|
+
onPromote,
|
|
535
|
+
onDemote,
|
|
536
|
+
onDelete,
|
|
537
|
+
onSave,
|
|
538
|
+
}: {
|
|
539
|
+
selectedSkill: string
|
|
540
|
+
selectedNode: SkillNode
|
|
541
|
+
selectedEdges: {
|
|
542
|
+
inheritsFrom: GraphEdge[]
|
|
543
|
+
children: GraphEdge[]
|
|
544
|
+
enhances: GraphEdge[]
|
|
545
|
+
conflicts: GraphEdge[]
|
|
546
|
+
} | null
|
|
547
|
+
selectedEvo: EvolutionEntry[]
|
|
548
|
+
selectedContent: string
|
|
549
|
+
view: SubView
|
|
550
|
+
editing: boolean
|
|
551
|
+
editContent: string
|
|
552
|
+
adaptation: { status: string; messages: { type: string; text: string }[]; suggestions: { type: string; description: string }[] } | null
|
|
553
|
+
actionLoading: boolean
|
|
554
|
+
onClose: () => void
|
|
555
|
+
onSelectSkill: (id: string) => void
|
|
556
|
+
onViewChange: (view: SubView) => void
|
|
557
|
+
onToggleEdit: () => void
|
|
558
|
+
onEditContentChange: (value: string) => void
|
|
559
|
+
onPromote: () => void | Promise<void>
|
|
560
|
+
onDemote: () => void | Promise<void>
|
|
561
|
+
onDelete: () => void | Promise<void>
|
|
562
|
+
onSave: () => void | Promise<void>
|
|
563
|
+
}) {
|
|
564
|
+
const acceptedCount = selectedEvo.filter(entry => entry.outcome === 'accepted').length
|
|
565
|
+
const rejectedCount = selectedEvo.filter(entry => entry.outcome !== 'accepted').length
|
|
566
|
+
const connectionGroups = selectedEdges ? [
|
|
567
|
+
{ label: 'Inherits from', tone: 'purple', ids: selectedEdges.inheritsFrom.map(edge => edge.from) },
|
|
568
|
+
{ label: 'Children', tone: 'blue', ids: selectedEdges.children.map(edge => edge.to) },
|
|
569
|
+
{ label: 'Enhances', tone: 'green', ids: [...new Set(selectedEdges.enhances.map(edge => edge.from === selectedSkill ? edge.to : edge.from))] },
|
|
570
|
+
{ label: 'Conflicts', tone: 'red', ids: [...new Set(selectedEdges.conflicts.map(edge => edge.from === selectedSkill ? edge.to : edge.from))] },
|
|
571
|
+
].filter(group => group.ids.length > 0) : []
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<div className="detail-panel" style={{
|
|
575
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.94), rgba(245,241,236,0.98))',
|
|
576
|
+
boxShadow: 'inset 1px 0 0 rgba(255,255,255,0.45)',
|
|
577
|
+
}}>
|
|
578
|
+
<div style={{
|
|
579
|
+
padding: '18px 18px 16px',
|
|
580
|
+
borderBottom: '1px solid var(--border)',
|
|
581
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.82), rgba(255,255,255,0.2))',
|
|
582
|
+
}}>
|
|
583
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, marginBottom: 14 }}>
|
|
584
|
+
<div>
|
|
585
|
+
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-muted)', letterSpacing: 0.6, textTransform: 'uppercase', marginBottom: 6 }}>
|
|
586
|
+
Skill inspector
|
|
587
|
+
</div>
|
|
588
|
+
<div style={{ fontSize: 21, fontWeight: 800, color: 'var(--text)', lineHeight: 1.2, marginBottom: 8 }}>{selectedNode.name}</div>
|
|
589
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
590
|
+
<span className="hero-chip hero-chip-neutral">{selectedNode.layer}</span>
|
|
591
|
+
{selectedNode.generation > 0 ? <span className="hero-chip hero-chip-green">gen {selectedNode.generation}</span> : <span className="hero-chip hero-chip-blue">original</span>}
|
|
592
|
+
{selectedNode.tags.slice(0, 4).map(tag => <span key={tag} className="hero-chip hero-chip-neutral">{tag}</span>)}
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
<button onClick={onClose} style={{
|
|
596
|
+
width: 34,
|
|
597
|
+
height: 34,
|
|
598
|
+
borderRadius: 999,
|
|
599
|
+
border: '1px solid var(--border)',
|
|
600
|
+
background: 'rgba(97,93,86,0.08)',
|
|
601
|
+
color: 'var(--text-dim)',
|
|
602
|
+
cursor: 'pointer',
|
|
603
|
+
fontSize: 16,
|
|
604
|
+
fontWeight: 700,
|
|
605
|
+
}}>×</button>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div style={{
|
|
609
|
+
padding: '16px',
|
|
610
|
+
borderRadius: 20,
|
|
611
|
+
border: '1px solid var(--border)',
|
|
612
|
+
background: 'linear-gradient(135deg, rgba(255,255,255,0.92), rgba(245,241,236,0.96))',
|
|
613
|
+
boxShadow: 'var(--shadow-sm)',
|
|
614
|
+
}}>
|
|
615
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, marginBottom: 10 }}>
|
|
616
|
+
<div style={{ fontSize: 40, fontWeight: 800, color: scoreColor(selectedNode.score), lineHeight: 1 }}>{(selectedNode.score * 100).toFixed(0)}</div>
|
|
617
|
+
<div style={{ paddingBottom: 6 }}>
|
|
618
|
+
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-muted)', letterSpacing: 0.4, textTransform: 'uppercase' }}>Quality score</div>
|
|
619
|
+
<div style={{ fontSize: 12, color: 'var(--text-dim)' }}>Current confidence in this skill’s network fit and maturity.</div>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
<div className="score-track" style={{ height: 7, marginBottom: 12 }}>
|
|
623
|
+
<div className="score-fill" style={{ width: `${selectedNode.score * 100}%`, background: `linear-gradient(90deg, var(--blue), ${scoreColor(selectedNode.score)})` }} />
|
|
624
|
+
</div>
|
|
625
|
+
<div className="grid-2" style={{ gap: 10 }}>
|
|
626
|
+
<div style={{ padding: '12px 14px', borderRadius: 16, background: 'rgba(107,73,223,0.08)', border: '1px solid rgba(107,73,223,0.14)' }}>
|
|
627
|
+
<div style={{ fontSize: 10, color: 'var(--purple)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.4, marginBottom: 4 }}>Failures mapped</div>
|
|
628
|
+
<div style={{ fontSize: 22, fontWeight: 800, color: 'var(--purple)' }}>{selectedNode.failureCount}</div>
|
|
629
|
+
</div>
|
|
630
|
+
<div style={{ padding: '12px 14px', borderRadius: 16, background: 'rgba(16,185,129,0.08)', border: '1px solid rgba(16,185,129,0.14)' }}>
|
|
631
|
+
<div style={{ fontSize: 10, color: 'var(--green)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.4, marginBottom: 4 }}>Last evolved</div>
|
|
632
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{selectedNode.lastEvolved ? new Date(selectedNode.lastEvolved).toLocaleDateString() : 'Not yet evolved'}</div>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
|
|
638
|
+
<div style={{ padding: '18px', display: 'grid', gap: 18 }}>
|
|
639
|
+
{connectionGroups.length > 0 ? (
|
|
640
|
+
<div>
|
|
641
|
+
<div className="section-label">Connections</div>
|
|
642
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
643
|
+
{connectionGroups.map(group => (
|
|
644
|
+
<div key={group.label} style={{ padding: '12px 14px', borderRadius: 18, background: 'rgba(255,255,255,0.74)', border: '1px solid var(--border)' }}>
|
|
645
|
+
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-muted)', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: 8 }}>{group.label}</div>
|
|
646
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
647
|
+
{group.ids.map(id => (
|
|
648
|
+
<button
|
|
649
|
+
key={`${group.label}-${id}`}
|
|
650
|
+
onClick={() => onSelectSkill(id)}
|
|
651
|
+
style={actionBtnStyle(
|
|
652
|
+
group.tone === 'purple'
|
|
653
|
+
? 'var(--purple)'
|
|
654
|
+
: group.tone === 'green'
|
|
655
|
+
? 'var(--green)'
|
|
656
|
+
: group.tone === 'red'
|
|
657
|
+
? 'var(--red)'
|
|
658
|
+
: 'var(--blue)'
|
|
659
|
+
)}
|
|
660
|
+
>
|
|
661
|
+
{id}
|
|
662
|
+
</button>
|
|
663
|
+
))}
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
))}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
) : null}
|
|
670
|
+
|
|
671
|
+
{selectedEvo.length > 0 ? (
|
|
672
|
+
<div>
|
|
673
|
+
<div className="section-label">Evolution record</div>
|
|
674
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
|
|
675
|
+
<span className="hero-chip hero-chip-green">{acceptedCount} accepted</span>
|
|
676
|
+
{rejectedCount > 0 ? <span className="hero-chip hero-chip-red">{rejectedCount} rejected</span> : null}
|
|
677
|
+
<span className="hero-chip hero-chip-neutral">{selectedEvo.length} total revisions</span>
|
|
678
|
+
</div>
|
|
679
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
680
|
+
{selectedEvo.map((entry, index) => (
|
|
681
|
+
<div key={`${entry.id}-${index}`} style={{
|
|
682
|
+
padding: '12px 14px',
|
|
683
|
+
borderRadius: 16,
|
|
684
|
+
background: 'rgba(255,255,255,0.74)',
|
|
685
|
+
border: `1px solid ${entry.outcome === 'accepted' ? 'rgba(16,185,129,0.18)' : 'rgba(239,68,68,0.18)'}`,
|
|
686
|
+
boxShadow: 'var(--shadow-xs)',
|
|
687
|
+
}}>
|
|
688
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', marginBottom: 6, flexWrap: 'wrap' }}>
|
|
689
|
+
<div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
690
|
+
<span className={`hero-chip hero-chip-${entry.outcome === 'accepted' ? 'green' : 'red'}`}>{entry.outcome}</span>
|
|
691
|
+
<span className="hero-chip hero-chip-neutral">{entry.action}</span>
|
|
692
|
+
</div>
|
|
693
|
+
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{new Date(entry.timestamp).toLocaleDateString()}</span>
|
|
694
|
+
</div>
|
|
695
|
+
<div style={{ fontSize: 12.5, color: 'var(--text-secondary)', lineHeight: 1.6, marginBottom: 8 }}>{entry.description}</div>
|
|
696
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
697
|
+
<span className="score-pill">T:{entry.task}</span>
|
|
698
|
+
<span className="score-pill">A:{entry.align}</span>
|
|
699
|
+
<span className="score-pill">S:{entry.sideEffect}</span>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
))}
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
) : null}
|
|
706
|
+
|
|
707
|
+
<div>
|
|
708
|
+
<div className="section-label">Navigate</div>
|
|
709
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
710
|
+
{view !== 'graph' ? <button onClick={() => onViewChange('graph')} style={actionBtnStyle('var(--blue)')}>◎ View in graph</button> : null}
|
|
711
|
+
{view !== 'general' ? <button onClick={() => onViewChange('general')} style={actionBtnStyle()}>▤ View in skills</button> : null}
|
|
712
|
+
{view !== 'coevolution' ? <button onClick={() => onViewChange('coevolution')} style={actionBtnStyle()}>↕ Co-evolution</button> : null}
|
|
713
|
+
{view !== 'projects' ? <button onClick={() => onViewChange('projects')} style={actionBtnStyle('var(--yellow)')}>◇ Project usage</button> : null}
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
|
|
717
|
+
<div>
|
|
718
|
+
<div className="section-label">Manage skill</div>
|
|
719
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
720
|
+
<button onClick={onToggleEdit} style={actionBtnStyle(editing ? 'var(--purple)' : undefined)}>
|
|
721
|
+
{editing ? '✕ Cancel edit' : '✎ Edit content'}
|
|
722
|
+
</button>
|
|
723
|
+
<button onClick={() => void onPromote()} disabled={actionLoading} style={{ ...actionBtnStyle('var(--blue)'), opacity: actionLoading ? 0.7 : 1 }}>
|
|
724
|
+
↑ Promote
|
|
725
|
+
</button>
|
|
726
|
+
<button onClick={() => void onDemote()} disabled={actionLoading} style={{ ...actionBtnStyle('var(--yellow)'), opacity: actionLoading ? 0.7 : 1 }}>
|
|
727
|
+
↓ Demote
|
|
728
|
+
</button>
|
|
729
|
+
<button onClick={() => void onDelete()} disabled={actionLoading} style={{ ...actionBtnStyle('var(--red)'), opacity: actionLoading ? 0.7 : 1 }}>
|
|
730
|
+
✕ Delete
|
|
731
|
+
</button>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
{editing ? (
|
|
736
|
+
<div>
|
|
737
|
+
<div className="section-label">Edit skill content</div>
|
|
738
|
+
<textarea
|
|
739
|
+
value={editContent}
|
|
740
|
+
onChange={event => onEditContentChange(event.target.value)}
|
|
741
|
+
style={{
|
|
742
|
+
width: '100%',
|
|
743
|
+
height: 260,
|
|
744
|
+
fontFamily: 'var(--font-mono)',
|
|
745
|
+
fontSize: 11.5,
|
|
746
|
+
background: '#1e1e2e',
|
|
747
|
+
color: '#cdd6f4',
|
|
748
|
+
border: '1px solid var(--border)',
|
|
749
|
+
borderRadius: 14,
|
|
750
|
+
padding: 14,
|
|
751
|
+
resize: 'vertical',
|
|
752
|
+
lineHeight: 1.65,
|
|
753
|
+
}}
|
|
754
|
+
/>
|
|
755
|
+
<button onClick={() => void onSave()} disabled={actionLoading} style={{ ...actionBtnStyle('var(--green)'), width: '100%', marginTop: 10, justifyContent: 'center', opacity: actionLoading ? 0.7 : 1 }}>
|
|
756
|
+
{actionLoading ? 'Saving changes…' : '✓ Save changes'}
|
|
757
|
+
</button>
|
|
758
|
+
</div>
|
|
759
|
+
) : null}
|
|
760
|
+
|
|
761
|
+
{adaptation ? (
|
|
762
|
+
<div>
|
|
763
|
+
<div className="section-label">Network adaptation</div>
|
|
764
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
|
|
765
|
+
<span className={`hero-chip hero-chip-${adaptation.status === 'ok' ? 'green' : adaptation.status === 'warning' ? 'yellow' : 'red'}`}>{adaptation.status}</span>
|
|
766
|
+
<span className="hero-chip hero-chip-neutral">post-change network response</span>
|
|
767
|
+
</div>
|
|
768
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
769
|
+
{adaptation.messages.map((message, index) => (
|
|
770
|
+
<div key={`${message.type}-${index}`} style={{
|
|
771
|
+
padding: '10px 12px',
|
|
772
|
+
borderRadius: 14,
|
|
773
|
+
border: `1px solid ${message.type === 'warning' ? 'var(--yellow-border)' : message.type === 'action' ? 'var(--red-border)' : 'var(--border)'}`,
|
|
774
|
+
background: message.type === 'warning' ? 'var(--yellow-light)' : message.type === 'action' ? 'var(--red-light)' : 'rgba(255,255,255,0.72)',
|
|
775
|
+
color: message.type === 'warning' ? 'var(--yellow)' : message.type === 'action' ? 'var(--red)' : 'var(--text-secondary)',
|
|
776
|
+
fontSize: 12,
|
|
777
|
+
lineHeight: 1.6,
|
|
778
|
+
}}>
|
|
779
|
+
{message.type === 'warning' ? '⚠ ' : message.type === 'action' ? '⚡ ' : 'ℹ '}{message.text}
|
|
780
|
+
</div>
|
|
781
|
+
))}
|
|
782
|
+
</div>
|
|
783
|
+
{adaptation.suggestions.length > 0 ? (
|
|
784
|
+
<div style={{ marginTop: 10, display: 'grid', gap: 6 }}>
|
|
785
|
+
{adaptation.suggestions.map((suggestion, index) => (
|
|
786
|
+
<div key={`${suggestion.type}-${index}`} style={{
|
|
787
|
+
padding: '10px 12px',
|
|
788
|
+
borderRadius: 14,
|
|
789
|
+
background: 'rgba(107,73,223,0.08)',
|
|
790
|
+
border: '1px solid rgba(107,73,223,0.14)',
|
|
791
|
+
fontSize: 11.5,
|
|
792
|
+
color: 'var(--text-secondary)',
|
|
793
|
+
lineHeight: 1.6,
|
|
794
|
+
}}>
|
|
795
|
+
<span className="badge badge-purple" style={{ marginRight: 8 }}>{suggestion.type}</span>
|
|
796
|
+
{suggestion.description}
|
|
797
|
+
</div>
|
|
798
|
+
))}
|
|
799
|
+
</div>
|
|
800
|
+
) : null}
|
|
801
|
+
</div>
|
|
802
|
+
) : null}
|
|
803
|
+
|
|
804
|
+
{selectedContent && !editing ? (
|
|
805
|
+
<div>
|
|
806
|
+
<div className="section-label">Skill content</div>
|
|
807
|
+
<pre style={{
|
|
808
|
+
margin: 0,
|
|
809
|
+
padding: '14px',
|
|
810
|
+
borderRadius: 16,
|
|
811
|
+
border: '1px solid var(--border)',
|
|
812
|
+
background: 'rgba(255,255,255,0.72)',
|
|
813
|
+
whiteSpace: 'pre-wrap',
|
|
814
|
+
wordBreak: 'break-word',
|
|
815
|
+
color: 'var(--text-secondary)',
|
|
816
|
+
lineHeight: 1.7,
|
|
817
|
+
maxHeight: 340,
|
|
818
|
+
overflow: 'auto',
|
|
819
|
+
fontSize: 11.5,
|
|
820
|
+
}}>
|
|
821
|
+
{selectedContent.slice(0, 2200)}
|
|
822
|
+
</pre>
|
|
823
|
+
</div>
|
|
824
|
+
) : null}
|
|
641
825
|
</div>
|
|
642
826
|
</div>
|
|
643
827
|
)
|