@decido/kernel-bridge 1.0.0 → 4.0.2
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 +31 -0
- package/dist/index.js +2593 -0
- package/dist/index.mjs +2518 -0
- package/package.json +13 -7
- package/.turbo/turbo-build.log +0 -13
- package/.turbo/turbo-lint.log +0 -30
- package/src/ai/components/PeerNetworkPanel.tsx +0 -219
- package/src/ai/components/TokenWalletPanel.tsx +0 -172
- package/src/ai/hooks/usePeerMesh.ts +0 -79
- package/src/ai/hooks/useTokenWallet.ts +0 -35
- package/src/ai/index.ts +0 -96
- package/src/ai/services/EmbeddingService.ts +0 -119
- package/src/ai/services/InferenceRouter.ts +0 -347
- package/src/ai/services/LocalAgentResponder.ts +0 -199
- package/src/ai/services/MLXBridge.ts +0 -278
- package/src/ai/services/OllamaService.ts +0 -326
- package/src/ai/services/PeerMesh.ts +0 -373
- package/src/ai/services/TokenWallet.ts +0 -237
- package/src/ai/services/providers/AnthropicProvider.ts +0 -229
- package/src/ai/services/providers/GeminiProvider.ts +0 -121
- package/src/ai/services/providers/LLMProvider.ts +0 -72
- package/src/ai/services/providers/OllamaProvider.ts +0 -84
- package/src/ai/services/providers/OpenAIProvider.ts +0 -178
- package/src/crypto.ts +0 -54
- package/src/index.ts +0 -4
- package/src/kernel.ts +0 -376
- package/src/rehydration.ts +0 -52
- package/tsconfig.json +0 -18
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decido/kernel-bridge",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Tauri v2 IPC Bridge for Decido OS",
|
|
5
|
-
"main": "./
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
11
13
|
}
|
|
12
14
|
},
|
|
13
15
|
"dependencies": {
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
"openai": "^6.24.0",
|
|
20
22
|
"socket.io-client": "^4.8.3",
|
|
21
23
|
"uuid": "^13.0.0",
|
|
22
|
-
"@decido/sdk": "
|
|
24
|
+
"@decido/sdk": "4.0.2"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@types/react": "^19.0.0",
|
|
@@ -30,8 +32,12 @@
|
|
|
30
32
|
"access": "public"
|
|
31
33
|
},
|
|
32
34
|
"license": "UNLICENSED",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"module": "./dist/index.mjs",
|
|
33
39
|
"scripts": {
|
|
34
40
|
"lint": "eslint \"src/**/*.ts*\"",
|
|
35
|
-
"build": "tsup src/index.ts --format esm
|
|
41
|
+
"build": "tsup src/index.ts --format esm,cjs"
|
|
36
42
|
}
|
|
37
43
|
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Debugger listening on ws://127.0.0.1:58975/0314506c-e1de-4a58-be5e-b4ac774586f4
|
|
3
|
-
For help, see: https://nodejs.org/en/docs/inspector
|
|
4
|
-
Debugger attached.
|
|
5
|
-
|
|
6
|
-
> @decido/kernel-bridge@1.0.0 build /Users/julioramirez/dev/active/OnBoardingDecido/packages/kernel-bridge
|
|
7
|
-
> tsc
|
|
8
|
-
|
|
9
|
-
Debugger listening on ws://127.0.0.1:58979/db35dc6b-ec5f-499c-b2bb-a281886a7590
|
|
10
|
-
For help, see: https://nodejs.org/en/docs/inspector
|
|
11
|
-
Debugger attached.
|
|
12
|
-
Waiting for the debugger to disconnect...
|
|
13
|
-
Waiting for the debugger to disconnect...
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Debugger listening on ws://127.0.0.1:60130/80df8f38-b3d4-4cfb-a50d-56cde2cc32c8
|
|
3
|
-
For help, see: https://nodejs.org/en/docs/inspector
|
|
4
|
-
Debugger attached.
|
|
5
|
-
|
|
6
|
-
> @macia/kernel-bridge@1.0.0 lint /Users/julioramirez/dev/active/Decido/packages/core-decido/kernel-bridge
|
|
7
|
-
> eslint "src/**/*.ts*"
|
|
8
|
-
|
|
9
|
-
Debugger listening on ws://127.0.0.1:60166/f14ee9d9-5a8a-4ad3-a957-a0f39414225c
|
|
10
|
-
For help, see: https://nodejs.org/en/docs/inspector
|
|
11
|
-
Debugger attached.
|
|
12
|
-
|
|
13
|
-
Oops! Something went wrong! :(
|
|
14
|
-
|
|
15
|
-
ESLint: 9.39.3
|
|
16
|
-
|
|
17
|
-
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
|
|
18
|
-
|
|
19
|
-
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
|
|
20
|
-
If you are using a .eslintrc.* file, please follow the migration guide
|
|
21
|
-
to update your configuration file to the new format:
|
|
22
|
-
|
|
23
|
-
https://eslint.org/docs/latest/use/configure/migration-guide
|
|
24
|
-
|
|
25
|
-
If you still have problems after following the migration guide, please stop by
|
|
26
|
-
https://eslint.org/chat/help to chat with the team.
|
|
27
|
-
|
|
28
|
-
Waiting for the debugger to disconnect...
|
|
29
|
-
[41m[30m ELIFECYCLE [39m[49m [31mCommand failed with exit code 2.[39m
|
|
30
|
-
Waiting for the debugger to disconnect...
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PeerNetworkPanel — P2P mesh control panel
|
|
3
|
-
*
|
|
4
|
-
* Shows connected peers, hosting controls, and shared results.
|
|
5
|
-
* Uses Decido Aura design tokens for full theme compatibility.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useState, useCallback } from 'react';
|
|
9
|
-
import { usePeerMesh } from '../hooks/usePeerMesh';
|
|
10
|
-
import type { PeerInfo } from '../services/PeerMesh';
|
|
11
|
-
|
|
12
|
-
// ─── Helpers ────────────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
function timeAgo(ts: number): string {
|
|
15
|
-
const diff = Date.now() - ts;
|
|
16
|
-
if (diff < 60_000) return 'just now';
|
|
17
|
-
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
18
|
-
return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ─── Component ──────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
export function PeerNetworkPanel() {
|
|
24
|
-
const {
|
|
25
|
-
peers, isHosting, isConnected, sharedResults, peerCount,
|
|
26
|
-
startHost, stopHost, connectToPeer, disconnect,
|
|
27
|
-
} = usePeerMesh();
|
|
28
|
-
|
|
29
|
-
const [peerAddress, setPeerAddress] = useState('');
|
|
30
|
-
const [hostPort, setHostPort] = useState('9876');
|
|
31
|
-
|
|
32
|
-
const handleConnect = useCallback(() => {
|
|
33
|
-
if (!peerAddress.trim()) return;
|
|
34
|
-
connectToPeer(peerAddress.trim());
|
|
35
|
-
setPeerAddress('');
|
|
36
|
-
}, [peerAddress, connectToPeer]);
|
|
37
|
-
|
|
38
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
39
|
-
if (e.key === 'Enter') handleConnect();
|
|
40
|
-
}, [handleConnect]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle">
|
|
44
|
-
{/* Header */}
|
|
45
|
-
<div className="flex items-center justify-between">
|
|
46
|
-
<div className="flex items-center gap-2">
|
|
47
|
-
<span className="text-lg">🌐</span>
|
|
48
|
-
<h3 className="text-sm font-semibold text-text-primary">Peer Network</h3>
|
|
49
|
-
{peerCount > 0 && (
|
|
50
|
-
<span className="px-1.5 py-0.5 text-[10px] font-bold rounded-full bg-emerald-500/20 text-accent-green">
|
|
51
|
-
{peerCount}
|
|
52
|
-
</span>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
<StatusBadge isHosting={isHosting} isConnected={isConnected} />
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
{/* Connection Controls */}
|
|
59
|
-
<div className="flex flex-col gap-2 p-3 rounded-xl bg-surface-tertiary/50">
|
|
60
|
-
{/* Host Toggle */}
|
|
61
|
-
<div className="flex items-center justify-between">
|
|
62
|
-
<div className="flex flex-col">
|
|
63
|
-
<span className="text-[11px] font-medium text-text-secondary">Host a Group</span>
|
|
64
|
-
<span className="text-[9px] text-text-muted">Others can connect to you</span>
|
|
65
|
-
</div>
|
|
66
|
-
<div className="flex items-center gap-2">
|
|
67
|
-
<input
|
|
68
|
-
className="w-16 px-2 py-1 text-[11px] font-mono text-text-primary bg-surface-primary
|
|
69
|
-
border border-border-subtle rounded-lg text-center
|
|
70
|
-
focus:border-border-strong focus:outline-none transition-colors"
|
|
71
|
-
value={hostPort}
|
|
72
|
-
onChange={(e) => setHostPort(e.target.value)}
|
|
73
|
-
placeholder="Port"
|
|
74
|
-
disabled={isHosting}
|
|
75
|
-
/>
|
|
76
|
-
<button
|
|
77
|
-
onClick={() => isHosting ? stopHost() : startHost(Number(hostPort))}
|
|
78
|
-
className={`px-3 py-1 text-[11px] font-medium rounded-lg transition-all
|
|
79
|
-
${isHosting
|
|
80
|
-
? 'bg-red-500/15 text-accent-red hover:bg-red-500/25'
|
|
81
|
-
: 'bg-emerald-500/15 text-accent-green hover:bg-emerald-500/25'
|
|
82
|
-
}`}
|
|
83
|
-
>
|
|
84
|
-
{isHosting ? '⏹ Stop' : '▶ Start'}
|
|
85
|
-
</button>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
{/* Divider */}
|
|
90
|
-
<div className="h-px bg-border-subtle" />
|
|
91
|
-
|
|
92
|
-
{/* Connect to Peer */}
|
|
93
|
-
<div className="flex flex-col gap-1.5">
|
|
94
|
-
<span className="text-[11px] font-medium text-text-secondary">Join a Group</span>
|
|
95
|
-
<div className="flex gap-2">
|
|
96
|
-
<input
|
|
97
|
-
className="flex-1 px-3 py-1.5 text-[11px] font-mono text-text-primary bg-surface-primary
|
|
98
|
-
border border-border-subtle rounded-lg placeholder:text-text-muted
|
|
99
|
-
focus:border-border-strong focus:outline-none transition-colors"
|
|
100
|
-
value={peerAddress}
|
|
101
|
-
onChange={(e) => setPeerAddress(e.target.value)}
|
|
102
|
-
onKeyDown={handleKeyDown}
|
|
103
|
-
placeholder="192.168.1.10:9876"
|
|
104
|
-
disabled={isConnected}
|
|
105
|
-
/>
|
|
106
|
-
{isConnected ? (
|
|
107
|
-
<button
|
|
108
|
-
onClick={disconnect}
|
|
109
|
-
className="px-3 py-1.5 text-[11px] font-medium rounded-lg bg-red-500/15 text-accent-red
|
|
110
|
-
hover:bg-red-500/25 transition-all"
|
|
111
|
-
>
|
|
112
|
-
Disconnect
|
|
113
|
-
</button>
|
|
114
|
-
) : (
|
|
115
|
-
<button
|
|
116
|
-
onClick={handleConnect}
|
|
117
|
-
className="px-3 py-1.5 text-[11px] font-medium rounded-lg bg-cyan-500/15 text-accent-cyan
|
|
118
|
-
hover:bg-cyan-500/25 transition-all"
|
|
119
|
-
>
|
|
120
|
-
Connect
|
|
121
|
-
</button>
|
|
122
|
-
)}
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
{/* Connected Peers */}
|
|
128
|
-
{peers.length > 0 && (
|
|
129
|
-
<div className="flex flex-col gap-1.5">
|
|
130
|
-
<span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
|
|
131
|
-
Connected Peers
|
|
132
|
-
</span>
|
|
133
|
-
{peers.map((peer) => (
|
|
134
|
-
<PeerRow key={peer.id} peer={peer} />
|
|
135
|
-
))}
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
|
|
139
|
-
{/* Empty State */}
|
|
140
|
-
{!isHosting && !isConnected && peers.length === 0 && (
|
|
141
|
-
<div className="flex flex-col items-center justify-center py-5 text-text-muted gap-1.5">
|
|
142
|
-
<span className="text-2xl opacity-40">🔗</span>
|
|
143
|
-
<span className="text-xs text-center">
|
|
144
|
-
Host a group or connect to a peer<br />
|
|
145
|
-
to share AI results
|
|
146
|
-
</span>
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
|
|
150
|
-
{/* Shared Results */}
|
|
151
|
-
{sharedResults.length > 0 && (
|
|
152
|
-
<div className="flex flex-col gap-1.5 mt-1">
|
|
153
|
-
<span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
|
|
154
|
-
Shared Results ({sharedResults.length})
|
|
155
|
-
</span>
|
|
156
|
-
<div className="flex flex-col gap-1 max-h-40 overflow-y-auto">
|
|
157
|
-
{sharedResults.slice(-8).reverse().map((r, i) => (
|
|
158
|
-
<div
|
|
159
|
-
key={`${r.timestamp}-${i}`}
|
|
160
|
-
className="px-3 py-2 rounded-xl bg-surface-glass text-[10px]"
|
|
161
|
-
>
|
|
162
|
-
<div className="flex items-center justify-between mb-1">
|
|
163
|
-
<span className="font-medium text-accent-purple">
|
|
164
|
-
{r.peerId.slice(0, 8)}
|
|
165
|
-
</span>
|
|
166
|
-
<span className="text-text-muted font-mono">{r.model}</span>
|
|
167
|
-
</div>
|
|
168
|
-
<p className="text-text-secondary line-clamp-2">{r.response.slice(0, 120)}...</p>
|
|
169
|
-
</div>
|
|
170
|
-
))}
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
)}
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ─── Subcomponents ──────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
function StatusBadge({ isHosting, isConnected }: { isHosting: boolean; isConnected: boolean }) {
|
|
181
|
-
if (isHosting) {
|
|
182
|
-
return (
|
|
183
|
-
<span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-emerald-500/15 text-[10px] font-medium text-accent-green">
|
|
184
|
-
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
|
185
|
-
Hosting
|
|
186
|
-
</span>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
if (isConnected) {
|
|
190
|
-
return (
|
|
191
|
-
<span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-cyan-500/15 text-[10px] font-medium text-accent-cyan">
|
|
192
|
-
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
|
|
193
|
-
Connected
|
|
194
|
-
</span>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
return (
|
|
198
|
-
<span className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-surface-glass text-[10px] text-text-muted">
|
|
199
|
-
<span className="w-1.5 h-1.5 rounded-full bg-surface-tertiary" />
|
|
200
|
-
Offline
|
|
201
|
-
</span>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function PeerRow({ peer }: { peer: PeerInfo }) {
|
|
206
|
-
return (
|
|
207
|
-
<div className="flex items-center justify-between px-3 py-2 rounded-xl bg-surface-glass transition-colors hover:bg-surface-tertiary/50">
|
|
208
|
-
<div className="flex items-center gap-2">
|
|
209
|
-
<span className="w-2 h-2 rounded-full bg-emerald-400" />
|
|
210
|
-
<span className="text-xs font-medium text-text-primary">{peer.name}</span>
|
|
211
|
-
</div>
|
|
212
|
-
<div className="flex items-center gap-2 text-[10px] text-text-muted">
|
|
213
|
-
<span className="font-mono">{peer.address}</span>
|
|
214
|
-
<span>·</span>
|
|
215
|
-
<span>{timeAgo(peer.lastSeen)}</span>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TokenWalletPanel — Visual spending dashboard for AI providers
|
|
3
|
-
*
|
|
4
|
-
* Shows total tokens, estimated cost, and per-provider breakdown.
|
|
5
|
-
* Uses Decido Aura design tokens for full theme compatibility.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useState } from 'react';
|
|
9
|
-
import { useTokenWallet } from '../hooks/useTokenWallet';
|
|
10
|
-
import type { TokenUsageEntry } from '../services/TokenWallet';
|
|
11
|
-
|
|
12
|
-
// ─── Provider colors (accent palette) ───────────────────────
|
|
13
|
-
|
|
14
|
-
const PROVIDER_COLORS: Record<string, string> = {
|
|
15
|
-
ollama: 'text-accent-green',
|
|
16
|
-
gemini: 'text-accent-blue',
|
|
17
|
-
anthropic: 'text-accent-purple',
|
|
18
|
-
openai: 'text-accent-cyan',
|
|
19
|
-
mlx: 'text-accent-amber',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const PROVIDER_BG: Record<string, string> = {
|
|
23
|
-
ollama: 'bg-emerald-500/10',
|
|
24
|
-
gemini: 'bg-blue-500/10',
|
|
25
|
-
anthropic: 'bg-purple-500/10',
|
|
26
|
-
openai: 'bg-cyan-500/10',
|
|
27
|
-
mlx: 'bg-amber-500/10',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const PROVIDER_ICONS: Record<string, string> = {
|
|
31
|
-
ollama: '🦙',
|
|
32
|
-
gemini: '✨',
|
|
33
|
-
anthropic: '🧠',
|
|
34
|
-
openai: '⚡',
|
|
35
|
-
mlx: '🍎',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// ─── Helpers ────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
function formatCost(usd: number): string {
|
|
41
|
-
if (usd === 0) return 'FREE';
|
|
42
|
-
if (usd < 0.01) return `$${usd.toFixed(6)}`;
|
|
43
|
-
if (usd < 1) return `$${usd.toFixed(4)}`;
|
|
44
|
-
return `$${usd.toFixed(2)}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function formatTokens(n: number): string {
|
|
48
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
49
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
50
|
-
return String(n);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function timeAgo(ts: number): string {
|
|
54
|
-
const diff = Date.now() - ts;
|
|
55
|
-
if (diff < 60_000) return 'just now';
|
|
56
|
-
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
57
|
-
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
58
|
-
return `${Math.floor(diff / 86_400_000)}d ago`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ─── Component ──────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
export function TokenWalletPanel() {
|
|
64
|
-
const { summary, totalTokens, totalCost, totalCalls, byProvider, clearHistory, getRecentHistory } = useTokenWallet();
|
|
65
|
-
const [showHistory, setShowHistory] = useState(false);
|
|
66
|
-
|
|
67
|
-
const providers = Object.entries(byProvider).sort((a, b) => b[1].cost - a[1].cost);
|
|
68
|
-
const history = showHistory ? getRecentHistory(15) : [];
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div className="flex flex-col gap-3 p-4 bg-surface-secondary rounded-2xl border border-border-subtle">
|
|
72
|
-
{/* Header */}
|
|
73
|
-
<div className="flex items-center justify-between">
|
|
74
|
-
<div className="flex items-center gap-2">
|
|
75
|
-
<span className="text-lg">💰</span>
|
|
76
|
-
<h3 className="text-sm font-semibold text-text-primary">Token Wallet</h3>
|
|
77
|
-
</div>
|
|
78
|
-
<button
|
|
79
|
-
onClick={clearHistory}
|
|
80
|
-
className="text-[10px] px-2 py-0.5 rounded-md bg-surface-glass text-text-muted
|
|
81
|
-
hover:bg-surface-tertiary hover:text-text-secondary transition-colors"
|
|
82
|
-
>
|
|
83
|
-
Clear
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
{/* Total Stats */}
|
|
88
|
-
<div className="grid grid-cols-3 gap-2">
|
|
89
|
-
<StatCard label="Tokens" value={formatTokens(totalTokens)} accent="text-accent-cyan" />
|
|
90
|
-
<StatCard label="Cost" value={formatCost(totalCost)} accent={totalCost === 0 ? 'text-accent-green' : 'text-accent-amber'} />
|
|
91
|
-
<StatCard label="Calls" value={String(totalCalls)} accent="text-accent-blue" />
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{/* Per-Provider Breakdown */}
|
|
95
|
-
{providers.length > 0 && (
|
|
96
|
-
<div className="flex flex-col gap-1.5 mt-1">
|
|
97
|
-
<span className="text-[10px] font-medium text-text-muted uppercase tracking-wider">By Provider</span>
|
|
98
|
-
{providers.map(([id, data]) => (
|
|
99
|
-
<div
|
|
100
|
-
key={id}
|
|
101
|
-
className={`flex items-center justify-between px-3 py-2 rounded-xl ${PROVIDER_BG[id] || 'bg-surface-glass'} transition-colors`}
|
|
102
|
-
>
|
|
103
|
-
<div className="flex items-center gap-2">
|
|
104
|
-
<span className="text-sm">{PROVIDER_ICONS[id] || '🤖'}</span>
|
|
105
|
-
<span className={`text-xs font-medium ${PROVIDER_COLORS[id] || 'text-text-primary'}`}>
|
|
106
|
-
{id.charAt(0).toUpperCase() + id.slice(1)}
|
|
107
|
-
</span>
|
|
108
|
-
</div>
|
|
109
|
-
<div className="flex items-center gap-3 text-[11px]">
|
|
110
|
-
<span className="text-text-muted">{data.calls} calls</span>
|
|
111
|
-
<span className="text-text-secondary font-mono">{formatTokens(data.tokens)}</span>
|
|
112
|
-
<span className="font-semibold text-text-primary font-mono">{formatCost(data.cost)}</span>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
))}
|
|
116
|
-
</div>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
|
-
{/* Empty State */}
|
|
120
|
-
{providers.length === 0 && (
|
|
121
|
-
<div className="flex flex-col items-center justify-center py-6 text-text-muted gap-1">
|
|
122
|
-
<span className="text-2xl opacity-50">📊</span>
|
|
123
|
-
<span className="text-xs">No AI calls recorded yet</span>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
{/* History Toggle */}
|
|
128
|
-
<button
|
|
129
|
-
onClick={() => setShowHistory(!showHistory)}
|
|
130
|
-
className="text-[10px] text-text-muted hover:text-text-secondary transition-colors self-center"
|
|
131
|
-
>
|
|
132
|
-
{showHistory ? '▲ Hide recent' : '▼ Show recent calls'}
|
|
133
|
-
</button>
|
|
134
|
-
|
|
135
|
-
{/* History List */}
|
|
136
|
-
{showHistory && history.length > 0 && (
|
|
137
|
-
<div className="flex flex-col gap-1 max-h-48 overflow-y-auto">
|
|
138
|
-
{history.reverse().map((entry, i) => (
|
|
139
|
-
<HistoryRow key={`${entry.timestamp}-${i}`} entry={entry} />
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ─── Subcomponents ──────────────────────────────────────────
|
|
148
|
-
|
|
149
|
-
function StatCard({ label, value, accent }: { label: string; value: string; accent: string }) {
|
|
150
|
-
return (
|
|
151
|
-
<div className="flex flex-col items-center p-2 rounded-xl bg-surface-tertiary/50">
|
|
152
|
-
<span className="text-[9px] text-text-muted uppercase tracking-wider">{label}</span>
|
|
153
|
-
<span className={`text-base font-bold font-mono ${accent}`}>{value}</span>
|
|
154
|
-
</div>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function HistoryRow({ entry }: { entry: TokenUsageEntry }) {
|
|
159
|
-
return (
|
|
160
|
-
<div className="flex items-center justify-between px-2 py-1.5 rounded-lg bg-surface-glass text-[10px]">
|
|
161
|
-
<div className="flex items-center gap-1.5">
|
|
162
|
-
<span>{PROVIDER_ICONS[entry.provider] || '🤖'}</span>
|
|
163
|
-
<span className="text-text-secondary font-mono">{entry.model.split('/').pop()?.slice(0, 18)}</span>
|
|
164
|
-
</div>
|
|
165
|
-
<div className="flex items-center gap-2">
|
|
166
|
-
<span className="text-text-muted">{formatTokens(entry.totalTokens)}</span>
|
|
167
|
-
<span className="text-text-primary font-mono">{formatCost(entry.estimatedCostUsd)}</span>
|
|
168
|
-
<span className="text-text-muted">{timeAgo(entry.timestamp)}</span>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePeerMesh — React hook for P2P group mesh
|
|
3
|
-
*
|
|
4
|
-
* Exposes peer list, hosting controls, and context sharing.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
8
|
-
import {
|
|
9
|
-
peerMesh,
|
|
10
|
-
type PeerInfo,
|
|
11
|
-
type SharedResult,
|
|
12
|
-
} from '../services/PeerMesh';
|
|
13
|
-
|
|
14
|
-
export function usePeerMesh() {
|
|
15
|
-
const [peers, setPeers] = useState<PeerInfo[]>([]);
|
|
16
|
-
const [isHosting, setIsHosting] = useState(false);
|
|
17
|
-
const [isConnected, setIsConnected] = useState(false);
|
|
18
|
-
const [sharedResults, setSharedResults] = useState<SharedResult[]>([]);
|
|
19
|
-
const resultsRef = useRef(sharedResults);
|
|
20
|
-
resultsRef.current = sharedResults;
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const unsubPeers = peerMesh.onPeersChanged((newPeers) => {
|
|
24
|
-
setPeers([...newPeers]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const unsubHosting = peerMesh.onHostingChanged((hosting) => {
|
|
28
|
-
setIsHosting(hosting);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const unsubConnected = peerMesh.onConnectionChanged((connected) => {
|
|
32
|
-
setIsConnected(connected);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const unsubResult = peerMesh.onSharedResult((result) => {
|
|
36
|
-
setSharedResults(prev => [...prev.slice(-49), result]);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
return () => {
|
|
40
|
-
unsubPeers();
|
|
41
|
-
unsubHosting();
|
|
42
|
-
unsubConnected();
|
|
43
|
-
unsubResult();
|
|
44
|
-
};
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
const startHost = useCallback((port?: number) => {
|
|
48
|
-
peerMesh.startHost(port);
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
const stopHost = useCallback(() => {
|
|
52
|
-
peerMesh.stopHost();
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
const connectToPeer = useCallback((address: string) => {
|
|
56
|
-
peerMesh.connectToPeer(address);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
const disconnect = useCallback(() => {
|
|
60
|
-
peerMesh.disconnect();
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const shareResult = useCallback((result: Omit<SharedResult, 'peerId' | 'timestamp'>) => {
|
|
64
|
-
peerMesh.shareResult(result);
|
|
65
|
-
}, []);
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
peers,
|
|
69
|
-
isHosting,
|
|
70
|
-
isConnected,
|
|
71
|
-
sharedResults,
|
|
72
|
-
peerCount: peers.length,
|
|
73
|
-
startHost,
|
|
74
|
-
stopHost,
|
|
75
|
-
connectToPeer,
|
|
76
|
-
disconnect,
|
|
77
|
-
shareResult,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useTokenWallet — React hook for the Token Wallet
|
|
3
|
-
*
|
|
4
|
-
* Subscribes to wallet changes and exposes a reactive summary.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
8
|
-
import { tokenWallet, type WalletSummary, type TokenUsageEntry } from '../services/TokenWallet';
|
|
9
|
-
|
|
10
|
-
export function useTokenWallet() {
|
|
11
|
-
const [summary, setSummary] = useState<WalletSummary>(tokenWallet.getSummary());
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
const unsubscribe = tokenWallet.subscribe(setSummary);
|
|
15
|
-
return unsubscribe;
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
const clearHistory = useCallback(() => {
|
|
19
|
-
tokenWallet.clearHistory();
|
|
20
|
-
}, []);
|
|
21
|
-
|
|
22
|
-
const getRecentHistory = useCallback((count = 20): TokenUsageEntry[] => {
|
|
23
|
-
return tokenWallet.getRecentHistory(count);
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
summary,
|
|
28
|
-
clearHistory,
|
|
29
|
-
getRecentHistory,
|
|
30
|
-
totalTokens: summary.totalTokens,
|
|
31
|
-
totalCost: summary.totalCostUsd,
|
|
32
|
-
totalCalls: summary.totalCalls,
|
|
33
|
-
byProvider: summary.byProvider,
|
|
34
|
-
};
|
|
35
|
-
}
|