fluxy-bot 0.1.40 → 0.1.42
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/client/src/App.tsx +3 -0
- package/client/src/components/BuildOverlay.tsx +87 -0
- package/client/src/components/Layout/DashboardLayout.tsx +1 -1
- package/client/src/hooks/useChat.ts +60 -50
- package/dist/assets/{index-Cx2_Nw7Z.js → index-Bu1DE8L8.js} +22 -22
- package/dist/assets/index-C6m9dj97.css +1 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/supervisor/claude-agent.ts +7 -0
- package/supervisor/index.ts +19 -5
- package/dist/assets/index-BLGh_Scz.css +0 -1
package/client/src/App.tsx
CHANGED
|
@@ -6,6 +6,8 @@ import DashboardLayout from './components/Layout/DashboardLayout';
|
|
|
6
6
|
import DashboardPage from './components/Dashboard/DashboardPage';
|
|
7
7
|
import ChatView from './components/Chat/ChatView';
|
|
8
8
|
import FluxyFab from './components/FluxyFab';
|
|
9
|
+
import BuildOverlay from './components/BuildOverlay';
|
|
10
|
+
|
|
9
11
|
import OnboardWizard from './components/Onboard/OnboardWizard';
|
|
10
12
|
import {
|
|
11
13
|
Sheet,
|
|
@@ -70,6 +72,7 @@ export default function App() {
|
|
|
70
72
|
<>
|
|
71
73
|
<ErrorBoundary fallback={<DashboardError />}>
|
|
72
74
|
<DashboardLayout onOpenOnboard={() => setShowOnboard(true)}>
|
|
75
|
+
<BuildOverlay ws={ws} />
|
|
73
76
|
<DashboardPage />
|
|
74
77
|
</DashboardLayout>
|
|
75
78
|
</ErrorBoundary>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import type { WsClient } from '../lib/ws-client';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ws: WsClient | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function BuildOverlay({ ws }: Props) {
|
|
9
|
+
const [state, setState] = useState<'idle' | 'building' | 'error'>('idle');
|
|
10
|
+
const [error, setError] = useState('');
|
|
11
|
+
const [copied, setCopied] = useState(false);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!ws) return;
|
|
15
|
+
|
|
16
|
+
const unsubs = [
|
|
17
|
+
ws.on('build:start', () => {
|
|
18
|
+
setState('building');
|
|
19
|
+
setError('');
|
|
20
|
+
setCopied(false);
|
|
21
|
+
}),
|
|
22
|
+
ws.on('build:complete', () => {
|
|
23
|
+
// Wait for API to be healthy before reloading (worker may be restarting)
|
|
24
|
+
const checkAndReload = () => {
|
|
25
|
+
fetch('/api/health')
|
|
26
|
+
.then((r) => { if (r.ok) window.location.reload(); else setTimeout(checkAndReload, 500); })
|
|
27
|
+
.catch(() => setTimeout(checkAndReload, 500));
|
|
28
|
+
};
|
|
29
|
+
checkAndReload();
|
|
30
|
+
}),
|
|
31
|
+
ws.on('build:error', (data: { error: string }) => {
|
|
32
|
+
setState('error');
|
|
33
|
+
setError(data.error || 'Unknown build error');
|
|
34
|
+
}),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
return () => unsubs.forEach((u) => u());
|
|
38
|
+
}, [ws]);
|
|
39
|
+
|
|
40
|
+
if (state === 'idle') return null;
|
|
41
|
+
|
|
42
|
+
const handleCopy = () => {
|
|
43
|
+
navigator.clipboard.writeText(error).then(() => {
|
|
44
|
+
setCopied(true);
|
|
45
|
+
setTimeout(() => setCopied(false), 2000);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="absolute inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-6">
|
|
51
|
+
{state === 'building' && (
|
|
52
|
+
<div className="text-center">
|
|
53
|
+
<div className="inline-block h-8 w-8 animate-spin rounded-full border-2 border-white/20 border-t-white mb-4" />
|
|
54
|
+
<p className="text-sm text-white/80">Building...</p>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{state === 'error' && (
|
|
59
|
+
<div className="w-full max-w-lg bg-zinc-900 rounded-lg border border-red-500/30 overflow-hidden">
|
|
60
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-red-500/20">
|
|
61
|
+
<span className="text-sm font-medium text-red-400">Build Failed</span>
|
|
62
|
+
<div className="flex gap-2">
|
|
63
|
+
<button
|
|
64
|
+
onClick={handleCopy}
|
|
65
|
+
className="px-3 py-1 text-xs rounded bg-white/10 hover:bg-white/20 text-white/80 transition-colors"
|
|
66
|
+
>
|
|
67
|
+
{copied ? 'Copied' : 'Copy'}
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
onClick={() => setState('idle')}
|
|
71
|
+
className="px-3 py-1 text-xs rounded bg-white/10 hover:bg-white/20 text-white/80 transition-colors"
|
|
72
|
+
>
|
|
73
|
+
Dismiss
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<pre className="p-4 text-xs text-red-300/90 overflow-auto max-h-64 whitespace-pre-wrap font-mono">
|
|
78
|
+
{error}
|
|
79
|
+
</pre>
|
|
80
|
+
<p className="px-4 pb-3 text-xs text-white/40">
|
|
81
|
+
Copy this error and paste it in the chat to fix
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -17,7 +17,7 @@ export default function DashboardLayout({ children, onOpenOnboard }: Props) {
|
|
|
17
17
|
<Sidebar />
|
|
18
18
|
</div>
|
|
19
19
|
{/* Main content */}
|
|
20
|
-
<main className="flex-1 overflow-y-auto p-4 md:p-6">{children}</main>
|
|
20
|
+
<main className="relative flex-1 overflow-y-auto p-4 md:p-6">{children}</main>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
23
|
);
|
|
@@ -52,58 +52,67 @@ export function useChat(ws: WsClient | null) {
|
|
|
52
52
|
.catch(() => {});
|
|
53
53
|
}, []);
|
|
54
54
|
|
|
55
|
-
// Load messages when conversationId is set
|
|
55
|
+
// Load messages when conversationId is set (with retry for worker restarts)
|
|
56
56
|
useEffect(() => {
|
|
57
57
|
if (!conversationId) return;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
let retries = 0;
|
|
60
|
+
|
|
61
|
+
const loadMessages = () => {
|
|
62
|
+
fetch(`/api/conversations/${conversationId}`)
|
|
63
|
+
.then((r) => {
|
|
64
|
+
if (!r.ok) throw new Error('not found');
|
|
65
|
+
return r.json();
|
|
66
|
+
})
|
|
67
|
+
.then((data) => {
|
|
68
|
+
if (cancelled) return;
|
|
69
|
+
if (data.messages?.length) {
|
|
70
|
+
setMessages(
|
|
71
|
+
data.messages
|
|
72
|
+
.filter((m: any) => m.role === 'user' || m.role === 'assistant')
|
|
73
|
+
.map((m: any) => {
|
|
74
|
+
let audioData: string | undefined;
|
|
75
|
+
if (m.audio_data) {
|
|
76
|
+
if (m.audio_data.startsWith('data:')) {
|
|
77
|
+
audioData = m.audio_data;
|
|
78
|
+
} else if (m.audio_data.includes('/')) {
|
|
79
|
+
audioData = `/api/files/${m.audio_data}`;
|
|
80
|
+
} else {
|
|
81
|
+
audioData = `data:audio/webm;base64,${m.audio_data}`;
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
84
|
+
|
|
85
|
+
let attachments: StoredAttachment[] | undefined;
|
|
86
|
+
if (m.attachments) {
|
|
87
|
+
try {
|
|
88
|
+
attachments = JSON.parse(m.attachments);
|
|
89
|
+
} catch { /* ignore malformed */ }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: m.id,
|
|
94
|
+
role: m.role,
|
|
95
|
+
content: m.content,
|
|
96
|
+
timestamp: m.created_at,
|
|
97
|
+
audioData,
|
|
98
|
+
hasAttachments: !!(attachments && attachments.length > 0),
|
|
99
|
+
attachments,
|
|
100
|
+
};
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.catch(() => {
|
|
106
|
+
// Worker may be restarting — retry a few times
|
|
107
|
+
if (!cancelled && retries < 5) {
|
|
108
|
+
retries++;
|
|
109
|
+
setTimeout(loadMessages, 1000);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
loadMessages();
|
|
115
|
+
return () => { cancelled = true; };
|
|
107
116
|
}, [conversationId]);
|
|
108
117
|
|
|
109
118
|
// Persist conversationId to DB when it changes
|
|
@@ -223,13 +232,14 @@ export function useChat(ws: WsClient | null) {
|
|
|
223
232
|
}, [ws, conversationId]);
|
|
224
233
|
|
|
225
234
|
const clearContext = useCallback(() => {
|
|
235
|
+
// Clear UI state only — starts a fresh conversation on next message.
|
|
236
|
+
// Old messages stay in DB (used as context for the agent).
|
|
226
237
|
setMessages([]);
|
|
227
238
|
setConversationId(null);
|
|
228
239
|
setStreamBuffer('');
|
|
229
240
|
setStreaming(false);
|
|
230
241
|
setTools([]);
|
|
231
242
|
prevConvId.current = null;
|
|
232
|
-
loaded.current = false;
|
|
233
243
|
fetch('/api/context/clear', { method: 'POST' }).catch(() => {});
|
|
234
244
|
}, []);
|
|
235
245
|
|