fluxy-bot 0.1.41 → 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.
@@ -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 */}
@@ -20,9 +20,13 @@ export default function BuildOverlay({ ws }: Props) {
20
20
  setCopied(false);
21
21
  }),
22
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);
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();
26
30
  }),
27
31
  ws.on('build:error', (data: { error: string }) => {
28
32
  setState('error');
@@ -43,7 +47,7 @@ export default function BuildOverlay({ ws }: Props) {
43
47
  };
44
48
 
45
49
  return (
46
- <div className="fixed inset-0 z-[100] bg-black/80 backdrop-blur-sm flex items-center justify-center p-6">
50
+ <div className="absolute inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-6">
47
51
  {state === 'building' && (
48
52
  <div className="text-center">
49
53
  <div className="inline-block h-8 w-8 animate-spin rounded-full border-2 border-white/20 border-t-white mb-4" />
@@ -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
- 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
- // Network error (worker may be restarting) — don't clear, just ignore
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