groove-dev 0.27.64 → 0.27.66
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/README.md +1 -1
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +103 -31
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-DiiEKVEo.js → index-BvvSZvQz.js} +1735 -1735
- package/node_modules/@groove-dev/gui/dist/assets/index-DFp5IOnd.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/network/activity-chart.jsx +10 -14
- package/node_modules/@groove-dev/gui/src/components/network/compute-header.jsx +67 -200
- package/node_modules/@groove-dev/gui/src/components/network/earnings-card.jsx +30 -0
- package/node_modules/@groove-dev/gui/src/components/network/fleet-table.jsx +114 -72
- package/node_modules/@groove-dev/gui/src/components/network/identity-bar.jsx +94 -0
- package/node_modules/@groove-dev/gui/src/components/network/node-card.jsx +88 -0
- package/node_modules/@groove-dev/gui/src/components/network/wallet-view.jsx +77 -0
- package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +13 -0
- package/node_modules/@groove-dev/gui/src/views/network.jsx +59 -18
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +103 -31
- package/packages/daemon/src/providers/claude-code.js +0 -1
- package/packages/gui/dist/assets/{index-DiiEKVEo.js → index-BvvSZvQz.js} +1735 -1735
- package/packages/gui/dist/assets/index-DFp5IOnd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/network/activity-chart.jsx +10 -14
- package/packages/gui/src/components/network/compute-header.jsx +67 -200
- package/packages/gui/src/components/network/earnings-card.jsx +30 -0
- package/packages/gui/src/components/network/fleet-table.jsx +114 -72
- package/packages/gui/src/components/network/identity-bar.jsx +94 -0
- package/packages/gui/src/components/network/node-card.jsx +88 -0
- package/packages/gui/src/components/network/wallet-view.jsx +77 -0
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +1 -1
- package/packages/gui/src/stores/groove.js +13 -0
- package/packages/gui/src/views/network.jsx +59 -18
- package/node_modules/@groove-dev/gui/dist/assets/index-B3AqeyS4.css +0 -1
- package/packages/gui/dist/assets/index-B3AqeyS4.css +0 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { memo, useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
4
|
+
import { cn } from '../../lib/cn';
|
|
5
|
+
import { StatusDot } from '../ui/status-dot';
|
|
6
|
+
import { Button } from '../ui/button';
|
|
7
|
+
import { Tooltip } from '../ui/tooltip';
|
|
8
|
+
import { fmtUptime } from '../../lib/format';
|
|
9
|
+
import { Copy, Check, Wallet } from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
function shortAddr(addr) {
|
|
12
|
+
if (!addr || typeof addr !== 'string') return '—';
|
|
13
|
+
if (addr.length < 14) return addr;
|
|
14
|
+
return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function nodeStatusMap(node) {
|
|
18
|
+
if (node.active && node.status === 'connected') return 'running';
|
|
19
|
+
if (node.status === 'connecting') return 'starting';
|
|
20
|
+
return 'stopped';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function statusColor(status) {
|
|
24
|
+
if (status === 'connected') return 'text-success';
|
|
25
|
+
if (status === 'connecting') return 'text-warning';
|
|
26
|
+
return 'text-danger';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const IdentityBar = memo(function IdentityBar() {
|
|
30
|
+
const node = useGrooveStore((s) => s.networkNode);
|
|
31
|
+
const wallet = useGrooveStore((s) => s.networkWallet);
|
|
32
|
+
const [copied, setCopied] = useState(false);
|
|
33
|
+
const [uptime, setUptime] = useState(0);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!node.startedAt) return;
|
|
37
|
+
const tick = () => setUptime(Math.floor((Date.now() - node.startedAt) / 1000));
|
|
38
|
+
tick();
|
|
39
|
+
const id = setInterval(tick, 1000);
|
|
40
|
+
return () => clearInterval(id);
|
|
41
|
+
}, [node.startedAt]);
|
|
42
|
+
|
|
43
|
+
const handleCopy = useCallback(async () => {
|
|
44
|
+
if (!node.nodeId) return;
|
|
45
|
+
try {
|
|
46
|
+
await navigator.clipboard.writeText(node.nodeId);
|
|
47
|
+
setCopied(true);
|
|
48
|
+
setTimeout(() => setCopied(false), 1500);
|
|
49
|
+
} catch { /* clipboard blocked */ }
|
|
50
|
+
}, [node.nodeId]);
|
|
51
|
+
|
|
52
|
+
const connStatus = node.status || 'disconnected';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex items-center h-11 px-4 bg-surface-0 border-b border-border-subtle gap-3">
|
|
56
|
+
{/* Left: node identity */}
|
|
57
|
+
<div className="flex items-center gap-2">
|
|
58
|
+
<StatusDot status={nodeStatusMap(node)} size="sm" />
|
|
59
|
+
<code className="text-xs font-mono text-text-0">{shortAddr(node.nodeId)}</code>
|
|
60
|
+
<Tooltip content={copied ? 'Copied' : 'Copy address'} side="bottom">
|
|
61
|
+
<button
|
|
62
|
+
onClick={handleCopy}
|
|
63
|
+
className="h-6 w-6 inline-flex items-center justify-center rounded border border-border-subtle text-text-3 hover:text-accent hover:border-accent/40 cursor-pointer transition-colors"
|
|
64
|
+
>
|
|
65
|
+
{copied ? <Check size={10} /> : <Copy size={10} />}
|
|
66
|
+
</button>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{/* Center: status + uptime */}
|
|
71
|
+
<div className="flex-1 flex items-center justify-center gap-3">
|
|
72
|
+
<span className={cn('text-2xs uppercase tracking-widest font-sans', statusColor(connStatus))}>
|
|
73
|
+
{connStatus === 'connected' ? 'Connected' : connStatus === 'connecting' ? 'Connecting' : 'Disconnected'}
|
|
74
|
+
</span>
|
|
75
|
+
<span className="w-1 h-1 rounded-full bg-text-4" />
|
|
76
|
+
<span className="text-2xs font-mono text-text-3 tabular-nums">
|
|
77
|
+
{fmtUptime(uptime)}
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Right: token balance + wallet */}
|
|
82
|
+
<div className="flex items-center gap-3">
|
|
83
|
+
<span className="text-xs font-mono text-text-2">
|
|
84
|
+
<span className="text-text-0">{wallet.balance}</span>
|
|
85
|
+
<span className="text-text-3"> GROOVE</span>
|
|
86
|
+
</span>
|
|
87
|
+
<Button variant="outline" size="sm" className="text-2xs gap-1.5" disabled>
|
|
88
|
+
<Wallet size={12} />
|
|
89
|
+
Connect Wallet
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
4
|
+
import { cn } from '../../lib/cn';
|
|
5
|
+
import { StatusDot } from '../ui/status-dot';
|
|
6
|
+
|
|
7
|
+
function shortAddr(addr) {
|
|
8
|
+
if (!addr || typeof addr !== 'string') return '—';
|
|
9
|
+
if (addr.length < 14) return addr;
|
|
10
|
+
return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function nodeStatusMap(node) {
|
|
14
|
+
if (node.active && node.status === 'connected') return 'running';
|
|
15
|
+
if (node.status === 'connecting') return 'starting';
|
|
16
|
+
return 'stopped';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function modelShort(model) {
|
|
20
|
+
if (!model) return '—';
|
|
21
|
+
const parts = model.split('/');
|
|
22
|
+
return parts[parts.length - 1];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const NodeCard = memo(function NodeCard() {
|
|
26
|
+
const node = useGrooveStore((s) => s.networkNode);
|
|
27
|
+
const hardware = node.hardware || {};
|
|
28
|
+
const memPct = Number.isFinite(node.memoryPct) ? node.memoryPct : null;
|
|
29
|
+
const layersLabel = Array.isArray(node.layers) ? `${node.layers[0]}-${node.layers[1]}` : '—';
|
|
30
|
+
|
|
31
|
+
const metrics = [
|
|
32
|
+
{ label: 'DEVICE', value: hardware.device || 'auto' },
|
|
33
|
+
{ label: 'GPU', value: hardware.gpu || 'None' },
|
|
34
|
+
{ label: 'LAYERS', value: layersLabel },
|
|
35
|
+
{ label: 'MODEL', value: modelShort(node.model) },
|
|
36
|
+
{ label: 'SESSIONS', value: node.sessions ?? 0 },
|
|
37
|
+
{ label: 'VRAM', value: hardware.memory || '—' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex flex-col h-full">
|
|
42
|
+
<div className="px-3 pt-2.5 pb-1">
|
|
43
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Your Node</span>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="px-3 py-2 space-y-2">
|
|
47
|
+
<div>
|
|
48
|
+
<div className="text-2xs text-text-4 uppercase tracking-wider">ADDRESS</div>
|
|
49
|
+
<div className="text-xs font-mono text-accent truncate">{shortAddr(node.nodeId)}</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="flex items-center gap-1.5">
|
|
53
|
+
<StatusDot status={nodeStatusMap(node)} size="sm" />
|
|
54
|
+
<span className="text-2xs capitalize text-text-2">{node.status || 'disconnected'}</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="border-t border-border-subtle my-1" />
|
|
58
|
+
|
|
59
|
+
<div className="grid grid-cols-2 gap-x-3 gap-y-1.5">
|
|
60
|
+
{metrics.map((m) => (
|
|
61
|
+
<div key={m.label}>
|
|
62
|
+
<div className="text-2xs text-text-4 uppercase tracking-wider">{m.label}</div>
|
|
63
|
+
<div className="text-xs font-mono text-text-1 tabular-nums truncate">{m.value}</div>
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{memPct != null && (
|
|
69
|
+
<div className="mt-1">
|
|
70
|
+
<div className="flex items-center justify-between mb-0.5">
|
|
71
|
+
<span className="text-2xs text-text-4 uppercase tracking-wider">MEM</span>
|
|
72
|
+
<span className="text-2xs font-mono text-text-3 tabular-nums">{Math.round(memPct)}%</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="h-1 w-full rounded-full bg-surface-3 overflow-hidden">
|
|
75
|
+
<div
|
|
76
|
+
className={cn(
|
|
77
|
+
'h-full rounded-full transition-all',
|
|
78
|
+
memPct > 90 ? 'bg-danger' : memPct > 70 ? 'bg-warning' : 'bg-accent',
|
|
79
|
+
)}
|
|
80
|
+
style={{ width: `${Math.min(100, Math.max(0, memPct))}%` }}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Badge } from '../ui/badge';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { Wallet, Cpu, Activity, Clock, Zap } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
const REWARD_CARDS = [
|
|
8
|
+
{ icon: Cpu, label: 'Compute Hours', value: '—' },
|
|
9
|
+
{ icon: Activity, label: 'Sessions Served', value: '—' },
|
|
10
|
+
{ icon: Clock, label: 'Uptime Score', value: '—' },
|
|
11
|
+
{ icon: Zap, label: 'Network Score', value: '—' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const PAYOUT_COLS = ['Date', 'Amount', 'Status', 'TX'];
|
|
15
|
+
|
|
16
|
+
export const WalletView = memo(function WalletView() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex flex-col h-full overflow-auto">
|
|
19
|
+
{/* Hero */}
|
|
20
|
+
<div className="px-6 py-8 text-center border-b border-border-subtle bg-surface-0">
|
|
21
|
+
<div className="text-3xl font-mono font-semibold text-text-0 tabular-nums">0.00</div>
|
|
22
|
+
<div className="text-sm font-mono text-text-3 mt-1">GROOVE</div>
|
|
23
|
+
<Badge variant="purple" className="mt-3">Base L2</Badge>
|
|
24
|
+
<div className="mt-4">
|
|
25
|
+
<Button variant="primary" size="md" className="gap-2" disabled>
|
|
26
|
+
<Wallet size={14} />
|
|
27
|
+
Connect Wallet
|
|
28
|
+
</Button>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="text-2xs text-text-4 mt-2">Connect your Base wallet to claim rewards</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* Reward Metrics */}
|
|
34
|
+
<div className="px-4 py-4">
|
|
35
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">EARNING POTENTIAL</div>
|
|
36
|
+
<div className="grid grid-cols-4 gap-2">
|
|
37
|
+
{REWARD_CARDS.map((card) => (
|
|
38
|
+
<div key={card.label} className="rounded-md border border-border-subtle bg-surface-1 px-3 py-3 text-center">
|
|
39
|
+
<card.icon size={16} className="text-text-3 mx-auto mb-1.5" />
|
|
40
|
+
<div className="text-lg font-mono text-text-0 tabular-nums">{card.value}</div>
|
|
41
|
+
<div className="text-2xs text-text-4 mt-0.5">{card.label}</div>
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{/* Earnings Timeline */}
|
|
48
|
+
<div className="px-4 py-4 border-t border-border-subtle">
|
|
49
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">EARNINGS HISTORY</div>
|
|
50
|
+
<div className="h-40 rounded-md border border-border-subtle bg-surface-0 flex items-center justify-center">
|
|
51
|
+
<span className="text-xs font-mono text-text-4">Earnings data will appear here</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{/* Payouts Table */}
|
|
56
|
+
<div className="px-4 py-4 border-t border-border-subtle">
|
|
57
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-3">PAYOUT HISTORY</div>
|
|
58
|
+
<div className="grid grid-cols-4 px-3 py-1.5 text-2xs font-mono text-text-4 uppercase tracking-wider">
|
|
59
|
+
{PAYOUT_COLS.map((col) => (
|
|
60
|
+
<span key={col}>{col}</span>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
<div className="px-3 py-6 text-center text-xs font-mono text-text-4">No payouts yet</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Banner */}
|
|
67
|
+
<div className="px-4 pb-6">
|
|
68
|
+
<div className="rounded-md border border-purple/20 bg-purple/5 px-4 py-3 flex items-center gap-3">
|
|
69
|
+
<Zap size={16} className="text-purple flex-shrink-0" />
|
|
70
|
+
<span className="text-xs font-sans text-text-2">
|
|
71
|
+
Rewards go live when the GROOVE token launches on Base. Keep your node running to accumulate compute credits.
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
@@ -19,7 +19,7 @@ const PROVIDERS = [
|
|
|
19
19
|
id: 'claude-code',
|
|
20
20
|
name: 'Claude Code',
|
|
21
21
|
subtitle: 'by Anthropic',
|
|
22
|
-
models: ['Opus 4.
|
|
22
|
+
models: ['Opus 4.6', 'Sonnet 4.6', 'Haiku 4.5'],
|
|
23
23
|
authType: 'Subscription or API key',
|
|
24
24
|
authModes: ['subscription', 'apikey'],
|
|
25
25
|
recommended: true,
|
|
@@ -105,6 +105,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
105
105
|
networkUpdateProgress: { updating: false, step: null, message: null, percent: 0, error: null },
|
|
106
106
|
networkCompute: { totalRamMb: 0, totalVramMb: 0, totalCpuCores: 0, totalBandwidthMbps: 0, activeNodes: 0, totalNodes: 0, avgLoad: 0 },
|
|
107
107
|
networkSnapshots: [],
|
|
108
|
+
networkWallet: { connected: false, address: null, balance: '0.00', token: 'GROOVE', chain: 'base-l2' },
|
|
109
|
+
networkEarnings: { today: 0, thisWeek: 0, allTime: 0, history: [] },
|
|
108
110
|
|
|
109
111
|
// ── Marketplace Auth ───────────────────────────────────────
|
|
110
112
|
marketplaceUser: null, // { id, displayName, avatar, ... } or null
|
|
@@ -732,6 +734,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
732
734
|
nodeCount: wsActive.length,
|
|
733
735
|
avgLoad: wsActive.length > 0 ? wsActive.reduce((s, n) => s + (n.load || 0), 0) / wsActive.length : 0,
|
|
734
736
|
myLoad: wsOwn?.load ?? 0,
|
|
737
|
+
totalVramMb: wsNodes.reduce((s, n) => s + (n.vram_mb || 0), 0),
|
|
738
|
+
totalRamMb: wsNodes.reduce((s, n) => s + (n.ram_mb || 0), 0),
|
|
735
739
|
};
|
|
736
740
|
let wsSnapshots = [...get().networkSnapshots, wsSnap];
|
|
737
741
|
if (wsSnapshots.length > 100) wsSnapshots = wsSnapshots.slice(-100);
|
|
@@ -2179,6 +2183,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2179
2183
|
nodeCount: activeNodes.length,
|
|
2180
2184
|
avgLoad: activeNodes.length > 0 ? activeNodes.reduce((s, n) => s + (n.load || 0), 0) / activeNodes.length : 0,
|
|
2181
2185
|
myLoad: ownNode?.load ?? 0,
|
|
2186
|
+
totalVramMb: nodes.reduce((s, n) => s + (n.vram_mb || 0), 0),
|
|
2187
|
+
totalRamMb: nodes.reduce((s, n) => s + (n.ram_mb || 0), 0),
|
|
2182
2188
|
};
|
|
2183
2189
|
let snapshots = [...get().networkSnapshots, snap];
|
|
2184
2190
|
if (snapshots.length > 100) snapshots = snapshots.slice(-100);
|
|
@@ -2258,6 +2264,13 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2258
2264
|
}
|
|
2259
2265
|
},
|
|
2260
2266
|
|
|
2267
|
+
async fetchNetworkWallet() {
|
|
2268
|
+
return get().networkWallet;
|
|
2269
|
+
},
|
|
2270
|
+
async fetchNetworkEarnings() {
|
|
2271
|
+
return get().networkEarnings;
|
|
2272
|
+
},
|
|
2273
|
+
|
|
2261
2274
|
async renameFile(oldPath, newPath) {
|
|
2262
2275
|
try {
|
|
2263
2276
|
await api.post('/files/rename', { oldPath, newPath });
|
|
@@ -6,12 +6,17 @@ import { Badge } from '../components/ui/badge';
|
|
|
6
6
|
import { Button } from '../components/ui/button';
|
|
7
7
|
import { Dialog, DialogContent, DialogTrigger } from '../components/ui/dialog';
|
|
8
8
|
import { ScrollArea } from '../components/ui/scroll-area';
|
|
9
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/tabs';
|
|
9
10
|
import { cn } from '../lib/cn';
|
|
10
|
-
import {
|
|
11
|
+
import { IdentityBar } from '../components/network/identity-bar';
|
|
11
12
|
import { ComputeHeader } from '../components/network/compute-header';
|
|
12
13
|
import { ActivityChart } from '../components/network/activity-chart';
|
|
13
14
|
import { ActivityStream } from '../components/network/activity-stream';
|
|
14
15
|
import { NetworkHealth } from '../components/network/network-health';
|
|
16
|
+
import { NodeCard } from '../components/network/node-card';
|
|
17
|
+
import { EarningsCard } from '../components/network/earnings-card';
|
|
18
|
+
import { FleetTable } from '../components/network/fleet-table';
|
|
19
|
+
import { WalletView } from '../components/network/wallet-view';
|
|
15
20
|
import { HEX, hexAlpha } from '../lib/theme-hex';
|
|
16
21
|
import { Globe, Download, Check, AlertCircle, Loader2, Trash2, ArrowUpCircle, Zap } from 'lucide-react';
|
|
17
22
|
|
|
@@ -405,12 +410,44 @@ function IdleHero() {
|
|
|
405
410
|
);
|
|
406
411
|
}
|
|
407
412
|
|
|
413
|
+
function NetworkOverview() {
|
|
414
|
+
return (
|
|
415
|
+
<div className="flex flex-col h-full">
|
|
416
|
+
<ComputeHeader />
|
|
417
|
+
<div className="flex-1 min-h-0 grid" style={{ gridTemplateColumns: '2.5fr 1fr', gap: '1px', background: HEX.surface3 }}>
|
|
418
|
+
{/* Left column */}
|
|
419
|
+
<div className="flex flex-col min-w-0 min-h-0" style={{ gap: '1px' }}>
|
|
420
|
+
<div className="flex-1 min-h-0 overflow-hidden bg-surface-1">
|
|
421
|
+
<ActivityChart />
|
|
422
|
+
</div>
|
|
423
|
+
<div className="flex-[0.5] min-h-0 overflow-hidden bg-surface-1">
|
|
424
|
+
<ActivityStream />
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
{/* Right column */}
|
|
428
|
+
<div className="flex flex-col min-w-0 min-h-0" style={{ gap: '1px' }}>
|
|
429
|
+
<div className="min-h-0 overflow-hidden bg-surface-1">
|
|
430
|
+
<NodeCard />
|
|
431
|
+
</div>
|
|
432
|
+
<div className="flex-1 min-h-0 overflow-hidden bg-surface-1">
|
|
433
|
+
<NetworkHealth />
|
|
434
|
+
</div>
|
|
435
|
+
<div className="min-h-0 overflow-hidden bg-surface-1">
|
|
436
|
+
<EarningsCard />
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
408
444
|
export default function NetworkView() {
|
|
409
445
|
const fetchNetworkNodeStatus = useGrooveStore((s) => s.fetchNetworkNodeStatus);
|
|
410
446
|
const fetchNetworkStatus = useGrooveStore((s) => s.fetchNetworkStatus);
|
|
411
447
|
const checkNetworkUpdate = useGrooveStore((s) => s.checkNetworkUpdate);
|
|
412
448
|
const installed = useGrooveStore((s) => s.networkInstalled);
|
|
413
449
|
const nodeActive = useGrooveStore((s) => s.networkNode.active);
|
|
450
|
+
const [activeTab, setActiveTab] = useState('overview');
|
|
414
451
|
|
|
415
452
|
useEffect(() => {
|
|
416
453
|
fetchNetworkNodeStatus();
|
|
@@ -440,23 +477,27 @@ export default function NetworkView() {
|
|
|
440
477
|
return (
|
|
441
478
|
<div className="flex flex-col h-full">
|
|
442
479
|
<NetworkHeader />
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
<
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
</
|
|
455
|
-
|
|
456
|
-
<
|
|
457
|
-
<
|
|
458
|
-
</
|
|
459
|
-
|
|
480
|
+
<IdentityBar />
|
|
481
|
+
|
|
482
|
+
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 min-h-0 flex flex-col">
|
|
483
|
+
<TabsList className="bg-surface-0 border-b border-border-subtle px-4 flex-shrink-0">
|
|
484
|
+
<TabsTrigger value="overview" className="text-xs">Overview</TabsTrigger>
|
|
485
|
+
<TabsTrigger value="fleet" className="text-xs">Fleet</TabsTrigger>
|
|
486
|
+
<TabsTrigger value="wallet" className="text-xs">Wallet</TabsTrigger>
|
|
487
|
+
</TabsList>
|
|
488
|
+
|
|
489
|
+
<TabsContent value="overview" className="flex-1 min-h-0 overflow-hidden">
|
|
490
|
+
<NetworkOverview />
|
|
491
|
+
</TabsContent>
|
|
492
|
+
|
|
493
|
+
<TabsContent value="fleet" className="flex-1 min-h-0 overflow-hidden bg-surface-1">
|
|
494
|
+
<FleetTable />
|
|
495
|
+
</TabsContent>
|
|
496
|
+
|
|
497
|
+
<TabsContent value="wallet" className="flex-1 min-h-0 overflow-hidden">
|
|
498
|
+
<WalletView />
|
|
499
|
+
</TabsContent>
|
|
500
|
+
</Tabs>
|
|
460
501
|
</div>
|
|
461
502
|
);
|
|
462
503
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.66",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -4145,9 +4145,36 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4145
4145
|
|
|
4146
4146
|
// Network node lifecycle (gated)
|
|
4147
4147
|
|
|
4148
|
+
let _localHwCache = null;
|
|
4149
|
+
function getLocalHardware() {
|
|
4150
|
+
if (!_localHwCache) {
|
|
4151
|
+
const sys = OllamaProvider.getSystemHardware();
|
|
4152
|
+
const vramGb = sys.gpu?.vram || 0;
|
|
4153
|
+
const ramGb = sys.totalRamGb || 0;
|
|
4154
|
+
const vramMb = vramGb * 1024;
|
|
4155
|
+
const ramMb = ramGb * 1024;
|
|
4156
|
+
const fmtGb = (gb) => gb > 0 ? `${gb} GB` : null;
|
|
4157
|
+
_localHwCache = {
|
|
4158
|
+
device: sys.gpu?.type === 'nvidia' ? 'cuda' : sys.gpu?.type === 'apple-silicon' ? 'metal' : 'cpu',
|
|
4159
|
+
gpu: sys.gpu?.name || null,
|
|
4160
|
+
memory: fmtGb(vramGb) || fmtGb(ramGb),
|
|
4161
|
+
vram: fmtGb(vramGb),
|
|
4162
|
+
ram: fmtGb(ramGb),
|
|
4163
|
+
cpuCores: sys.cores || null,
|
|
4164
|
+
ram_mb: ramMb,
|
|
4165
|
+
vram_mb: vramMb,
|
|
4166
|
+
gpu_model: sys.gpu?.name || null,
|
|
4167
|
+
cpu_cores: sys.cores || 0,
|
|
4168
|
+
bandwidth_mbps: 0,
|
|
4169
|
+
max_context_length: 0,
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
return _localHwCache;
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4148
4175
|
function snapshotNode() {
|
|
4149
4176
|
const n = daemon.networkNode || {};
|
|
4150
|
-
const hw = n.hardware ||
|
|
4177
|
+
const hw = n.hardware || getLocalHardware();
|
|
4151
4178
|
return {
|
|
4152
4179
|
active: !!n.active,
|
|
4153
4180
|
status: n.status || 'stopped',
|
|
@@ -4155,7 +4182,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4155
4182
|
layers: n.layers || null,
|
|
4156
4183
|
model: n.model || null,
|
|
4157
4184
|
sessions: n.sessions || 0,
|
|
4158
|
-
hardware:
|
|
4185
|
+
hardware: hw,
|
|
4159
4186
|
installed: !!(daemon.config?.networkBeta?.installed),
|
|
4160
4187
|
ram_mb: Number(hw.ram_mb) || 0,
|
|
4161
4188
|
vram_mb: Number(hw.vram_mb) || 0,
|
|
@@ -4212,6 +4239,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4212
4239
|
cpuCores: caps.cpu_cores || null,
|
|
4213
4240
|
bandwidthMbps: caps.bandwidth_mbps || null,
|
|
4214
4241
|
maxContext: caps.max_context_length || null,
|
|
4242
|
+
ram_mb: Number(caps.ram_mb) || 0,
|
|
4243
|
+
vram_mb: Number(caps.vram_mb) || 0,
|
|
4244
|
+
gpu_model: caps.gpu_model || null,
|
|
4245
|
+
cpu_cores: Number(caps.cpu_cores) || 0,
|
|
4246
|
+
bandwidth_mbps: Number(caps.bandwidth_mbps) || 0,
|
|
4247
|
+
max_context_length: Number(caps.max_context_length) || 0,
|
|
4215
4248
|
};
|
|
4216
4249
|
}
|
|
4217
4250
|
|
|
@@ -4282,7 +4315,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4282
4315
|
layers: null,
|
|
4283
4316
|
model: null,
|
|
4284
4317
|
sessions: 0,
|
|
4285
|
-
hardware:
|
|
4318
|
+
hardware: getLocalHardware(),
|
|
4286
4319
|
startedAt: Date.now(),
|
|
4287
4320
|
events: [],
|
|
4288
4321
|
};
|
|
@@ -4454,14 +4487,15 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4454
4487
|
daemon.networkNode.layers = self.layers;
|
|
4455
4488
|
changed = true;
|
|
4456
4489
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4490
|
+
const prev = daemon.networkNode.hardware || getLocalHardware();
|
|
4491
|
+
const enriched = { ...prev };
|
|
4492
|
+
if (self.device) enriched.device = self.device;
|
|
4493
|
+
if (self.gpu_model) { enriched.gpu = self.gpu_model; enriched.gpu_model = self.gpu_model; }
|
|
4494
|
+
if (Number(self.ram_mb) > 0) { enriched.ram_mb = Number(self.ram_mb); }
|
|
4495
|
+
if (Number(self.vram_mb) > 0) { enriched.vram_mb = Number(self.vram_mb); enriched.memory = enriched.vram_mb >= 1024 ? `${(enriched.vram_mb / 1024).toFixed(1)} GB` : `${enriched.vram_mb} MB`; }
|
|
4496
|
+
if (Number(self.cpu_cores) > 0) { enriched.cpu_cores = Number(self.cpu_cores); enriched.cpuCores = Number(self.cpu_cores); }
|
|
4497
|
+
daemon.networkNode.hardware = enriched;
|
|
4498
|
+
changed = true;
|
|
4465
4499
|
}
|
|
4466
4500
|
const availModel = Array.isArray(data.models)
|
|
4467
4501
|
? data.models.find((m) => m && m.available !== false)
|
|
@@ -4474,25 +4508,39 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4474
4508
|
}
|
|
4475
4509
|
|
|
4476
4510
|
const capStr = (s, max = 200) => (typeof s === 'string' ? s.slice(0, max) : s);
|
|
4477
|
-
const
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4511
|
+
const selfId = daemon.networkNode?.nodeId;
|
|
4512
|
+
const localHw = getLocalHardware();
|
|
4513
|
+
const safeNodes = (Array.isArray(data.nodes) ? data.nodes : []).map((n) => {
|
|
4514
|
+
const nid = n.node_id || n.nodeId || '';
|
|
4515
|
+
const isSelf = selfId && nid && (nid === selfId || (nid.length >= 6 && selfId.startsWith(nid.replace(/\.{2,}$/, ''))));
|
|
4516
|
+
const base = {
|
|
4517
|
+
node_id: capStr(nid),
|
|
4518
|
+
device: capStr(n.device),
|
|
4519
|
+
layers: Array.isArray(n.layers) ? n.layers.slice(0, 2) : n.layers,
|
|
4520
|
+
status: capStr(n.status, 50),
|
|
4521
|
+
active_sessions: n.active_sessions ?? 0,
|
|
4522
|
+
ram_mb: Number(n.ram_mb) || 0,
|
|
4523
|
+
vram_mb: Number(n.vram_mb) || 0,
|
|
4524
|
+
gpu_model: capStr(n.gpu_model || '', 200),
|
|
4525
|
+
cpu_cores: Number(n.cpu_cores) || 0,
|
|
4526
|
+
bandwidth_mbps: Number(n.bandwidth_mbps) || 0.0,
|
|
4527
|
+
max_context_length: Number(n.max_context_length) || 0,
|
|
4528
|
+
load: Number(n.load) || 0.0,
|
|
4529
|
+
gpu_utilization_pct: Number(n.gpu_utilization_pct) || 0,
|
|
4530
|
+
vram_used_mb: Number(n.vram_used_mb) || 0,
|
|
4531
|
+
ram_used_mb: Number(n.ram_used_mb) || 0,
|
|
4532
|
+
ram_pct: Number(n.ram_pct) || 0,
|
|
4533
|
+
uptime_seconds: Number(n.uptime_seconds) || 0,
|
|
4534
|
+
};
|
|
4535
|
+
if (isSelf) {
|
|
4536
|
+
if (!base.device) base.device = localHw.device;
|
|
4537
|
+
if (!base.gpu_model) base.gpu_model = localHw.gpu_model || '';
|
|
4538
|
+
if (!base.ram_mb) base.ram_mb = localHw.ram_mb;
|
|
4539
|
+
if (!base.vram_mb) base.vram_mb = localHw.vram_mb;
|
|
4540
|
+
if (!base.cpu_cores) base.cpu_cores = localHw.cpu_cores;
|
|
4541
|
+
}
|
|
4542
|
+
return base;
|
|
4543
|
+
});
|
|
4496
4544
|
|
|
4497
4545
|
return res.json({
|
|
4498
4546
|
nodes: safeNodes,
|
|
@@ -4515,7 +4563,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4515
4563
|
const localCpuCores = sysHw.cores || 0;
|
|
4516
4564
|
const selfNode = node.active && node.nodeId ? [{
|
|
4517
4565
|
node_id: node.nodeId,
|
|
4518
|
-
device: hw.device || '
|
|
4566
|
+
device: hw.device || (sysHw.gpu?.type === 'nvidia' ? 'cuda' : sysHw.gpu?.type === 'apple-silicon' ? 'metal' : 'cpu'),
|
|
4519
4567
|
layers: node.layers || [0, 0],
|
|
4520
4568
|
status: node.status === 'connected' ? 'active' : node.status,
|
|
4521
4569
|
active_sessions: node.sessions || 0,
|
|
@@ -5178,6 +5226,30 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
5178
5226
|
})();
|
|
5179
5227
|
});
|
|
5180
5228
|
|
|
5229
|
+
// --- Wallet & earnings stubs (Base L2 — wired to real data post-mainnet) ---
|
|
5230
|
+
|
|
5231
|
+
app.get('/api/network/wallet', networkGate, (req, res) => {
|
|
5232
|
+
res.json({ connected: false, address: null, balance: '0.00', token: 'GROOVE', chain: 'base-l2' });
|
|
5233
|
+
});
|
|
5234
|
+
|
|
5235
|
+
app.get('/api/network/earnings', networkGate, (req, res) => {
|
|
5236
|
+
res.json({ today: 0, thisWeek: 0, allTime: 0, history: [], currency: 'GROOVE' });
|
|
5237
|
+
});
|
|
5238
|
+
|
|
5239
|
+
app.post('/api/network/wallet/connect', networkGate, (req, res) => {
|
|
5240
|
+
res.status(501).json({ error: 'Wallet connection not yet available. Coming with mainnet launch.' });
|
|
5241
|
+
});
|
|
5242
|
+
|
|
5243
|
+
app.get('/api/network/node/identity', networkGate, (req, res) => {
|
|
5244
|
+
const node = daemon.networkNode;
|
|
5245
|
+
res.json({
|
|
5246
|
+
nodeId: node?.nodeId || null,
|
|
5247
|
+
address: node?.nodeId || null,
|
|
5248
|
+
startedAt: node?.startedAt || null,
|
|
5249
|
+
uptime: node?.startedAt ? Math.floor((Date.now() - node.startedAt) / 1000) : 0,
|
|
5250
|
+
});
|
|
5251
|
+
});
|
|
5252
|
+
|
|
5181
5253
|
// Startup hook — called from index.js once the server is up. Non-blocking;
|
|
5182
5254
|
// updates daemon.networkUpdateAvailable and broadcasts so the GUI can badge.
|
|
5183
5255
|
daemon.checkNetworkUpdate = async function checkNetworkUpdate() {
|
|
@@ -40,7 +40,6 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
40
40
|
static managesOwnContext = true; // Claude Code compacts context internally (~25-37% → 2-8%)
|
|
41
41
|
static models = [
|
|
42
42
|
{ id: 'claude-opus-4-6', name: 'Claude Opus 4.6', tier: 'heavy', contextWindow: 1_000_000 },
|
|
43
|
-
{ id: 'claude-opus-4-7', name: 'Claude Opus 4.7', tier: 'heavy', contextWindow: 1_000_000 },
|
|
44
43
|
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', tier: 'medium', contextWindow: 200_000 },
|
|
45
44
|
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', tier: 'light', contextWindow: 200_000 },
|
|
46
45
|
];
|