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.
- 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 -99
- package/worker/index.ts +8 -1
- package/workspace/client/src/components/Landing/LandingPage.tsx +14 -0
- package/dist-fluxy/assets/fluxy-Bg7VWQTH.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,51 +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
|
-
// 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
|
-
}, [
|
|
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
|
|
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
|
-
{
|
|
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);
|