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.
@@ -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
- fetch(`/api/conversations/${conversationId}`)
59
- .then((r) => {
60
- if (!r.ok) throw new Error('not found');
61
- return r.json();
62
- })
63
- .then((data) => {
64
- if (data.messages?.length) {
65
- setMessages(
66
- data.messages
67
- .filter((m: any) => m.role === 'user' || m.role === 'assistant')
68
- .map((m: any) => {
69
- // Backward compat for audio_data: file path → URL, data: prefix → legacy, else → prepend data URL
70
- let audioData: string | undefined;
71
- if (m.audio_data) {
72
- if (m.audio_data.startsWith('data:')) {
73
- audioData = m.audio_data; // legacy data URL
74
- } else if (m.audio_data.includes('/')) {
75
- audioData = `/api/files/${m.audio_data}`; // file path → HTTP URL
76
- } else {
77
- audioData = `data:audio/webm;base64,${m.audio_data}`; // raw base64
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
- // Parse stored attachments
82
- let attachments: StoredAttachment[] | undefined;
83
- if (m.attachments) {
84
- try {
85
- attachments = JSON.parse(m.attachments);
86
- } catch { /* ignore malformed */ }
87
- }
88
-
89
- return {
90
- id: m.id,
91
- role: m.role,
92
- content: m.content,
93
- timestamp: m.created_at,
94
- audioData,
95
- hasAttachments: !!(attachments && attachments.length > 0),
96
- attachments,
97
- };
98
- }),
99
- );
100
- }
101
- })
102
- .catch(() => {
103
- // Conversation gone — clear
104
- setConversationId(null);
105
- fetch('/api/context/clear', { method: 'POST' }).catch(() => {});
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