@saccolabs/tars 1.8.2 → 1.9.0
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/context/skills/gws-setup/SKILL.md +71 -0
- package/dist/auth/workspace-auth-service.d.ts +10 -0
- package/dist/auth/workspace-auth-service.js +78 -0
- package/dist/auth/workspace-auth-service.js.map +1 -0
- package/dist/cli/commands/setup.js +158 -5
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/stop.js +3 -0
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/supervisor/dashboard-service.d.ts +12 -0
- package/dist/supervisor/dashboard-service.js +109 -0
- package/dist/supervisor/dashboard-service.js.map +1 -0
- package/dist/supervisor/gemini-engine.js +26 -4
- package/dist/supervisor/gemini-engine.js.map +1 -1
- package/dist/supervisor/main.js +4 -0
- package/dist/supervisor/main.js.map +1 -1
- package/package.json +2 -1
- package/stock_apps/dashboard/DEPLOY.md +30 -0
- package/stock_apps/dashboard/README.md +36 -0
- package/stock_apps/dashboard/dash.log +134 -0
- package/stock_apps/dashboard/eslint.config.mjs +19 -0
- package/stock_apps/dashboard/next.config.ts +12 -0
- package/stock_apps/dashboard/package-lock.json +8581 -0
- package/stock_apps/dashboard/package.json +42 -0
- package/stock_apps/dashboard/postcss.config.mjs +5 -0
- package/stock_apps/dashboard/public/file.svg +1 -0
- package/stock_apps/dashboard/public/globe.svg +1 -0
- package/stock_apps/dashboard/public/next.svg +1 -0
- package/stock_apps/dashboard/public/tars-logo.png +0 -0
- package/stock_apps/dashboard/public/vercel.svg +1 -0
- package/stock_apps/dashboard/public/window.svg +1 -0
- package/stock_apps/dashboard/server.js +488 -0
- package/stock_apps/dashboard/src/app/globals.css +122 -0
- package/stock_apps/dashboard/src/app/icon.png +0 -0
- package/stock_apps/dashboard/src/app/layout.tsx +35 -0
- package/stock_apps/dashboard/src/app/page.tsx +170 -0
- package/stock_apps/dashboard/src/components/FileExplorer.tsx +238 -0
- package/stock_apps/dashboard/src/components/IntelligencePanel.tsx +322 -0
- package/stock_apps/dashboard/src/components/MetricsPanel.tsx +347 -0
- package/stock_apps/dashboard/src/components/SystemActions.tsx +168 -0
- package/stock_apps/dashboard/src/context/SocketContext.tsx +62 -0
- package/stock_apps/dashboard/src/lib/socket.ts +10 -0
- package/stock_apps/dashboard/tsconfig.json +27 -0
- package/dist/discord/discord-bot.d.ts +0 -37
- package/dist/discord/discord-bot.js +0 -210
- package/dist/discord/discord-bot.js.map +0 -1
- package/dist/discord/message-formatter.d.ts +0 -95
- package/dist/discord/message-formatter.js +0 -482
- package/dist/discord/message-formatter.js.map +0 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState, memo } from 'react';
|
|
3
|
+
import { useSocket } from '@/context/SocketContext';
|
|
4
|
+
import {
|
|
5
|
+
Brain,
|
|
6
|
+
Calendar,
|
|
7
|
+
History,
|
|
8
|
+
Fingerprint,
|
|
9
|
+
Database,
|
|
10
|
+
Sparkles,
|
|
11
|
+
Hash,
|
|
12
|
+
Clock,
|
|
13
|
+
CheckCircle2,
|
|
14
|
+
AlertCircle
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
17
|
+
|
|
18
|
+
const SectionHeader = ({ icon: Icon, title, color }: any) => (
|
|
19
|
+
<div className="flex items-center gap-3 mb-3">
|
|
20
|
+
<div
|
|
21
|
+
className={`p-1.5 rounded-lg bg-opacity-10 ${color.replace('text-', 'bg-')} border border-white/5`}
|
|
22
|
+
>
|
|
23
|
+
<Icon size={12} className={color} />
|
|
24
|
+
</div>
|
|
25
|
+
<h3 className="text-[10px] font-black text-white uppercase tracking-[0.2em]">{title}</h3>
|
|
26
|
+
<div className="h-[1px] bg-white/5 flex-1 ml-4"></div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const MemoryItem = memo(({ fact, value }: { fact: string; value: any }) => {
|
|
31
|
+
const displayValue =
|
|
32
|
+
typeof value === 'object' && value !== null
|
|
33
|
+
? value.value || JSON.stringify(value)
|
|
34
|
+
: String(value);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-col gap-1 p-1.5 rounded-lg hover:bg-white/[0.03] transition-colors border border-transparent hover:border-white/5 group">
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
<Fingerprint
|
|
40
|
+
size={10}
|
|
41
|
+
className="text-accent-primary opacity-60 group-hover:opacity-100 transition-opacity"
|
|
42
|
+
/>
|
|
43
|
+
<span className="text-[9px] font-black text-accent-primary uppercase tracking-tighter">
|
|
44
|
+
{fact.replace(/_/g, ' ')}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
<span className="text-[11px] text-white font-medium leading-relaxed pl-4 break-words">
|
|
48
|
+
{displayValue}
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const TaskItem = memo(({ task }: { task: any }) => {
|
|
55
|
+
const isEnabled = task.enabled;
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex items-center gap-3 p-2.5 rounded-xl bg-white/[0.02] border border-white/5 hover:border-accent-warning/30 transition-all group">
|
|
58
|
+
<div
|
|
59
|
+
className={`p-1.5 rounded-lg ${isEnabled ? 'bg-accent-warning/10' : 'bg-white/5 opacity-40'}`}
|
|
60
|
+
>
|
|
61
|
+
<Calendar size={14} className={isEnabled ? 'text-accent-warning' : 'text-white'} />
|
|
62
|
+
</div>
|
|
63
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
64
|
+
<span
|
|
65
|
+
className={`text-[10px] font-black truncate uppercase tracking-tight ${isEnabled ? 'text-white' : 'text-white/30'}`}
|
|
66
|
+
>
|
|
67
|
+
{task.title}
|
|
68
|
+
</span>
|
|
69
|
+
<span className="text-[8px] text-white/40 font-mono mt-0.5 truncate italic">
|
|
70
|
+
{task.schedule}
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="flex flex-col items-end gap-1">
|
|
74
|
+
{task.lastRun && (
|
|
75
|
+
<div className="flex items-center gap-1.5 text-[8px] font-bold text-accent-secondary uppercase">
|
|
76
|
+
<CheckCircle2 size={10} />
|
|
77
|
+
<span className="text-white">Success</span>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
{!isEnabled && (
|
|
81
|
+
<span className="text-[8px] font-black text-white/20 uppercase tracking-widest border border-white/10 px-1.5 py-0.5 rounded">
|
|
82
|
+
Paused
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const CognitiveBuffer = memo(({ data }: { data: any }) => {
|
|
91
|
+
const factsList = data.facts?.facts || data.facts || {};
|
|
92
|
+
return (
|
|
93
|
+
<div className="card bg-[#0c0c0c] border-white/10 flex flex-col h-[400px] p-4">
|
|
94
|
+
<div className="card-header-btop">Cognitive Buffer (Memory)</div>
|
|
95
|
+
<div className="mt-2 flex-1 overflow-y-auto custom-scrollbar pr-2">
|
|
96
|
+
<SectionHeader icon={Brain} title="Long-Term Facts" color="text-accent-primary" />
|
|
97
|
+
<div className="flex flex-col gap-1.5">
|
|
98
|
+
{Object.entries(factsList).map(([key, value]: any) => (
|
|
99
|
+
<MemoryItem key={key} fact={key} value={value} />
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const JobQueue = memo(({ data }: { data: any }) => {
|
|
108
|
+
return (
|
|
109
|
+
<div className="card bg-[#0c0c0c] border-white/10 flex flex-col h-[400px] p-4">
|
|
110
|
+
<div className="card-header-btop">Autonomous Job Queue</div>
|
|
111
|
+
<div className="mt-2 flex-1 overflow-y-auto custom-scrollbar pr-2">
|
|
112
|
+
<SectionHeader
|
|
113
|
+
icon={Calendar}
|
|
114
|
+
title="Scheduled Actions"
|
|
115
|
+
color="text-accent-warning"
|
|
116
|
+
/>
|
|
117
|
+
<div className="flex flex-col gap-2">
|
|
118
|
+
{data.tasks && data.tasks.length > 0 ? (
|
|
119
|
+
data.tasks.map((task: any) => <TaskItem key={task.id} task={task} />)
|
|
120
|
+
) : (
|
|
121
|
+
<div className="p-12 text-center opacity-20 italic text-[10px] uppercase font-black tracking-widest text-white">
|
|
122
|
+
No Active Tasks
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const SessionIntelligence = memo(({ data }: { data: any }) => {
|
|
132
|
+
return (
|
|
133
|
+
<div className="card bg-[#0c0c0c] border-white/10 flex flex-col h-[400px] p-4 shadow-2xl">
|
|
134
|
+
<div className="card-header-btop text-accent-primary border-accent-primary/30">
|
|
135
|
+
Session Intelligence
|
|
136
|
+
</div>
|
|
137
|
+
<div className="mt-2 flex-1 overflow-y-auto custom-scrollbar pr-2">
|
|
138
|
+
<SectionHeader
|
|
139
|
+
icon={Sparkles}
|
|
140
|
+
title="Current Context"
|
|
141
|
+
color="text-accent-primary"
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
<div className="grid grid-cols-2 gap-2.5 mb-4">
|
|
145
|
+
<div className="bg-white/5 rounded-xl p-2.5 border border-white/5">
|
|
146
|
+
<span className="text-[8px] font-black text-white/40 uppercase block mb-1">
|
|
147
|
+
Interactions
|
|
148
|
+
</span>
|
|
149
|
+
<span className="text-base font-black text-white font-mono leading-none">
|
|
150
|
+
{data.session?.interactionCount || 0}
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
<div className="bg-white/5 rounded-xl p-2.5 border border-white/5">
|
|
154
|
+
<span className="text-[8px] font-black text-white/40 uppercase block mb-1">
|
|
155
|
+
Context Depth
|
|
156
|
+
</span>
|
|
157
|
+
<span className="text-base font-black text-white font-mono leading-none">
|
|
158
|
+
{Math.round((data.session?.totalNetTokens || 0) / 1000)}K
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<SectionHeader
|
|
164
|
+
icon={Database}
|
|
165
|
+
title="Token Telemetry"
|
|
166
|
+
color="text-accent-secondary"
|
|
167
|
+
/>
|
|
168
|
+
<div className="space-y-3 px-1">
|
|
169
|
+
{[
|
|
170
|
+
{
|
|
171
|
+
label: 'Input Tokens',
|
|
172
|
+
value: data.session?.totalInputTokens,
|
|
173
|
+
color: 'bg-accent-primary',
|
|
174
|
+
max: data.session?.totalNetTokens || 1000000
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: 'Output Tokens',
|
|
178
|
+
value: data.session?.totalOutputTokens,
|
|
179
|
+
color: 'bg-accent-secondary',
|
|
180
|
+
max: (data.session?.totalNetTokens || 1000000) / 10
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
label: 'Cached Tokens',
|
|
184
|
+
value: data.session?.totalCachedTokens,
|
|
185
|
+
color: 'bg-accent-warning',
|
|
186
|
+
max: data.session?.totalNetTokens || 1000000
|
|
187
|
+
}
|
|
188
|
+
].map((stat) => (
|
|
189
|
+
<div key={stat.label} className="space-y-1">
|
|
190
|
+
<div className="flex justify-between items-baseline">
|
|
191
|
+
<span className="text-[8px] font-black text-white/40 uppercase tracking-tighter">
|
|
192
|
+
{stat.label}
|
|
193
|
+
</span>
|
|
194
|
+
<span className="text-[9px] font-bold text-white font-mono">
|
|
195
|
+
{(stat.value || 0).toLocaleString()}
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div className="h-1 bg-white/5 rounded-full overflow-hidden">
|
|
199
|
+
<motion.div
|
|
200
|
+
initial={{ width: 0 }}
|
|
201
|
+
animate={{
|
|
202
|
+
width: `${Math.min(100, ((stat.value || 0) / stat.max) * 100)}%`
|
|
203
|
+
}}
|
|
204
|
+
className={`h-full ${stat.color} opacity-60`}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div className="mt-8">
|
|
212
|
+
<SectionHeader
|
|
213
|
+
icon={History}
|
|
214
|
+
title="Session Lifecycle"
|
|
215
|
+
color="text-accent-warning"
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
219
|
+
<div className="bg-white/5 rounded-xl p-2.5 border border-white/5">
|
|
220
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
221
|
+
<Database size={8} className="text-white/40" />
|
|
222
|
+
<span className="text-[8px] font-black text-white/40 uppercase block">
|
|
223
|
+
Total Sessions
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
<span className="text-sm font-black text-white font-mono">
|
|
227
|
+
{data.sessionStats?.total || 0}
|
|
228
|
+
</span>
|
|
229
|
+
</div>
|
|
230
|
+
<div className="bg-white/5 rounded-xl p-2.5 border border-white/5">
|
|
231
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
232
|
+
<Clock size={8} className="text-white/40" />
|
|
233
|
+
<span className="text-[8px] font-black text-white/40 uppercase block">
|
|
234
|
+
Last Switch
|
|
235
|
+
</span>
|
|
236
|
+
</div>
|
|
237
|
+
<span className="text-[10px] font-bold text-white font-mono leading-tight">
|
|
238
|
+
{data.sessionStats?.lastSwitch
|
|
239
|
+
? new Date(data.sessionStats.lastSwitch).toLocaleTimeString([], {
|
|
240
|
+
hour: '2-digit',
|
|
241
|
+
minute: '2-digit',
|
|
242
|
+
second: '2-digit'
|
|
243
|
+
})
|
|
244
|
+
: 'N/A'}
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div className="space-y-1.5 max-h-[120px] overflow-y-auto custom-scrollbar pr-1">
|
|
250
|
+
{data.sessionStats?.history?.map((s: any, i: number) => (
|
|
251
|
+
<div
|
|
252
|
+
key={i}
|
|
253
|
+
className="flex justify-between items-center p-2 rounded-lg bg-white/[0.02] border border-white/5 hover:bg-white/[0.04] transition-colors group"
|
|
254
|
+
>
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
<div className="w-1 h-1 rounded-full bg-accent-primary opacity-40 group-hover:opacity-100 transition-opacity" />
|
|
257
|
+
<span className="text-[9px] font-mono font-bold text-white/60">
|
|
258
|
+
S_{s.id.slice(0, 6)}
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
261
|
+
<span className="text-[8px] font-mono text-white/30">
|
|
262
|
+
{new Date(s.time).toLocaleDateString()}{' '}
|
|
263
|
+
{new Date(s.time).toLocaleTimeString([], {
|
|
264
|
+
hour: '2-digit',
|
|
265
|
+
minute: '2-digit'
|
|
266
|
+
})}
|
|
267
|
+
</span>
|
|
268
|
+
</div>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="mt-6 p-3 bg-accent-primary/5 border border-accent-primary/20 rounded-xl flex items-center gap-3">
|
|
273
|
+
<div className="p-1.5 bg-accent-primary/20 rounded-lg">
|
|
274
|
+
<Hash size={14} className="text-accent-primary" />
|
|
275
|
+
</div>
|
|
276
|
+
<div className="flex flex-col">
|
|
277
|
+
<span className="text-[8px] font-black text-white/40 uppercase leading-none">
|
|
278
|
+
Session_ID
|
|
279
|
+
</span>
|
|
280
|
+
<span className="text-[9px] font-mono text-white mt-1 break-all">
|
|
281
|
+
{data.session?.sessionId?.split('-')[0]}...
|
|
282
|
+
</span>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
export function IntelligencePanel() {
|
|
291
|
+
const { socket, subscribe, unsubscribe } = useSocket();
|
|
292
|
+
const [data, setData] = useState<any>(null);
|
|
293
|
+
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (!socket) return;
|
|
296
|
+
subscribe('intelligence');
|
|
297
|
+
|
|
298
|
+
const handleInit = (initData: any) => setData(initData);
|
|
299
|
+
const handleUpdate = ({ type, data: updateData }: any) => {
|
|
300
|
+
setData((prev: any) => ({ ...prev, [type]: updateData }));
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
socket.on('intelligence_init', handleInit);
|
|
304
|
+
socket.on('intelligence_update', handleUpdate);
|
|
305
|
+
|
|
306
|
+
return () => {
|
|
307
|
+
unsubscribe('intelligence');
|
|
308
|
+
socket.off('intelligence_init', handleInit);
|
|
309
|
+
socket.off('intelligence_update', handleUpdate);
|
|
310
|
+
};
|
|
311
|
+
}, [socket, subscribe, unsubscribe]);
|
|
312
|
+
|
|
313
|
+
if (!data) return null;
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
|
317
|
+
<CognitiveBuffer data={data} />
|
|
318
|
+
<JobQueue data={data} />
|
|
319
|
+
<SessionIntelligence data={data} />
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState, useRef, memo, useCallback } from 'react';
|
|
3
|
+
import { useSocket } from '@/context/SocketContext';
|
|
4
|
+
import {
|
|
5
|
+
Terminal,
|
|
6
|
+
Cpu,
|
|
7
|
+
MemoryStick as Memory,
|
|
8
|
+
Database,
|
|
9
|
+
Activity,
|
|
10
|
+
Clock,
|
|
11
|
+
Server,
|
|
12
|
+
RefreshCw,
|
|
13
|
+
Zap,
|
|
14
|
+
HardDrive,
|
|
15
|
+
ArrowUpRight,
|
|
16
|
+
ArrowDownLeft
|
|
17
|
+
} from 'lucide-react';
|
|
18
|
+
import { motion } from 'framer-motion';
|
|
19
|
+
|
|
20
|
+
const MetricCard = memo(({ icon: Icon, label, value, unit, color, extra, children }: any) => (
|
|
21
|
+
<div className="card card-with-header relative hover:border-accent-primary/40 transition-all border-white/5 bg-[#0c0c0c] p-4">
|
|
22
|
+
<div className="card-header-btop">{label}</div>
|
|
23
|
+
<div className="flex items-center justify-between mb-4 mt-2">
|
|
24
|
+
<div className="flex items-center gap-3">
|
|
25
|
+
<div
|
|
26
|
+
className={`p-1.5 rounded-lg bg-opacity-10 ${color.replace('text-', 'bg-')} border border-white/5`}
|
|
27
|
+
>
|
|
28
|
+
<Icon size={18} className={color} />
|
|
29
|
+
</div>
|
|
30
|
+
<span className={`text-[14px] font-black font-mono text-white`}>{value}</span>
|
|
31
|
+
<span className={`text-[9px] font-bold opacity-40 text-white uppercase`}>
|
|
32
|
+
{unit}
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div className="w-full bg-white/5 h-1.5 rounded-full overflow-hidden relative mb-2">
|
|
38
|
+
<motion.div
|
|
39
|
+
initial={{ width: 0 }}
|
|
40
|
+
animate={{ width: `${Math.min(100, parseFloat(value))}%` }}
|
|
41
|
+
transition={{ duration: 1, ease: 'easeOut' }}
|
|
42
|
+
className={`h-full ${color.replace('text-', 'bg-')} shadow-[0_0:10px_currentColor]`}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{extra && (
|
|
47
|
+
<div className="flex justify-between items-center text-[9px] font-bold text-white uppercase mt-3 tracking-widest opacity-60">
|
|
48
|
+
{extra}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
));
|
|
54
|
+
|
|
55
|
+
export const LogViewer = memo(() => {
|
|
56
|
+
const { socket, subscribe, unsubscribe } = useSocket();
|
|
57
|
+
const [logs, setLogs] = useState<string[]>([]);
|
|
58
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!socket) return;
|
|
62
|
+
subscribe('logs');
|
|
63
|
+
|
|
64
|
+
const handleLogsInit = (data: string[]) => setLogs(data);
|
|
65
|
+
const handleLogsUpdate = (newLines: string[]) =>
|
|
66
|
+
setLogs((prev) => [...prev, ...newLines].slice(-300));
|
|
67
|
+
|
|
68
|
+
socket.on('logs_init', handleLogsInit);
|
|
69
|
+
socket.on('logs_update', handleLogsUpdate);
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
unsubscribe('logs');
|
|
73
|
+
socket.off('logs_init', handleLogsInit);
|
|
74
|
+
socket.off('logs_update', handleLogsUpdate);
|
|
75
|
+
};
|
|
76
|
+
}, [socket, subscribe, unsubscribe]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (scrollRef.current) {
|
|
80
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
81
|
+
}
|
|
82
|
+
}, [logs]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="card card-with-header flex flex-col h-[400px] shadow-2xl transition-all border-white/10 bg-black/40 p-0 overflow-visible relative">
|
|
86
|
+
<div className="card-header-btop text-accent-secondary border-accent-secondary/30">
|
|
87
|
+
Supervisor Feed
|
|
88
|
+
</div>
|
|
89
|
+
<div className="flex items-center justify-between p-3 border-b border-white/5 mb-1 bg-[#0a0a0a]/50 mt-4">
|
|
90
|
+
<div className="flex items-center gap-3">
|
|
91
|
+
<span className="text-[8px] text-white/40 uppercase flex items-center gap-2 font-bold tracking-widest">
|
|
92
|
+
<Server size={10} className="text-accent-primary opacity-50" />{' '}
|
|
93
|
+
pm2_supervisor
|
|
94
|
+
<span className="w-1.5 h-1.5 bg-accent-secondary rounded-full animate-pulse shadow-[0_0_8px_#10B981]"></span>
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div
|
|
100
|
+
ref={scrollRef}
|
|
101
|
+
className="flex-1 overflow-y-auto p-4 font-mono text-[10px] md:text-[11px] leading-relaxed custom-scrollbar selection:bg-accent-primary selection:text-white rounded-b-2xl"
|
|
102
|
+
>
|
|
103
|
+
{logs.length === 0 ? (
|
|
104
|
+
<div className="flex flex-col items-center justify-center h-full gap-4 opacity-30">
|
|
105
|
+
<RefreshCw size={24} className="animate-spin text-accent-primary" />
|
|
106
|
+
<span className="font-black tracking-[0.3em] uppercase text-[9px] text-white">
|
|
107
|
+
Establishing Stream...
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
logs.map((log, i) => {
|
|
112
|
+
const isError = log.includes('[ERR]');
|
|
113
|
+
const isWarn = log.includes('[WARN]');
|
|
114
|
+
|
|
115
|
+
// Clean up Gemini events for readability
|
|
116
|
+
let displayLog = log;
|
|
117
|
+
if (log.includes('Raw Gemini Event')) {
|
|
118
|
+
try {
|
|
119
|
+
const match = log.match(/\[Turn (\d+)\]: (\{.*\})/);
|
|
120
|
+
if (match) {
|
|
121
|
+
const turn = match[1];
|
|
122
|
+
const data = JSON.parse(match[2]);
|
|
123
|
+
displayLog = `[Turn ${turn}] GEMINI_${data.type.toUpperCase()}: ${typeof data.value === 'string' ? data.value.slice(0, 100) + (data.value.length > 100 ? '...' : '') : JSON.stringify(data.value)}`;
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
key={i}
|
|
131
|
+
className={`flex gap-4 py-1 border-b border-white/[0.03] hover:bg-white/5 transition-all ${isError ? 'text-accent-danger font-bold' : isWarn ? 'text-accent-warning font-bold' : 'text-white/80'}`}
|
|
132
|
+
>
|
|
133
|
+
<span className="opacity-20 shrink-0 w-10 select-none font-black text-right">
|
|
134
|
+
{(i + 1).toString().padStart(3, '0')}
|
|
135
|
+
</span>
|
|
136
|
+
<span className="break-all whitespace-pre-wrap flex-1">
|
|
137
|
+
{displayLog}
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
})
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
export function MetricsPanel() {
|
|
149
|
+
const { socket, subscribe, unsubscribe } = useSocket();
|
|
150
|
+
const [metrics, setMetrics] = useState<any>(null);
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!socket) return;
|
|
154
|
+
subscribe('metrics');
|
|
155
|
+
|
|
156
|
+
const handleMetrics = (data: any) => setMetrics(data);
|
|
157
|
+
socket.on('metrics_update', handleMetrics);
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
unsubscribe('metrics');
|
|
161
|
+
socket.off('metrics_update', handleMetrics);
|
|
162
|
+
};
|
|
163
|
+
}, [socket, subscribe, unsubscribe]);
|
|
164
|
+
|
|
165
|
+
const formatUptime = useCallback((seconds: number) => {
|
|
166
|
+
const d = Math.floor(seconds / (3600 * 24));
|
|
167
|
+
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
|
168
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
169
|
+
return `${d}d ${h}h ${m}m`;
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
if (!metrics)
|
|
173
|
+
return (
|
|
174
|
+
<div className="flex flex-col items-center justify-center p-20 gap-6 opacity-30">
|
|
175
|
+
<RefreshCw size={32} className="animate-spin text-accent-primary" />
|
|
176
|
+
<span className="text-white font-black tracking-[0.5em] uppercase text-xs">
|
|
177
|
+
Syncing...
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="flex flex-col gap-8">
|
|
184
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6">
|
|
185
|
+
<MetricCard
|
|
186
|
+
icon={Cpu}
|
|
187
|
+
label="Processor"
|
|
188
|
+
value={metrics.cpu.load}
|
|
189
|
+
unit="%"
|
|
190
|
+
color="text-accent-primary"
|
|
191
|
+
extra={
|
|
192
|
+
<div className="flex items-center justify-between w-full">
|
|
193
|
+
<div className="flex items-center gap-2">
|
|
194
|
+
<Clock size={10} className="opacity-50" />
|
|
195
|
+
<span className="text-white">
|
|
196
|
+
UP: {formatUptime(metrics.uptime)}
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<span className="text-white font-bold">{metrics.cpu.temp}°C</span>
|
|
200
|
+
</div>
|
|
201
|
+
}
|
|
202
|
+
>
|
|
203
|
+
<div className="grid grid-cols-8 gap-1 mt-3">
|
|
204
|
+
{metrics.cpu.cpus?.map((load: string, i: number) => (
|
|
205
|
+
<div
|
|
206
|
+
key={i}
|
|
207
|
+
className="h-4 bg-white/5 w-full overflow-hidden rounded-sm relative group"
|
|
208
|
+
>
|
|
209
|
+
<div
|
|
210
|
+
className="absolute bottom-0 left-0 right-0 bg-accent-primary opacity-60"
|
|
211
|
+
style={{ height: `${load}%` }}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
</MetricCard>
|
|
217
|
+
|
|
218
|
+
<MetricCard
|
|
219
|
+
icon={Memory}
|
|
220
|
+
label="Memory"
|
|
221
|
+
value={metrics.mem.usage}
|
|
222
|
+
unit="%"
|
|
223
|
+
color="text-accent-primary"
|
|
224
|
+
extra={
|
|
225
|
+
<div className="flex justify-between w-full">
|
|
226
|
+
<span className="text-white font-bold">{metrics.mem.used}G USED</span>
|
|
227
|
+
<span className="text-white opacity-40">{metrics.mem.total}G</span>
|
|
228
|
+
</div>
|
|
229
|
+
}
|
|
230
|
+
>
|
|
231
|
+
<div className="flex flex-col gap-1.5 mt-3">
|
|
232
|
+
<div className="flex justify-between items-center text-[8px] font-black opacity-40 uppercase text-white px-1">
|
|
233
|
+
<span>Cached: {metrics.mem.cached}G</span>
|
|
234
|
+
<span>Swap: {metrics.mem.swapUsed}G</span>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</MetricCard>
|
|
238
|
+
|
|
239
|
+
{metrics.gpu && (
|
|
240
|
+
<MetricCard
|
|
241
|
+
icon={Zap}
|
|
242
|
+
label="Graphics (AMD)"
|
|
243
|
+
value={metrics.gpu.usage}
|
|
244
|
+
unit="%"
|
|
245
|
+
color="text-accent-secondary"
|
|
246
|
+
extra={
|
|
247
|
+
<div className="flex justify-between w-full">
|
|
248
|
+
<span className="text-white font-bold">
|
|
249
|
+
{metrics.gpu.memUsed}G VRAM
|
|
250
|
+
</span>
|
|
251
|
+
<span className="text-white opacity-40">{metrics.gpu.temp}°C</span>
|
|
252
|
+
</div>
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
<div className="flex flex-col gap-1.5 mt-3">
|
|
256
|
+
<div className="flex justify-between items-center bg-white/[0.03] p-1.5 px-2.5 rounded-lg border border-white/5">
|
|
257
|
+
<span className="text-[8px] font-black opacity-40 uppercase text-white">
|
|
258
|
+
Power
|
|
259
|
+
</span>
|
|
260
|
+
<span className="text-[11px] text-white font-black font-mono">
|
|
261
|
+
{metrics.gpu.power}W
|
|
262
|
+
</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex justify-between items-center bg-white/[0.03] p-1.5 px-2.5 rounded-lg border border-white/5">
|
|
265
|
+
<span className="text-[8px] font-black opacity-40 uppercase text-white">
|
|
266
|
+
Clock
|
|
267
|
+
</span>
|
|
268
|
+
<span className="text-[11px] text-white font-black font-mono">
|
|
269
|
+
{metrics.gpu.clock}
|
|
270
|
+
</span>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</MetricCard>
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
<MetricCard
|
|
277
|
+
icon={HardDrive}
|
|
278
|
+
label="Storage"
|
|
279
|
+
value={metrics.disks[0]?.use || 0}
|
|
280
|
+
unit="%"
|
|
281
|
+
color="text-accent-warning"
|
|
282
|
+
extra={
|
|
283
|
+
<span className="text-white truncate max-w-full">
|
|
284
|
+
{metrics.disks[0]?.mount}
|
|
285
|
+
</span>
|
|
286
|
+
}
|
|
287
|
+
>
|
|
288
|
+
<div className="flex flex-col gap-1.5 mt-3">
|
|
289
|
+
{metrics.disks.slice(1, 3).map((d: any, i: number) => (
|
|
290
|
+
<div
|
|
291
|
+
key={i}
|
|
292
|
+
className="flex justify-between items-center bg-white/[0.03] p-1.5 px-2.5 rounded-lg border border-white/5"
|
|
293
|
+
>
|
|
294
|
+
<span className="text-[8px] font-black opacity-40 uppercase text-white truncate max-w-[60px]">
|
|
295
|
+
{d.mount}
|
|
296
|
+
</span>
|
|
297
|
+
<span className="text-[10px] text-white font-black font-mono">
|
|
298
|
+
{d.use}%
|
|
299
|
+
</span>
|
|
300
|
+
</div>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
</MetricCard>
|
|
304
|
+
|
|
305
|
+
<MetricCard
|
|
306
|
+
icon={Activity}
|
|
307
|
+
label="Traffic"
|
|
308
|
+
value={metrics.net[0]?.rx || 0}
|
|
309
|
+
unit=" KB/s"
|
|
310
|
+
color="text-accent-secondary"
|
|
311
|
+
>
|
|
312
|
+
<div className="flex flex-col gap-1.5 mt-3">
|
|
313
|
+
{metrics.net.slice(0, 1).map((n: any, i: number) => (
|
|
314
|
+
<div key={i} className="flex flex-col gap-1">
|
|
315
|
+
<div className="flex justify-between items-center bg-white/[0.03] p-1.5 px-2.5 rounded-lg border border-white/5">
|
|
316
|
+
<div className="flex items-center gap-2">
|
|
317
|
+
<ArrowDownLeft
|
|
318
|
+
size={10}
|
|
319
|
+
className="text-accent-secondary"
|
|
320
|
+
/>
|
|
321
|
+
<span className="text-[8px] font-black opacity-40 uppercase text-white">
|
|
322
|
+
{n.iface} In
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
325
|
+
<span className="text-[11px] text-white font-black font-mono">
|
|
326
|
+
{n.rx}
|
|
327
|
+
</span>
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex justify-between items-center bg-white/[0.03] p-1.5 px-2.5 rounded-lg border border-white/5">
|
|
330
|
+
<div className="flex items-center gap-2">
|
|
331
|
+
<ArrowUpRight size={10} className="text-accent-warning" />
|
|
332
|
+
<span className="text-[8px] font-black opacity-40 uppercase text-white">
|
|
333
|
+
{n.iface} Out
|
|
334
|
+
</span>
|
|
335
|
+
</div>
|
|
336
|
+
<span className="text-[11px] text-white font-black font-mono">
|
|
337
|
+
{n.tx}
|
|
338
|
+
</span>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
))}
|
|
342
|
+
</div>
|
|
343
|
+
</MetricCard>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|