fluxy-bot 0.2.41 → 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-CLjt1lU-.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.41",
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,48 +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
- } else {
111
- setAuthState('unauthenticated');
112
- }
113
- } catch {
114
- // Worker not ready — allow (onboarding scenario)
115
- setAuthState('first-run');
116
- }
117
- })();
118
- }, []);
119
-
120
- // Connect WebSocket only when authenticated or first-run
121
93
  useEffect(() => {
122
- if (authState !== 'authenticated' && authState !== 'first-run') return;
123
-
124
94
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
125
95
  const host = location.host;
126
96
  const client = new WsClient(`${proto}//${host}/fluxy/ws`);
@@ -162,20 +132,17 @@ function FluxyApp() {
162
132
  client.disconnect();
163
133
  clientRef.current = null;
164
134
  };
165
- }, [authState]);
135
+ }, []);
166
136
 
167
- // Load settings
168
137
  useEffect(() => {
169
138
  fetch('/api/settings')
170
139
  .then((r) => r.json())
171
140
  .then((s) => {
172
- if (s.agent_name) setBotName(s.agent_name);
173
141
  if (s.whisper_enabled === 'true') setWhisperEnabled(true);
174
142
  })
175
143
  .catch(() => {});
176
144
  }, []);
177
145
 
178
- // Close menu on outside click
179
146
  useEffect(() => {
180
147
  if (!menuOpen) return;
181
148
  const handler = (e: MouseEvent) => {
@@ -188,10 +155,97 @@ function FluxyApp() {
188
155
  const { messages, streaming, streamBuffer, tools, sendMessage, stopStreaming, clearContext } =
189
156
  useFluxyChat(clientRef.current, reloadTrigger);
190
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
+
191
245
  const showChat = authState === 'authenticated' || authState === 'first-run';
192
246
 
193
247
  return (
194
- <div className="flex flex-col h-dvh overflow-hidden">
248
+ <div className="flex flex-col h-dvh overflow-hidden relative">
195
249
  {/* Header */}
196
250
  <div className="flex items-center gap-3 px-4 py-3 border-b border-border shrink-0">
197
251
  <button
@@ -203,39 +257,10 @@ function FluxyApp() {
203
257
  </button>
204
258
  <img src="/fluxy.png" alt={botName} className="h-5 w-auto" />
205
259
  <span className="text-sm font-semibold">{botName}</span>
206
- {showChat && <div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />}
207
260
  <div className="flex-1" />
208
- {showChat && (
209
- <div className="relative" ref={menuRef}>
210
- <button
211
- onClick={() => setMenuOpen((v) => !v)}
212
- 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"
213
- >
214
- <MoreVertical className="h-4 w-4" />
215
- </button>
216
- {menuOpen && (
217
- <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">
218
- <button
219
- onClick={() => { setShowWizard(true); setMenuOpen(false); }}
220
- 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"
221
- >
222
- <Wand2 className="h-4 w-4" />
223
- Setup Wizard
224
- </button>
225
- <button
226
- onClick={() => { clearContext(); setMenuOpen(false); }}
227
- 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"
228
- >
229
- <Trash2 className="h-4 w-4" />
230
- Clear context
231
- </button>
232
- </div>
233
- )}
234
- </div>
235
- )}
236
261
  </div>
237
262
 
238
- {/* Auth gate or chat */}
263
+ {/* Auth gate */}
239
264
  {authState === 'checking' && (
240
265
  <div className="flex-1 flex items-center justify-center">
241
266
  <div className="h-6 w-6 border-2 border-muted-foreground/30 border-t-foreground rounded-full animate-spin" />
@@ -243,36 +268,11 @@ function FluxyApp() {
243
268
  )}
244
269
 
245
270
  {authState === 'unauthenticated' && (
246
- <LoginForm
247
- botName={botName}
248
- onSuccess={() => setAuthState('authenticated')}
249
- />
271
+ <LoginForm botName={botName} onSuccess={() => setAuthState('authenticated')} />
250
272
  )}
251
273
 
252
- {showChat && (
253
- <>
254
- <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
255
- <MessageList messages={messages} streaming={streaming} streamBuffer={streamBuffer} tools={tools} />
256
- <InputBar onSend={sendMessage} onStop={stopStreaming} streaming={streaming} whisperEnabled={whisperEnabled} />
257
- </div>
258
-
259
- {showWizard && (
260
- <OnboardWizard
261
- onComplete={() => {
262
- setShowWizard(false);
263
- fetch('/api/settings')
264
- .then((r) => r.json())
265
- .then((s) => {
266
- if (s.agent_name) setBotName(s.agent_name);
267
- setWhisperEnabled(s.whisper_enabled === 'true');
268
- })
269
- .catch(() => {});
270
- window.parent?.postMessage({ type: 'fluxy:onboard-complete' }, '*');
271
- }}
272
- />
273
- )}
274
- </>
275
- )}
274
+ {/* Chat — only mounted when auth is resolved, so useFluxyChat won't fire 401s */}
275
+ {showChat && <ChatView botName={botName} />}
276
276
  </div>
277
277
  );
278
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
 
@@ -31,6 +31,14 @@ export default function LandingPage() {
31
31
  const handler = (e: MessageEvent) => {
32
32
  if (e.data?.type === 'fluxy:onboard-complete') {
33
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(() => {});
34
42
  }
35
43
  };
36
44
  window.addEventListener('message', handler);