fluxy-bot 0.1.41 → 0.1.43
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 +2 -1
- package/client/src/components/BuildOverlay.tsx +17 -25
- package/client/src/components/Layout/DashboardLayout.tsx +1 -1
- package/client/src/hooks/useChat.ts +60 -48
- package/client/src/lib/ws-client.ts +1 -1
- package/dist/assets/{index-CQQ1WiY2.js → index-BAUWfBMW.js} +21 -21
- package/dist/assets/index-CiN0-4-O.css +1 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +3 -3
- package/shared/workspace.ts +1 -1
- package/supervisor/index.ts +60 -42
- package/supervisor/vite-dev.ts +71 -0
- package/supervisor/worker.ts +2 -1
- package/vite.config.ts +1 -4
- package/dist/assets/index-gfMKLDbJ.css +0 -1
package/client/src/App.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import DashboardPage from './components/Dashboard/DashboardPage';
|
|
|
7
7
|
import ChatView from './components/Chat/ChatView';
|
|
8
8
|
import FluxyFab from './components/FluxyFab';
|
|
9
9
|
import BuildOverlay from './components/BuildOverlay';
|
|
10
|
+
|
|
10
11
|
import OnboardWizard from './components/Onboard/OnboardWizard';
|
|
11
12
|
import {
|
|
12
13
|
Sheet,
|
|
@@ -71,6 +72,7 @@ export default function App() {
|
|
|
71
72
|
<>
|
|
72
73
|
<ErrorBoundary fallback={<DashboardError />}>
|
|
73
74
|
<DashboardLayout onOpenOnboard={() => setShowOnboard(true)}>
|
|
75
|
+
<BuildOverlay ws={ws} />
|
|
74
76
|
<DashboardPage />
|
|
75
77
|
</DashboardLayout>
|
|
76
78
|
</ErrorBoundary>
|
|
@@ -117,7 +119,6 @@ export default function App() {
|
|
|
117
119
|
</SheetContent>
|
|
118
120
|
</Sheet>
|
|
119
121
|
|
|
120
|
-
<BuildOverlay ws={ws} />
|
|
121
122
|
<FluxyFab onClick={() => setChatOpen((o) => !o)} />
|
|
122
123
|
|
|
123
124
|
{/* Onboarding wizard overlay */}
|
|
@@ -6,7 +6,7 @@ interface Props {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export default function BuildOverlay({ ws }: Props) {
|
|
9
|
-
const [
|
|
9
|
+
const [toast, setToast] = useState(false);
|
|
10
10
|
const [error, setError] = useState('');
|
|
11
11
|
const [copied, setCopied] = useState(false);
|
|
12
12
|
|
|
@@ -14,27 +14,18 @@ export default function BuildOverlay({ ws }: Props) {
|
|
|
14
14
|
if (!ws) return;
|
|
15
15
|
|
|
16
16
|
const unsubs = [
|
|
17
|
-
ws.on('
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setCopied(false);
|
|
21
|
-
}),
|
|
22
|
-
ws.on('build:complete', () => {
|
|
23
|
-
setState('idle');
|
|
24
|
-
// Auto-refresh after a brief delay so the user sees the transition
|
|
25
|
-
setTimeout(() => window.location.reload(), 500);
|
|
17
|
+
ws.on('changes:applied', () => {
|
|
18
|
+
setToast(true);
|
|
19
|
+
setTimeout(() => setToast(false), 3000);
|
|
26
20
|
}),
|
|
27
21
|
ws.on('build:error', (data: { error: string }) => {
|
|
28
|
-
|
|
29
|
-
setError(data.error || 'Unknown build error');
|
|
22
|
+
setError(data.error || 'Unknown error');
|
|
30
23
|
}),
|
|
31
24
|
];
|
|
32
25
|
|
|
33
26
|
return () => unsubs.forEach((u) => u());
|
|
34
27
|
}, [ws]);
|
|
35
28
|
|
|
36
|
-
if (state === 'idle') return null;
|
|
37
|
-
|
|
38
29
|
const handleCopy = () => {
|
|
39
30
|
navigator.clipboard.writeText(error).then(() => {
|
|
40
31
|
setCopied(true);
|
|
@@ -43,18 +34,19 @@ export default function BuildOverlay({ ws }: Props) {
|
|
|
43
34
|
};
|
|
44
35
|
|
|
45
36
|
return (
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
<>
|
|
38
|
+
{/* Toast notification */}
|
|
39
|
+
{toast && (
|
|
40
|
+
<div className="fixed bottom-4 right-4 z-50 px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg shadow-lg text-sm text-zinc-300 animate-in fade-in slide-in-from-bottom-2 duration-200">
|
|
41
|
+
Dashboard updated
|
|
51
42
|
</div>
|
|
52
43
|
)}
|
|
53
44
|
|
|
54
|
-
{
|
|
55
|
-
|
|
45
|
+
{/* HMR error — non-blocking, dismissible */}
|
|
46
|
+
{error && (
|
|
47
|
+
<div className="fixed bottom-4 right-4 z-50 w-96 max-w-[calc(100vw-2rem)] bg-zinc-900 rounded-lg border border-red-500/30 shadow-lg overflow-hidden">
|
|
56
48
|
<div className="flex items-center justify-between px-4 py-3 border-b border-red-500/20">
|
|
57
|
-
<span className="text-sm font-medium text-red-400">
|
|
49
|
+
<span className="text-sm font-medium text-red-400">HMR Error</span>
|
|
58
50
|
<div className="flex gap-2">
|
|
59
51
|
<button
|
|
60
52
|
onClick={handleCopy}
|
|
@@ -63,14 +55,14 @@ export default function BuildOverlay({ ws }: Props) {
|
|
|
63
55
|
{copied ? 'Copied' : 'Copy'}
|
|
64
56
|
</button>
|
|
65
57
|
<button
|
|
66
|
-
onClick={() =>
|
|
58
|
+
onClick={() => setError('')}
|
|
67
59
|
className="px-3 py-1 text-xs rounded bg-white/10 hover:bg-white/20 text-white/80 transition-colors"
|
|
68
60
|
>
|
|
69
61
|
Dismiss
|
|
70
62
|
</button>
|
|
71
63
|
</div>
|
|
72
64
|
</div>
|
|
73
|
-
<pre className="p-4 text-xs text-red-300/90 overflow-auto max-h-
|
|
65
|
+
<pre className="p-4 text-xs text-red-300/90 overflow-auto max-h-48 whitespace-pre-wrap font-mono">
|
|
74
66
|
{error}
|
|
75
67
|
</pre>
|
|
76
68
|
<p className="px-4 pb-3 text-xs text-white/40">
|
|
@@ -78,6 +70,6 @@ export default function BuildOverlay({ ws }: Props) {
|
|
|
78
70
|
</p>
|
|
79
71
|
</div>
|
|
80
72
|
)}
|
|
81
|
-
|
|
73
|
+
</>
|
|
82
74
|
);
|
|
83
75
|
}
|
|
@@ -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,56 +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
|
-
|
|
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; };
|
|
105
116
|
}, [conversationId]);
|
|
106
117
|
|
|
107
118
|
// Persist conversationId to DB when it changes
|
|
@@ -121,7 +132,8 @@ export function useChat(ws: WsClient | null) {
|
|
|
121
132
|
if (!ws) return;
|
|
122
133
|
|
|
123
134
|
const unsubs = [
|
|
124
|
-
ws.on('bot:typing', () => {
|
|
135
|
+
ws.on('bot:typing', (data: { conversationId?: string }) => {
|
|
136
|
+
if (data.conversationId) setConversationId(data.conversationId);
|
|
125
137
|
setStreaming(true);
|
|
126
138
|
setTools([]);
|
|
127
139
|
}),
|
|
@@ -20,7 +20,7 @@ export class WsClient {
|
|
|
20
20
|
|
|
21
21
|
constructor(url?: string) {
|
|
22
22
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
23
|
-
const host =
|
|
23
|
+
const host = location.host;
|
|
24
24
|
this.url = url ?? `${proto}//${host}/ws`;
|
|
25
25
|
}
|
|
26
26
|
|