helixevo 0.2.27 → 0.2.29
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { execSync } from 'child_process'
|
|
3
|
-
import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, mkdirSync } from 'fs'
|
|
3
|
+
import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'fs'
|
|
4
4
|
import { join } from 'path'
|
|
5
5
|
import { homedir } from 'os'
|
|
6
6
|
|
|
@@ -45,8 +45,15 @@ function copyDashboardFiles(sourceDir: string, newVersion: string): void {
|
|
|
45
45
|
// Write version marker
|
|
46
46
|
writeFileSync(join(HELIX_DASHBOARD_DIR, '.helixevo-version'), newVersion)
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Write .env.local — survives copyToHelix overwrites from old CLIs
|
|
49
49
|
writeFileSync(join(HELIX_DASHBOARD_DIR, '.env.local'), `HELIXEVO_VERSION=${newVersion}\n`)
|
|
50
|
+
|
|
51
|
+
// Touch next.config.mjs to trigger Next.js dev mode auto-restart
|
|
52
|
+
const configPath = join(HELIX_DASHBOARD_DIR, 'next.config.mjs')
|
|
53
|
+
if (existsSync(configPath)) {
|
|
54
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
55
|
+
writeFileSync(configPath, content) // rewrite same content = new mtime
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export async function POST() {
|
|
@@ -313,7 +313,7 @@ export default function NetworkClient({
|
|
|
313
313
|
<button
|
|
314
314
|
key={sv.key}
|
|
315
315
|
className={`tab-item ${view === sv.key ? 'active' : ''}`}
|
|
316
|
-
onClick={() => { setView(sv.key)
|
|
316
|
+
onClick={() => { setView(sv.key) }}
|
|
317
317
|
>
|
|
318
318
|
{sv.label}
|
|
319
319
|
</button>
|
|
@@ -324,7 +324,7 @@ export default function NetworkClient({
|
|
|
324
324
|
<div style={{ display: 'flex', gap: 0 }}>
|
|
325
325
|
{/* Main content */}
|
|
326
326
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
327
|
-
{view === 'graph' && <GraphView flowNodes={flowNodes} flowEdges={flowEdges} stats={stats} />}
|
|
327
|
+
{view === 'graph' && <GraphView flowNodes={flowNodes} flowEdges={flowEdges} stats={stats} onNodeClick={setSelectedSkill} />}
|
|
328
328
|
{view === 'general' && (
|
|
329
329
|
<GeneralView
|
|
330
330
|
generalized={generalized} evolved={evolved} original={original}
|
|
@@ -339,6 +339,7 @@ export default function NetworkClient({
|
|
|
339
339
|
<CoEvolutionView
|
|
340
340
|
generalized={generalized} evolved={evolved} original={original}
|
|
341
341
|
edges={graphEdges} evolutionBySkill={evolutionBySkill} projects={projects}
|
|
342
|
+
onSelect={setSelectedSkill}
|
|
342
343
|
/>
|
|
343
344
|
)}
|
|
344
345
|
</div>
|
|
@@ -386,20 +387,20 @@ export default function NetworkClient({
|
|
|
386
387
|
{selectedEdges.inheritsFrom.length > 0 && (
|
|
387
388
|
<div style={{ fontSize: 12 }}>
|
|
388
389
|
<span style={{ color: 'var(--text-dim)' }}>Inherits from: </span>
|
|
389
|
-
{selectedEdges.inheritsFrom.map(e => <span key={e.from} className="badge badge-purple" style={{ marginRight: 3 }}>{e.from}</span>)}
|
|
390
|
+
{selectedEdges.inheritsFrom.map(e => <span key={e.from} className="badge badge-purple" style={{ marginRight: 3, cursor: 'pointer' }} onClick={() => setSelectedSkill(e.from)}>{e.from}</span>)}
|
|
390
391
|
</div>
|
|
391
392
|
)}
|
|
392
393
|
{selectedEdges.children.length > 0 && (
|
|
393
394
|
<div style={{ fontSize: 12 }}>
|
|
394
395
|
<span style={{ color: 'var(--text-dim)' }}>Children: </span>
|
|
395
|
-
{selectedEdges.children.map(e => <span key={e.to} className="badge badge-blue" style={{ marginRight: 3 }}>{e.to}</span>)}
|
|
396
|
+
{selectedEdges.children.map(e => <span key={e.to} className="badge badge-blue" style={{ marginRight: 3, cursor: 'pointer' }} onClick={() => setSelectedSkill(e.to)}>{e.to}</span>)}
|
|
396
397
|
</div>
|
|
397
398
|
)}
|
|
398
399
|
{selectedEdges.enhances.length > 0 && (
|
|
399
400
|
<div style={{ fontSize: 12 }}>
|
|
400
401
|
<span style={{ color: 'var(--text-dim)' }}>Enhances: </span>
|
|
401
402
|
{[...new Set(selectedEdges.enhances.map(e => e.from === selectedSkill ? e.to : e.from))].map(id =>
|
|
402
|
-
<span key={id} className="badge badge-green" style={{ marginRight: 3 }}>{id}</span>
|
|
403
|
+
<span key={id} className="badge badge-green" style={{ marginRight: 3, cursor: 'pointer' }} onClick={() => setSelectedSkill(id)}>{id}</span>
|
|
403
404
|
)}
|
|
404
405
|
</div>
|
|
405
406
|
)}
|
|
@@ -407,7 +408,7 @@ export default function NetworkClient({
|
|
|
407
408
|
<div style={{ fontSize: 12 }}>
|
|
408
409
|
<span style={{ color: 'var(--text-dim)' }}>Conflicts: </span>
|
|
409
410
|
{[...new Set(selectedEdges.conflicts.map(e => e.from === selectedSkill ? e.to : e.from))].map(id =>
|
|
410
|
-
<span key={id} className="badge badge-red" style={{ marginRight: 3 }}>{id}</span>
|
|
411
|
+
<span key={id} className="badge badge-red" style={{ marginRight: 3, cursor: 'pointer' }} onClick={() => setSelectedSkill(id)}>{id}</span>
|
|
411
412
|
)}
|
|
412
413
|
</div>
|
|
413
414
|
)}
|
|
@@ -440,6 +441,28 @@ export default function NetworkClient({
|
|
|
440
441
|
</div>
|
|
441
442
|
)}
|
|
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
|
+
|
|
443
466
|
{/* ─── Management Actions ─── */}
|
|
444
467
|
<div style={{ marginBottom: 20 }}>
|
|
445
468
|
<div className="section-label">Actions</div>
|
|
@@ -583,7 +606,7 @@ export default function NetworkClient({
|
|
|
583
606
|
// SUB-VIEW: Graph
|
|
584
607
|
// ═══════════════════════════════════════════════════════════════════
|
|
585
608
|
|
|
586
|
-
function GraphView({ flowNodes, flowEdges, stats }: { flowNodes: Node[]; flowEdges: Edge[]; stats: Props['stats'] }) {
|
|
609
|
+
function GraphView({ flowNodes, flowEdges, stats, onNodeClick }: { flowNodes: Node[]; flowEdges: Edge[]; stats: Props['stats']; onNodeClick: (id: string) => void }) {
|
|
587
610
|
return (
|
|
588
611
|
<div style={{ position: 'relative', height: 'calc(100vh - 200px)', width: '100%', borderRadius: 12, overflow: 'hidden', border: '1px solid var(--border)', background: 'var(--bg)' }}>
|
|
589
612
|
<ReactFlow
|
|
@@ -591,6 +614,7 @@ function GraphView({ flowNodes, flowEdges, stats }: { flowNodes: Node[]; flowEdg
|
|
|
591
614
|
edges={flowEdges}
|
|
592
615
|
nodeTypes={nodeTypes}
|
|
593
616
|
connectionLineType={ConnectionLineType.SmoothStep}
|
|
617
|
+
onNodeClick={(_event, node) => onNodeClick(node.id)}
|
|
594
618
|
fitView
|
|
595
619
|
fitViewOptions={{ padding: 0.2 }}
|
|
596
620
|
proOptions={{ hideAttribution: true }}
|
|
@@ -808,13 +832,24 @@ function ProjectsView({
|
|
|
808
832
|
</div>
|
|
809
833
|
</div>
|
|
810
834
|
{unresolved.length >= 3 && (
|
|
811
|
-
<
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
835
|
+
<button
|
|
836
|
+
onClick={async () => {
|
|
837
|
+
try {
|
|
838
|
+
await fetch('/api/run', {
|
|
839
|
+
method: 'POST',
|
|
840
|
+
headers: { 'Content-Type': 'application/json' },
|
|
841
|
+
body: JSON.stringify({ command: 'specialize', project: project.name }),
|
|
842
|
+
})
|
|
843
|
+
} catch {}
|
|
844
|
+
}}
|
|
845
|
+
style={{
|
|
846
|
+
padding: '6px 14px', borderRadius: 8, border: 'none', cursor: 'pointer',
|
|
847
|
+
background: 'var(--green)', color: '#fff',
|
|
848
|
+
fontSize: 11, fontWeight: 600,
|
|
849
|
+
}}
|
|
850
|
+
>
|
|
851
|
+
Specialize for {project.name} →
|
|
852
|
+
</button>
|
|
818
853
|
)}
|
|
819
854
|
</div>
|
|
820
855
|
</div>
|
|
@@ -869,10 +904,11 @@ function ProjectsView({
|
|
|
869
904
|
// ═══════════════════════════════════════════════════════════════════
|
|
870
905
|
|
|
871
906
|
function CoEvolutionView({
|
|
872
|
-
generalized, evolved, original, edges, evolutionBySkill, projects,
|
|
907
|
+
generalized, evolved, original, edges, evolutionBySkill, projects, onSelect,
|
|
873
908
|
}: {
|
|
874
909
|
generalized: SkillNode[]; evolved: SkillNode[]; original: SkillNode[]
|
|
875
910
|
edges: GraphEdge[]; evolutionBySkill: Record<string, EvolutionEntry[]>; projects: ProjectData[]
|
|
911
|
+
onSelect: (id: string) => void
|
|
876
912
|
}) {
|
|
877
913
|
const allNodes = [...generalized, ...evolved, ...original]
|
|
878
914
|
|
|
@@ -924,7 +960,7 @@ function CoEvolutionView({
|
|
|
924
960
|
width: 8, height: 8, borderRadius: '50%',
|
|
925
961
|
background: 'var(--purple)', flexShrink: 0,
|
|
926
962
|
}} />
|
|
927
|
-
<span style={{ fontWeight: 700, fontSize: 14 }}>{chain.parent.name}</span>
|
|
963
|
+
<span onClick={() => onSelect(chain.parent.id)} style={{ fontWeight: 700, fontSize: 14, cursor: 'pointer' }}>{chain.parent.name}</span>
|
|
928
964
|
<span style={{ fontSize: 22, fontWeight: 800, color: scoreColor(chain.parent.score), marginLeft: 'auto' }}>
|
|
929
965
|
{(chain.parent.score * 100).toFixed(0)}
|
|
930
966
|
</span>
|
|
@@ -939,7 +975,7 @@ function CoEvolutionView({
|
|
|
939
975
|
padding: '6px 0',
|
|
940
976
|
}}>
|
|
941
977
|
<span style={{ fontSize: 10, color: 'var(--purple)' }}>↳</span>
|
|
942
|
-
<span style={{ fontWeight: 500, fontSize: 13 }}>{child.name}</span>
|
|
978
|
+
<span onClick={() => onSelect(child.id)} style={{ fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>{child.name}</span>
|
|
943
979
|
<span className="badge badge-green" style={{ fontSize: 9 }}>gen {child.generation}</span>
|
|
944
980
|
<span style={{ marginLeft: 'auto', fontSize: 13, fontWeight: 700, color: scoreColor(child.score) }}>
|
|
945
981
|
{(child.score * 100).toFixed(0)}
|
|
@@ -968,9 +1004,9 @@ function CoEvolutionView({
|
|
|
968
1004
|
display: 'flex', alignItems: 'center', gap: 10,
|
|
969
1005
|
padding: '8px 0', borderBottom: i < enhancePairs.length - 1 ? '1px solid var(--border)' : 'none',
|
|
970
1006
|
}}>
|
|
971
|
-
<span style={{ fontWeight: 500, fontSize: 13 }}>{pair.from.name}</span>
|
|
1007
|
+
<span onClick={() => onSelect(pair.from.id)} style={{ fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>{pair.from.name}</span>
|
|
972
1008
|
<span style={{ color: 'var(--green)', fontSize: 12 }}>↔</span>
|
|
973
|
-
<span style={{ fontWeight: 500, fontSize: 13 }}>{pair.to.name}</span>
|
|
1009
|
+
<span onClick={() => onSelect(pair.to.id)} style={{ fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>{pair.to.name}</span>
|
|
974
1010
|
<span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-dim)' }}>
|
|
975
1011
|
strength: {(pair.strength * 100).toFixed(0)}%
|
|
976
1012
|
</span>
|
|
@@ -991,9 +1027,9 @@ function CoEvolutionView({
|
|
|
991
1027
|
display: 'flex', alignItems: 'center', gap: 10,
|
|
992
1028
|
padding: '8px 0', borderBottom: i < conflictPairs.length - 1 ? '1px solid var(--border)' : 'none',
|
|
993
1029
|
}}>
|
|
994
|
-
<span style={{ fontWeight: 500, fontSize: 13 }}>{pair.from.name}</span>
|
|
1030
|
+
<span onClick={() => onSelect(pair.from.id)} style={{ fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>{pair.from.name}</span>
|
|
995
1031
|
<span style={{ color: 'var(--red)', fontSize: 12 }}>⚡</span>
|
|
996
|
-
<span style={{ fontWeight: 500, fontSize: 13 }}>{pair.to.name}</span>
|
|
1032
|
+
<span onClick={() => onSelect(pair.to.id)} style={{ fontWeight: 500, fontSize: 13, cursor: 'pointer' }}>{pair.to.name}</span>
|
|
997
1033
|
<span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-dim)' }}>
|
|
998
1034
|
strength: {(pair.strength * 100).toFixed(0)}%
|
|
999
1035
|
</span>
|
|
@@ -15,7 +15,7 @@ function compareVersions(current: string, latest: string): boolean {
|
|
|
15
15
|
return false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
type UpdateState = 'idle' | 'updating' | '
|
|
18
|
+
type UpdateState = 'idle' | 'updating' | 'reloading' | 'error'
|
|
19
19
|
|
|
20
20
|
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
21
21
|
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
@@ -23,7 +23,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
23
23
|
const [state, setState] = useState<UpdateState>('idle')
|
|
24
24
|
const [newVersion, setNewVersion] = useState<string | null>(null)
|
|
25
25
|
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
|
26
|
-
const [
|
|
26
|
+
const [countdown, setCountdown] = useState(0)
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
29
|
let mounted = true
|
|
@@ -53,8 +53,22 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
53
53
|
const data = await res.json()
|
|
54
54
|
|
|
55
55
|
if (data.success) {
|
|
56
|
-
setState('installed')
|
|
57
56
|
setNewVersion(data.version)
|
|
57
|
+
setState('reloading')
|
|
58
|
+
|
|
59
|
+
// Next.js dev mode detects config change and auto-restarts.
|
|
60
|
+
// Wait for it to recompile, then reload.
|
|
61
|
+
let secs = 8
|
|
62
|
+
setCountdown(secs)
|
|
63
|
+
const timer = setInterval(() => {
|
|
64
|
+
secs--
|
|
65
|
+
setCountdown(secs)
|
|
66
|
+
if (secs <= 0) {
|
|
67
|
+
clearInterval(timer)
|
|
68
|
+
// Hard reload with cache busting
|
|
69
|
+
window.location.href = window.location.pathname + '?v=' + Date.now()
|
|
70
|
+
}
|
|
71
|
+
}, 1000)
|
|
58
72
|
} else {
|
|
59
73
|
setState('error')
|
|
60
74
|
setErrorMsg(data.error ?? 'Update failed')
|
|
@@ -65,65 +79,47 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
const restartCmd = 'helixevo dashboard'
|
|
69
|
-
const handleCopy = async () => {
|
|
70
|
-
try {
|
|
71
|
-
await navigator.clipboard.writeText(restartCmd)
|
|
72
|
-
setCopied(true)
|
|
73
|
-
setTimeout(() => setCopied(false), 2000)
|
|
74
|
-
} catch {}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
82
|
return (
|
|
78
83
|
<div style={{
|
|
79
84
|
position: 'fixed',
|
|
80
85
|
bottom: 20,
|
|
81
86
|
right: 20,
|
|
82
|
-
width:
|
|
87
|
+
width: 320,
|
|
83
88
|
background: 'var(--bg-card)',
|
|
84
|
-
border: `1px solid ${state === '
|
|
89
|
+
border: `1px solid ${state === 'reloading' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
|
|
85
90
|
borderRadius: 'var(--radius-lg)',
|
|
86
91
|
boxShadow: 'var(--shadow-xl)',
|
|
87
92
|
padding: '16px 18px',
|
|
88
93
|
zIndex: 9999,
|
|
89
94
|
animation: 'updateSlideIn 0.4s ease-out',
|
|
90
95
|
}}>
|
|
91
|
-
{/* Close
|
|
92
|
-
{state
|
|
96
|
+
{/* Close */}
|
|
97
|
+
{(state === 'idle' || state === 'error') && (
|
|
93
98
|
<button
|
|
94
99
|
onClick={() => setDismissed(true)}
|
|
95
100
|
style={{
|
|
96
101
|
position: 'absolute', top: 8, right: 10,
|
|
97
102
|
background: 'none', border: 'none', cursor: 'pointer',
|
|
98
|
-
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
|
|
99
|
-
padding: '2px 4px',
|
|
103
|
+
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1, padding: '2px 4px',
|
|
100
104
|
}}
|
|
101
|
-
|
|
102
|
-
>
|
|
103
|
-
×
|
|
104
|
-
</button>
|
|
105
|
+
>×</button>
|
|
105
106
|
)}
|
|
106
107
|
|
|
107
108
|
{/* Header */}
|
|
108
109
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
109
110
|
<div style={{
|
|
110
111
|
width: 28, height: 28, borderRadius: '50%',
|
|
111
|
-
background: state === '
|
|
112
|
-
|
|
113
|
-
: 'var(--purple-light)',
|
|
114
|
-
display: 'flex',
|
|
115
|
-
alignItems: 'center', justifyContent: 'center',
|
|
112
|
+
background: state === 'reloading' ? 'var(--green-light)' : state === 'error' ? 'var(--red-light)' : 'var(--purple-light)',
|
|
113
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
116
114
|
}}>
|
|
117
|
-
{state === 'updating' ? (
|
|
115
|
+
{(state === 'updating' || state === 'reloading') ? (
|
|
118
116
|
<div style={{
|
|
119
|
-
width: 14, height: 14,
|
|
120
|
-
|
|
117
|
+
width: 14, height: 14,
|
|
118
|
+
border: `2px solid ${state === 'reloading' ? 'var(--green-border)' : 'var(--purple-border)'}`,
|
|
119
|
+
borderTopColor: state === 'reloading' ? 'var(--green)' : 'var(--purple)',
|
|
120
|
+
borderRadius: '50%',
|
|
121
121
|
animation: 'updateSpin 0.8s linear infinite',
|
|
122
122
|
}} />
|
|
123
|
-
) : state === 'installed' ? (
|
|
124
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
125
|
-
<path d="M20 6L9 17l-5-5" />
|
|
126
|
-
</svg>
|
|
127
123
|
) : state === 'error' ? (
|
|
128
124
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--red)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
129
125
|
<path d="M18 6L6 18M6 6l12 12" />
|
|
@@ -135,41 +131,29 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
135
131
|
)}
|
|
136
132
|
</div>
|
|
137
133
|
<div>
|
|
138
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)'
|
|
134
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>
|
|
139
135
|
{state === 'updating' ? 'Installing...'
|
|
140
|
-
: state === '
|
|
136
|
+
: state === 'reloading' ? 'Reloading...'
|
|
141
137
|
: state === 'error' ? 'Update Failed'
|
|
142
138
|
: 'Update Available'}
|
|
143
139
|
</div>
|
|
144
140
|
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
145
|
-
{state === '
|
|
146
|
-
? <>v{newVersion}
|
|
141
|
+
{state === 'reloading'
|
|
142
|
+
? <>v{newVersion} installed — refreshing in {countdown}s</>
|
|
147
143
|
: <>v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
|
|
148
144
|
}
|
|
149
145
|
</div>
|
|
150
146
|
</div>
|
|
151
147
|
</div>
|
|
152
148
|
|
|
153
|
-
{/* Idle
|
|
149
|
+
{/* Idle */}
|
|
154
150
|
{state === 'idle' && (
|
|
155
|
-
<button
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
color: '#fff',
|
|
162
|
-
border: 'none',
|
|
163
|
-
borderRadius: 'var(--radius)',
|
|
164
|
-
fontSize: 13,
|
|
165
|
-
fontWeight: 600,
|
|
166
|
-
cursor: 'pointer',
|
|
167
|
-
display: 'flex',
|
|
168
|
-
alignItems: 'center',
|
|
169
|
-
justifyContent: 'center',
|
|
170
|
-
gap: 6,
|
|
171
|
-
}}
|
|
172
|
-
>
|
|
151
|
+
<button onClick={handleUpdate} style={{
|
|
152
|
+
width: '100%', padding: '9px 16px',
|
|
153
|
+
background: 'var(--purple)', color: '#fff', border: 'none',
|
|
154
|
+
borderRadius: 'var(--radius)', fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
155
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
|
156
|
+
}}>
|
|
173
157
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
174
158
|
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
175
159
|
</svg>
|
|
@@ -177,62 +161,36 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
177
161
|
</button>
|
|
178
162
|
)}
|
|
179
163
|
|
|
180
|
-
{/* Updating
|
|
164
|
+
{/* Updating */}
|
|
181
165
|
{state === 'updating' && (
|
|
182
166
|
<div style={{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
borderRadius: 'var(--radius)',
|
|
187
|
-
fontSize: 12,
|
|
188
|
-
color: 'var(--text-secondary)',
|
|
189
|
-
textAlign: 'center',
|
|
167
|
+
padding: '9px 16px', background: 'var(--bg-section)',
|
|
168
|
+
borderRadius: 'var(--radius)', fontSize: 12,
|
|
169
|
+
color: 'var(--text-secondary)', textAlign: 'center',
|
|
190
170
|
}}>
|
|
191
171
|
Installing helixevo@latest...
|
|
192
172
|
</div>
|
|
193
173
|
)}
|
|
194
174
|
|
|
195
|
-
{/*
|
|
196
|
-
{state === '
|
|
175
|
+
{/* Reloading with countdown */}
|
|
176
|
+
{state === 'reloading' && (
|
|
197
177
|
<div>
|
|
198
178
|
<div style={{
|
|
199
|
-
padding: '
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
color: 'var(--green)',
|
|
204
|
-
fontWeight: 600,
|
|
205
|
-
marginBottom: 10,
|
|
206
|
-
textAlign: 'center',
|
|
179
|
+
padding: '9px 16px', background: 'var(--green-light)',
|
|
180
|
+
borderRadius: 'var(--radius)', fontSize: 12,
|
|
181
|
+
color: 'var(--green)', textAlign: 'center', fontWeight: 600,
|
|
182
|
+
marginBottom: 8,
|
|
207
183
|
}}>
|
|
208
|
-
|
|
184
|
+
Dashboard will refresh automatically
|
|
209
185
|
</div>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
2. Run:
|
|
219
|
-
</div>
|
|
220
|
-
<div
|
|
221
|
-
onClick={handleCopy}
|
|
222
|
-
style={{
|
|
223
|
-
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
224
|
-
padding: '7px 12px',
|
|
225
|
-
background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
226
|
-
borderRadius: 'var(--radius)',
|
|
227
|
-
fontFamily: 'var(--font-mono)', fontSize: 11,
|
|
228
|
-
color: 'var(--text)', cursor: 'pointer',
|
|
229
|
-
}}
|
|
230
|
-
>
|
|
231
|
-
<span>$ {restartCmd}</span>
|
|
232
|
-
<span style={{ fontSize: 10, color: copied ? 'var(--green)' : 'var(--purple)', fontWeight: 600, fontFamily: 'var(--font)' }}>
|
|
233
|
-
{copied ? 'Copied!' : 'Copy'}
|
|
234
|
-
</span>
|
|
235
|
-
</div>
|
|
186
|
+
{/* Progress bar */}
|
|
187
|
+
<div style={{ height: 3, background: 'var(--bg-section)', borderRadius: 2, overflow: 'hidden' }}>
|
|
188
|
+
<div style={{
|
|
189
|
+
height: '100%', background: 'var(--green)',
|
|
190
|
+
borderRadius: 2,
|
|
191
|
+
width: `${((8 - countdown) / 8) * 100}%`,
|
|
192
|
+
transition: 'width 1s linear',
|
|
193
|
+
}} />
|
|
236
194
|
</div>
|
|
237
195
|
</div>
|
|
238
196
|
)}
|
|
@@ -241,32 +199,18 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
241
199
|
{state === 'error' && (
|
|
242
200
|
<>
|
|
243
201
|
<div style={{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
borderRadius: 'var(--radius)',
|
|
248
|
-
fontSize: 11,
|
|
249
|
-
color: 'var(--red)',
|
|
250
|
-
marginBottom: 8,
|
|
251
|
-
maxHeight: 60,
|
|
252
|
-
overflow: 'auto',
|
|
202
|
+
padding: '8px 12px', background: 'var(--red-light)',
|
|
203
|
+
borderRadius: 'var(--radius)', fontSize: 11, color: 'var(--red)',
|
|
204
|
+
marginBottom: 8, maxHeight: 60, overflow: 'auto',
|
|
253
205
|
}}>
|
|
254
206
|
{errorMsg}
|
|
255
207
|
</div>
|
|
256
|
-
<button
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
color: 'var(--text-secondary)',
|
|
263
|
-
border: '1px solid var(--border)',
|
|
264
|
-
borderRadius: 'var(--radius)',
|
|
265
|
-
fontSize: 12,
|
|
266
|
-
fontWeight: 600,
|
|
267
|
-
cursor: 'pointer',
|
|
268
|
-
}}
|
|
269
|
-
>
|
|
208
|
+
<button onClick={handleUpdate} style={{
|
|
209
|
+
width: '100%', padding: '7px 12px',
|
|
210
|
+
background: 'var(--bg-section)', color: 'var(--text-secondary)',
|
|
211
|
+
border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
212
|
+
fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
213
|
+
}}>
|
|
270
214
|
Retry
|
|
271
215
|
</button>
|
|
272
216
|
</>
|
package/package.json
CHANGED