@meshxdata/fops 0.1.46 → 0.1.48

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,412 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from "react";
2
-
3
- /**
4
- * WebSocket hook for real-time FopsCore events.
5
- */
6
- export function useWebSocket() {
7
- const [connected, setConnected] = useState(false);
8
- const [sessions, setSessions] = useState([]);
9
- const [activeSession, setActiveSession] = useState(null);
10
- const [agents, setAgents] = useState([]);
11
- const [messages, setMessages] = useState([]);
12
- const [streamingText, setStreamingText] = useState("");
13
- const [thinkingText, setThinkingText] = useState("");
14
- const [statusText, setStatusText] = useState("");
15
- const [toolCalls, setToolCalls] = useState([]);
16
- const [pendingConfirm, setPendingConfirm] = useState(null);
17
- const [isStreaming, setIsStreaming] = useState(false);
18
- const [services, setServices] = useState([]);
19
- const [foundationStats, setFoundationStats] = useState(null);
20
- const [updateAvailable, setUpdateAvailable] = useState(null);
21
- const wsRef = useRef(null);
22
- const lastStreamEventRef = useRef(0);
23
-
24
- const connect = useCallback(() => {
25
- const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
26
- const ws = new WebSocket(`${proto}//${window.location.host}/ws`);
27
-
28
- ws.onopen = () => setConnected(true);
29
- ws.onclose = () => {
30
- setConnected(false);
31
- setTimeout(connect, 2000);
32
- };
33
-
34
- ws.onmessage = (e) => {
35
- const msg = JSON.parse(e.data);
36
-
37
- switch (msg.type) {
38
- case "init": {
39
- const incoming = msg.sessions || [];
40
- setSessions((prev) => (incoming.length >= prev.length ? incoming : prev));
41
- setActiveSession(msg.activeSession);
42
- setAgents(msg.agents || []);
43
- if (msg.services) setServices(msg.services);
44
- if (msg.foundationStats) setFoundationStats(msg.foundationStats);
45
- if (msg.updateAvailable) setUpdateAvailable(msg.updateAvailable);
46
- // Load messages for active session
47
- if (msg.activeSession) {
48
- fetch(`/api/sessions/${msg.activeSession}`)
49
- .then((r) => r.json())
50
- .then((data) => setMessages(data.messages || []));
51
- }
52
- break;
53
- }
54
-
55
- case "session:created":
56
- setSessions((prev) =>
57
- prev.some((s) => s.id === msg.sessionId)
58
- ? prev
59
- : [...prev, { id: msg.sessionId, agent: msg.agentName, status: "idle" }]
60
- );
61
- break;
62
-
63
- case "session:switched":
64
- setActiveSession(msg.sessionId);
65
- setStreamingText("");
66
- setThinkingText("");
67
- setToolCalls([]);
68
- fetch(`/api/sessions/${msg.sessionId}`)
69
- .then((r) => r.json())
70
- .then((data) => setMessages(data.messages || []));
71
- break;
72
-
73
- case "session:closed":
74
- setSessions((prev) => prev.filter((s) => s.id !== msg.sessionId));
75
- break;
76
-
77
- case "stream:start":
78
- lastStreamEventRef.current = Date.now();
79
- setIsStreaming(true);
80
- setStreamingText("");
81
- setThinkingText("");
82
- setStatusText("Thinking");
83
- setToolCalls([]);
84
- break;
85
-
86
- case "stream:chunk":
87
- lastStreamEventRef.current = Date.now();
88
- setStreamingText(msg.text);
89
- setStatusText("Responding");
90
- break;
91
-
92
- case "stream:thinking":
93
- lastStreamEventRef.current = Date.now();
94
- setThinkingText((prev) => prev + msg.text);
95
- setStatusText("Reasoning");
96
- break;
97
-
98
- case "stream:status":
99
- lastStreamEventRef.current = Date.now();
100
- setStatusText(msg.status);
101
- break;
102
-
103
- case "stream:end":
104
- lastStreamEventRef.current = 0;
105
- setIsStreaming(false);
106
- setStreamingText("");
107
- setThinkingText("");
108
- setStatusText("");
109
- // Refresh messages
110
- if (msg.sessionId) {
111
- fetch(`/api/sessions/${msg.sessionId}`)
112
- .then((r) => r.json())
113
- .then((data) => setMessages(data.messages || []));
114
- }
115
- break;
116
-
117
- case "tool:start":
118
- lastStreamEventRef.current = Date.now();
119
- setToolCalls((prev) => [...prev, { name: msg.name, status: "running" }]);
120
- setStatusText(`Using ${msg.name}`);
121
- break;
122
-
123
- case "tool:result":
124
- lastStreamEventRef.current = Date.now();
125
- setToolCalls((prev) =>
126
- prev.map((tc) =>
127
- tc.name === msg.name && tc.status === "running"
128
- ? { ...tc, status: msg.ok ? "done" : "error" }
129
- : tc
130
- )
131
- );
132
- break;
133
-
134
- case "tool:confirm":
135
- setPendingConfirm({ name: msg.name, input: msg.input });
136
- break;
137
-
138
- case "stack:status":
139
- if (msg.services) setServices(msg.services);
140
- if (msg.foundationStats) setFoundationStats(msg.foundationStats);
141
- break;
142
-
143
- case "ollama:log":
144
- setMessages((prev) => {
145
- const last = prev[prev.length - 1];
146
- if (last?.role === "assistant" && last.content.startsWith("### Ollama Setup")) {
147
- return [...prev.slice(0, -1), { role: "assistant", content: last.content + `\n${msg.line}` }];
148
- }
149
- return prev;
150
- });
151
- break;
152
-
153
- case "ollama:done": {
154
- const result = msg.result || {};
155
- const lines = ["### Ollama Setup Complete\n"];
156
- for (const s of (result.steps || [])) {
157
- const icon = s.status === "ok" ? "\u2713" : s.status === "error" ? "\u2717" : s.status === "warn" ? "\u26A0" : "\u25CF";
158
- lines.push(`- ${icon} **${s.step}** \u2014 ${s.detail}`);
159
- }
160
- if (result.ok && result.model) {
161
- lines.push(`\n**Ready!** Use \`/local\` to switch to Ollama mode with \`${result.model}\``);
162
- lines.push(`Or set \`OLLAMA_MODEL=${result.model}\` in your environment.`);
163
- } else if (!result.ok) {
164
- lines.push("\n**Setup had errors.** Check the issues above.");
165
- }
166
- setMessages((prev) => {
167
- const filtered = prev.filter((m) => !(m.role === "assistant" && m.content.startsWith("### Ollama Setup\nDetecting")));
168
- return [...filtered, { role: "assistant", content: lines.join("\n") }];
169
- });
170
- break;
171
- }
172
-
173
- case "stack:log":
174
- setMessages((prev) => {
175
- const last = prev[prev.length - 1];
176
- if (last?.role === "assistant" && (last.content.startsWith("### Starting") || last.content.startsWith("### Stopping"))) {
177
- const lines = last.content.split("\n");
178
- if (lines.length > 20) lines.splice(1, lines.length - 20);
179
- return [...prev.slice(0, -1), { role: "assistant", content: lines.join("\n") + `\n${msg.line}` }];
180
- }
181
- return prev;
182
- });
183
- break;
184
-
185
- case "stack:done": {
186
- const label = msg.action === "up" ? "started" : "stopped";
187
- const summary = msg.ok
188
- ? `\n\n**Stack ${label}.**`
189
- : `\n\n**Stack ${msg.action} failed.** ${msg.error || `(exit code ${msg.exitCode ?? "unknown"})`}`;
190
- setMessages((prev) => {
191
- const last = prev[prev.length - 1];
192
- if (last?.role === "assistant" && (last.content.startsWith("### Starting") || last.content.startsWith("### Stopping"))) {
193
- return [...prev.slice(0, -1), { role: "assistant", content: last.content + summary }];
194
- }
195
- return [...prev, { role: "assistant", content: `### Stack${summary}` }];
196
- });
197
- break;
198
- }
199
-
200
- case "bootstrap:log":
201
- setMessages((prev) => {
202
- const last = prev[prev.length - 1];
203
- if (last?.role === "assistant" && last.content.startsWith("### Bootstrap")) {
204
- const lines = last.content.split("\n");
205
- if (lines.length > 20) lines.splice(1, lines.length - 20);
206
- return [...prev.slice(0, -1), { role: "assistant", content: lines.join("\n") + `\n${msg.line}` }];
207
- }
208
- return prev;
209
- });
210
- break;
211
-
212
- case "bootstrap:done": {
213
- const summary = msg.ok
214
- ? "\n\n**Bootstrap complete.**"
215
- : `\n\n**Bootstrap failed.** ${msg.error || `(exit code ${msg.exitCode ?? "unknown"})`}`;
216
- setMessages((prev) => {
217
- const last = prev[prev.length - 1];
218
- if (last?.role === "assistant" && last.content.startsWith("### Bootstrap")) {
219
- return [...prev.slice(0, -1), { role: "assistant", content: last.content + summary }];
220
- }
221
- return [...prev, { role: "assistant", content: `### Bootstrap${summary}` }];
222
- });
223
- break;
224
- }
225
-
226
- case "train:log":
227
- setMessages((prev) => {
228
- const last = prev[prev.length - 1];
229
- if (last?.role === "assistant" && last.content.startsWith("### Embedding Training")) {
230
- const lines = last.content.split("\n");
231
- if (lines.length > 20) lines.splice(1, lines.length - 20);
232
- return [...prev.slice(0, -1), { role: "assistant", content: lines.join("\n") + `\n${msg.line}` }];
233
- }
234
- return prev;
235
- });
236
- break;
237
-
238
- case "train:done": {
239
- const result = msg.result || {};
240
- const lines = [result.ok ? "### Embedding Training Complete\n" : "### Embedding Training Failed\n"];
241
- for (const s of (result.steps || [])) {
242
- const icon = s.status === "ok" ? "\u2713" : s.status === "error" ? "\u2717" : s.status === "warn" ? "\u26A0" : "\u25CF";
243
- lines.push(`- ${icon} **${s.step}** \u2014 ${s.detail}`);
244
- }
245
- if (!result.ok) {
246
- lines.push("\n**Training had errors.** Check the issues above.");
247
- }
248
- setMessages((prev) => {
249
- const filtered = prev.filter((m) => !(m.role === "assistant" && m.content.startsWith("### Embedding Training\n")));
250
- return [...filtered, { role: "assistant", content: lines.join("\n") }];
251
- });
252
- break;
253
- }
254
-
255
- case "error":
256
- lastStreamEventRef.current = 0;
257
- setIsStreaming(false);
258
- setStatusText("");
259
- setStreamingText("");
260
- setThinkingText("");
261
- if (msg.message) {
262
- setMessages((prev) => [...prev, { role: "assistant", content: `**Error:** ${msg.message}` }]);
263
- }
264
- break;
265
- }
266
- };
267
-
268
- wsRef.current = ws;
269
- }, []);
270
-
271
- useEffect(() => {
272
- connect();
273
- return () => wsRef.current?.close();
274
- }, [connect]);
275
-
276
- // Poll services + foundation stats every 30s via REST API
277
- useEffect(() => {
278
- const poll = () => {
279
- fetch("/api/status")
280
- .then((r) => r.json())
281
- .then((data) => {
282
- if (data.services) setServices(data.services);
283
- if (data.foundationStats) setFoundationStats(data.foundationStats);
284
- if (data.updateAvailable) setUpdateAvailable(data.updateAvailable);
285
- })
286
- .catch(() => {});
287
- };
288
- poll();
289
- const interval = setInterval(poll, 30000);
290
- return () => clearInterval(interval);
291
- }, []);
292
-
293
- // Stale-streaming failsafe: if isStreaming but no events for 90s, auto-reset
294
- useEffect(() => {
295
- const interval = setInterval(() => {
296
- if (lastStreamEventRef.current > 0) {
297
- const elapsed = Date.now() - lastStreamEventRef.current;
298
- if (elapsed > 90_000) {
299
- lastStreamEventRef.current = 0;
300
- setIsStreaming(false);
301
- setStreamingText("");
302
- setThinkingText("");
303
- setStatusText("");
304
- }
305
- }
306
- }, 5000);
307
- return () => clearInterval(interval);
308
- }, []);
309
-
310
- const send = useCallback((type, data = {}) => {
311
- if (wsRef.current?.readyState === WebSocket.OPEN) {
312
- wsRef.current.send(JSON.stringify({ type, ...data }));
313
- }
314
- }, []);
315
-
316
- const sendMessage = useCallback((text) => {
317
- if (activeSession) {
318
- setMessages((prev) => [...prev, { role: "user", content: text }]);
319
- send("send", { sessionId: activeSession, text });
320
- }
321
- }, [activeSession, send]);
322
-
323
- const createSession = useCallback((agentName) => {
324
- send("create_session", { agentName });
325
- }, [send]);
326
-
327
- const switchSession = useCallback((sessionId) => {
328
- send("switch", { sessionId });
329
- }, [send]);
330
-
331
- const closeSession = useCallback((sessionId) => {
332
- send("close_session", { sessionId });
333
- }, [send]);
334
-
335
- const cancelStream = useCallback(() => {
336
- if (activeSession) {
337
- send("cancel", { sessionId: activeSession });
338
- }
339
- // Always reset input-blocking state so the user can type again
340
- lastStreamEventRef.current = 0;
341
- setIsStreaming(false);
342
- setStreamingText("");
343
- setThinkingText("");
344
- setStatusText("");
345
- }, [activeSession, send]);
346
-
347
- const sendOllamaSetup = useCallback(() => {
348
- send("ollama_setup");
349
- }, [send]);
350
-
351
- const sendStackUp = useCallback(() => {
352
- send("stack_up");
353
- }, [send]);
354
-
355
- const sendStackDown = useCallback(() => {
356
- send("stack_down");
357
- }, [send]);
358
-
359
- const sendBootstrap = useCallback(() => {
360
- send("bootstrap");
361
- }, [send]);
362
-
363
- const sendTrain = useCallback(() => {
364
- send("train");
365
- }, [send]);
366
-
367
- const sendLearningEvent = useCallback((eventType, payload = {}, sessionId = null) => {
368
- if (!eventType) return;
369
- send("learning_event", { eventType, payload, sessionId });
370
- }, [send]);
371
-
372
- const respondConfirm = useCallback((action) => {
373
- send("tool_confirm_response", { action });
374
- setPendingConfirm(null);
375
- }, [send]);
376
-
377
- const appendMessage = useCallback((sessionId, role, content) => {
378
- if (!sessionId || typeof content !== "string" || !content.trim()) return;
379
- send("append_message", { sessionId, role, content });
380
- }, [send]);
381
-
382
- return {
383
- connected,
384
- sessions,
385
- activeSession,
386
- agents,
387
- messages,
388
- setMessages,
389
- streamingText,
390
- thinkingText,
391
- statusText,
392
- toolCalls,
393
- isStreaming,
394
- services,
395
- foundationStats,
396
- updateAvailable,
397
- pendingConfirm,
398
- respondConfirm,
399
- sendMessage,
400
- sendOllamaSetup,
401
- sendStackUp,
402
- sendStackDown,
403
- sendBootstrap,
404
- sendTrain,
405
- sendLearningEvent,
406
- appendMessage,
407
- createSession,
408
- switchSession,
409
- closeSession,
410
- cancelStream,
411
- };
412
- }
@@ -1,78 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- /* ===== Base ===== */
4
- body {
5
- margin: 0;
6
- padding: 0;
7
- font-family: "Sora", system-ui, -apple-system, sans-serif;
8
- background: #08080c;
9
- color: #e0dfe4;
10
- height: 100vh;
11
- height: 100dvh;
12
- overflow: hidden;
13
- -webkit-font-smoothing: antialiased;
14
- -moz-osx-font-smoothing: grayscale;
15
- }
16
-
17
- #root {
18
- height: 100vh;
19
- height: 100dvh;
20
- display: flex;
21
- flex-direction: column;
22
- }
23
-
24
- code, pre, .font-mono {
25
- font-family: "IBM Plex Mono", "JetBrains Mono", "Fira Code", monospace;
26
- }
27
-
28
- ::selection {
29
- background: rgba(249, 115, 22, 0.3);
30
- color: #e0dfe4;
31
- }
32
-
33
- /* ===== Scrollbar ===== */
34
- ::-webkit-scrollbar { width: 5px; }
35
- ::-webkit-scrollbar-track { background: transparent; }
36
- ::-webkit-scrollbar-thumb { background: #26263a; border-radius: 10px; }
37
- ::-webkit-scrollbar-thumb:hover { background: #3f3f56; }
38
-
39
- /* ===== Animations ===== */
40
- @keyframes pulse {
41
- 0%, 100% { opacity: 1; }
42
- 50% { opacity: 0.35; }
43
- }
44
- .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
45
-
46
- @keyframes fadeIn {
47
- from { opacity: 0; transform: translateY(6px); }
48
- to { opacity: 1; transform: translateY(0); }
49
- }
50
- .animate-fade-in { animation: fadeIn 0.25s ease-out; }
51
-
52
- @keyframes slideIn {
53
- from { opacity: 0; transform: translateX(-6px); }
54
- to { opacity: 1; transform: translateX(0); }
55
- }
56
- .animate-slide-in { animation: slideIn 0.2s ease-out; }
57
-
58
- @keyframes cursorBlink {
59
- 0%, 100% { opacity: 1; }
60
- 50% { opacity: 0; }
61
- }
62
- .animate-cursor { animation: cursorBlink 1s step-end infinite; }
63
-
64
- @keyframes glowPulse {
65
- 0%, 100% { box-shadow: 0 0 8px rgba(249, 115, 22, 0.15); }
66
- 50% { box-shadow: 0 0 16px rgba(249, 115, 22, 0.3); }
67
- }
68
-
69
- /* ===== Noise Overlay ===== */
70
- .noise::after {
71
- content: '';
72
- position: fixed;
73
- inset: 0;
74
- pointer-events: none;
75
- opacity: 0.02;
76
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
77
- z-index: 9999;
78
- }
@@ -1,6 +0,0 @@
1
- import React from "react";
2
- import { createRoot } from "react-dom/client";
3
- import App from "./App.jsx";
4
- import "./index.css";
5
-
6
- createRoot(document.getElementById("root")).render(<App />);
@@ -1,21 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
- import tailwindcss from "@tailwindcss/vite";
4
-
5
- export default defineConfig({
6
- plugins: [react(), tailwindcss()],
7
- root: "src/web/frontend",
8
- build: {
9
- outDir: "../dist",
10
- emptyOutDir: true,
11
- },
12
- server: {
13
- proxy: {
14
- "/api": "http://localhost:3099",
15
- "/ws": {
16
- target: "ws://localhost:3099",
17
- ws: true,
18
- },
19
- },
20
- },
21
- });