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.
- package/dist-fluxy/assets/fluxy-Cfl58Zg0.js +37 -0
- package/dist-fluxy/fluxy.html +1 -1
- package/package.json +1 -1
- package/supervisor/chat/fluxy-main.tsx +96 -96
- package/worker/index.ts +8 -1
- package/workspace/client/src/components/Landing/LandingPage.tsx +8 -0
- package/dist-fluxy/assets/fluxy-CLjt1lU-.js +0 -37
package/dist-fluxy/fluxy.html
CHANGED
|
@@ -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-
|
|
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
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom/client';
|
|
3
|
-
import { ArrowLeft, MoreVertical, Trash2, Wand2
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
{
|
|
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);
|