fluxy-bot 0.2.40 → 0.2.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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
6
6
  <title>Fluxy Chat</title>
7
- <script type="module" crossorigin src="/fluxy/assets/fluxy-Bg7VWQTH.js"></script>
7
+ <script type="module" crossorigin src="/fluxy/assets/fluxy-Cfl58Zg0.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/fluxy/assets/globals--DVlL_N5.js">
9
9
  <link rel="stylesheet" crossorigin href="/fluxy/assets/globals-DMXu7CFU.css">
10
10
  </head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
- import { ArrowLeft, MoreVertical, Trash2, Wand2, LogIn } from 'lucide-react';
3
+ import { ArrowLeft, MoreVertical, Trash2, Wand2 } from 'lucide-react';
4
4
  import { WsClient } from './src/lib/ws-client';
5
5
  import { useFluxyChat } from './src/hooks/useFluxyChat';
6
6
  import OnboardWizard from './OnboardWizard';
@@ -79,51 +79,18 @@ function LoginForm({ botName, onSuccess }: { botName: string; onSuccess: () => v
79
79
  );
80
80
  }
81
81
 
82
- function FluxyApp() {
82
+ /** Chat view — only mounted when authenticated or first-run */
83
+ function ChatView({ botName }: { botName: string }) {
83
84
  const clientRef = useRef<WsClient | null>(null);
84
85
  const [connected, setConnected] = useState(false);
85
- const [botName, setBotName] = useState('Fluxy');
86
86
  const [whisperEnabled, setWhisperEnabled] = useState(false);
87
87
  const [menuOpen, setMenuOpen] = useState(false);
88
88
  const [showWizard, setShowWizard] = useState(false);
89
89
  const [reloadTrigger, setReloadTrigger] = useState(0);
90
- const [authState, setAuthState] = useState<AuthState>('checking');
91
90
  const menuRef = useRef<HTMLDivElement>(null);
92
91
  const wasConnected = useRef(false);
93
92
 
94
- // Check auth on mount
95
- useEffect(() => {
96
- (async () => {
97
- try {
98
- // Check if authenticated
99
- const meRes = await fetch('/api/auth/me');
100
- if (meRes.ok) {
101
- const me = await meRes.json();
102
- if (me.authenticated) { setAuthState('authenticated'); return; }
103
- }
104
-
105
- // Not authenticated — check if credentials are configured
106
- const cfgRes = await fetch('/api/auth/configured');
107
- const cfg = await cfgRes.json();
108
- if (!cfg.configured) {
109
- setAuthState('first-run');
110
- // Auto-show onboard wizard on first run
111
- if (!cfg.onboardComplete) setShowWizard(true);
112
- } else {
113
- setAuthState('unauthenticated');
114
- }
115
- } catch {
116
- // Worker not ready — allow (onboarding scenario)
117
- setAuthState('first-run');
118
- setShowWizard(true);
119
- }
120
- })();
121
- }, []);
122
-
123
- // Connect WebSocket only when authenticated or first-run
124
93
  useEffect(() => {
125
- if (authState !== 'authenticated' && authState !== 'first-run') return;
126
-
127
94
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
128
95
  const host = location.host;
129
96
  const client = new WsClient(`${proto}//${host}/fluxy/ws`);
@@ -165,20 +132,17 @@ function FluxyApp() {
165
132
  client.disconnect();
166
133
  clientRef.current = null;
167
134
  };
168
- }, [authState]);
135
+ }, []);
169
136
 
170
- // Load settings
171
137
  useEffect(() => {
172
138
  fetch('/api/settings')
173
139
  .then((r) => r.json())
174
140
  .then((s) => {
175
- if (s.agent_name) setBotName(s.agent_name);
176
141
  if (s.whisper_enabled === 'true') setWhisperEnabled(true);
177
142
  })
178
143
  .catch(() => {});
179
144
  }, []);
180
145
 
181
- // Close menu on outside click
182
146
  useEffect(() => {
183
147
  if (!menuOpen) return;
184
148
  const handler = (e: MouseEvent) => {
@@ -191,10 +155,97 @@ function FluxyApp() {
191
155
  const { messages, streaming, streamBuffer, tools, sendMessage, stopStreaming, clearContext } =
192
156
  useFluxyChat(clientRef.current, reloadTrigger);
193
157
 
158
+ return (
159
+ <>
160
+ {/* Status + menu in header area */}
161
+ <div className="flex items-center gap-2 shrink-0" style={{ position: 'absolute', right: 12, top: 10 }}>
162
+ <div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />
163
+ <div className="relative" ref={menuRef}>
164
+ <button
165
+ onClick={() => setMenuOpen((v) => !v)}
166
+ className="flex items-center justify-center h-7 w-7 rounded-full text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
167
+ >
168
+ <MoreVertical className="h-4 w-4" />
169
+ </button>
170
+ {menuOpen && (
171
+ <div className="absolute right-0 top-full mt-1 min-w-[160px] rounded-md border border-border bg-popover py-1 shadow-lg z-50">
172
+ <button
173
+ onClick={() => { setShowWizard(true); setMenuOpen(false); }}
174
+ className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
175
+ >
176
+ <Wand2 className="h-4 w-4" />
177
+ Setup Wizard
178
+ </button>
179
+ <button
180
+ onClick={() => { clearContext(); setMenuOpen(false); }}
181
+ className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
182
+ >
183
+ <Trash2 className="h-4 w-4" />
184
+ Clear context
185
+ </button>
186
+ </div>
187
+ )}
188
+ </div>
189
+ </div>
190
+
191
+ <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
192
+ <MessageList messages={messages} streaming={streaming} streamBuffer={streamBuffer} tools={tools} />
193
+ <InputBar onSend={sendMessage} onStop={stopStreaming} streaming={streaming} whisperEnabled={whisperEnabled} />
194
+ </div>
195
+
196
+ {showWizard && (
197
+ <OnboardWizard
198
+ onComplete={() => {
199
+ setShowWizard(false);
200
+ fetch('/api/settings')
201
+ .then((r) => r.json())
202
+ .then((s) => {
203
+ if (s.whisper_enabled === 'true') setWhisperEnabled(true);
204
+ })
205
+ .catch(() => {});
206
+ window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
207
+ }}
208
+ />
209
+ )}
210
+ </>
211
+ );
212
+ }
213
+
214
+ function FluxyApp() {
215
+ const [botName, setBotName] = useState('Fluxy');
216
+ const [authState, setAuthState] = useState<AuthState>('checking');
217
+
218
+ // Check auth on mount
219
+ useEffect(() => {
220
+ (async () => {
221
+ try {
222
+ const meRes = await fetch('/api/auth/me');
223
+ if (meRes.ok) {
224
+ const me = await meRes.json();
225
+ if (me.authenticated) { setAuthState('authenticated'); return; }
226
+ }
227
+
228
+ const cfgRes = await fetch('/api/auth/configured');
229
+ const cfg = await cfgRes.json();
230
+ setAuthState(cfg.configured ? 'unauthenticated' : 'first-run');
231
+ } catch {
232
+ setAuthState('first-run');
233
+ }
234
+ })();
235
+ }, []);
236
+
237
+ // Load bot name
238
+ useEffect(() => {
239
+ fetch('/api/settings')
240
+ .then((r) => r.json())
241
+ .then((s) => { if (s.agent_name) setBotName(s.agent_name); })
242
+ .catch(() => {});
243
+ }, []);
244
+
194
245
  const showChat = authState === 'authenticated' || authState === 'first-run';
195
246
 
196
247
  return (
197
- <div className="flex flex-col h-dvh overflow-hidden">
248
+ <div className="flex flex-col h-dvh overflow-hidden relative">
198
249
  {/* Header */}
199
250
  <div className="flex items-center gap-3 px-4 py-3 border-b border-border shrink-0">
200
251
  <button
@@ -206,39 +257,10 @@ function FluxyApp() {
206
257
  </button>
207
258
  <img src="/fluxy.png" alt={botName} className="h-5 w-auto" />
208
259
  <span className="text-sm font-semibold">{botName}</span>
209
- {showChat && <div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />}
210
260
  <div className="flex-1" />
211
- {showChat && (
212
- <div className="relative" ref={menuRef}>
213
- <button
214
- onClick={() => setMenuOpen((v) => !v)}
215
- className="flex items-center justify-center h-7 w-7 rounded-full text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
216
- >
217
- <MoreVertical className="h-4 w-4" />
218
- </button>
219
- {menuOpen && (
220
- <div className="absolute right-0 top-full mt-1 min-w-[160px] rounded-md border border-border bg-popover py-1 shadow-lg z-50">
221
- <button
222
- onClick={() => { setShowWizard(true); setMenuOpen(false); }}
223
- className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
224
- >
225
- <Wand2 className="h-4 w-4" />
226
- Setup Wizard
227
- </button>
228
- <button
229
- onClick={() => { clearContext(); setMenuOpen(false); }}
230
- className="flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-white/[0.06] transition-colors"
231
- >
232
- <Trash2 className="h-4 w-4" />
233
- Clear context
234
- </button>
235
- </div>
236
- )}
237
- </div>
238
- )}
239
261
  </div>
240
262
 
241
- {/* Auth gate or chat */}
263
+ {/* Auth gate */}
242
264
  {authState === 'checking' && (
243
265
  <div className="flex-1 flex items-center justify-center">
244
266
  <div className="h-6 w-6 border-2 border-muted-foreground/30 border-t-foreground rounded-full animate-spin" />
@@ -246,36 +268,11 @@ function FluxyApp() {
246
268
  )}
247
269
 
248
270
  {authState === 'unauthenticated' && (
249
- <LoginForm
250
- botName={botName}
251
- onSuccess={() => setAuthState('authenticated')}
252
- />
271
+ <LoginForm botName={botName} onSuccess={() => setAuthState('authenticated')} />
253
272
  )}
254
273
 
255
- {showChat && (
256
- <>
257
- <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
258
- <MessageList messages={messages} streaming={streaming} streamBuffer={streamBuffer} tools={tools} />
259
- <InputBar onSend={sendMessage} onStop={stopStreaming} streaming={streaming} whisperEnabled={whisperEnabled} />
260
- </div>
261
-
262
- {showWizard && (
263
- <OnboardWizard
264
- onComplete={() => {
265
- setShowWizard(false);
266
- fetch('/api/settings')
267
- .then((r) => r.json())
268
- .then((s) => {
269
- if (s.agent_name) setBotName(s.agent_name);
270
- setWhisperEnabled(s.whisper_enabled === 'true');
271
- })
272
- .catch(() => {});
273
- window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
274
- }}
275
- />
276
- )}
277
- </>
278
- )}
274
+ {/* Chat — only mounted when auth is resolved, so useFluxyChat won't fire 401s */}
275
+ {showChat && <ChatView botName={botName} />}
279
276
  </div>
280
277
  );
281
278
  }
package/worker/index.ts CHANGED
@@ -328,7 +328,7 @@ app.post('/api/portal/verify-password', (req, res) => {
328
328
  res.json({ valid: verifyPassword(password, stored) });
329
329
  });
330
330
 
331
- app.post('/api/onboard', (req, res) => {
331
+ app.post('/api/onboard', async (req, res) => {
332
332
  const { userName, agentName, provider, model, apiKey, baseUrl, portalUser, portalPass, whisperEnabled, whisperKey } = req.body;
333
333
  setSetting('user_name', userName || '');
334
334
  setSetting('agent_name', agentName || 'Fluxy');
@@ -367,6 +367,13 @@ app.post('/api/onboard', (req, res) => {
367
367
 
368
368
  saveConfig(currentCfg);
369
369
 
370
+ // Auto-login: issue JWT cookie so the user is immediately authenticated after onboarding
371
+ if (portalUser) {
372
+ const token = await signToken({ sub: portalUser.trim().toLowerCase(), role: 'owner' });
373
+ const secure = req.headers['x-forwarded-proto'] === 'https' || req.protocol === 'https';
374
+ res.setHeader('Set-Cookie', buildSessionCookie(token, secure));
375
+ }
376
+
370
377
  res.json({ ok: true });
371
378
  });
372
379
 
@@ -20,11 +20,25 @@ export default function LandingPage() {
20
20
  });
21
21
  }, []);
22
22
 
23
+ // Hide/show bubble during onboarding
24
+ useEffect(() => {
25
+ const bubble = document.getElementById('fluxy-widget-bubble');
26
+ if (bubble) bubble.style.display = showOnboard ? 'none' : '';
27
+ }, [showOnboard]);
28
+
23
29
  // Listen for onboard complete from fluxy iframe
24
30
  useEffect(() => {
25
31
  const handler = (e: MessageEvent) => {
26
32
  if (e.data?.type === 'fluxy:onboard-complete') {
27
33
  setShowOnboard(false);
34
+ // Re-check auth — onboard auto-issues a JWT cookie
35
+ fetch('/api/auth/me').then((r) => r.json())
36
+ .then((me) => { if (me?.authenticated) setIsAuthed(true); })
37
+ .catch(() => {});
38
+ // Re-fetch bot name
39
+ fetch('/api/settings').then((r) => r.json())
40
+ .then((s) => { if (s?.agent_name) setBotName(s.agent_name); })
41
+ .catch(() => {});
28
42
  }
29
43
  };
30
44
  window.addEventListener('message', handler);