fluxy-bot 0.2.37 → 0.2.39
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-CLjt1lU-.js +37 -0
- package/dist-fluxy/assets/globals-DMXu7CFU.css +1 -0
- package/dist-fluxy/assets/{onboard-B_QyBFoO.js → onboard-CdR_xRWS.js} +1 -1
- package/dist-fluxy/fluxy.html +3 -3
- package/dist-fluxy/onboard.html +3 -3
- package/package.json +3 -1
- package/shared/auth.ts +55 -0
- package/shared/config.ts +1 -0
- package/supervisor/chat/fluxy-main.tsx +171 -53
- package/supervisor/index.ts +34 -3
- package/worker/index.ts +77 -0
- package/workspace/client/src/App.tsx +61 -4
- package/workspace/client/src/components/Auth/LoginPage.tsx +83 -0
- package/workspace/client/src/components/Landing/LandingPage.tsx +63 -0
- package/workspace/client/src/main.tsx +4 -1
- package/dist-fluxy/assets/fluxy-tSkoe-BG.js +0 -37
- package/dist-fluxy/assets/globals-CZf7hjQW.css +0 -1
- /package/dist-fluxy/assets/{globals-CnbiE5KJ.js → globals--DVlL_N5.js} +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
|
|
2
3
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
3
4
|
import DashboardLayout from './components/Layout/DashboardLayout';
|
|
4
5
|
import DashboardPage from './components/Dashboard/DashboardPage';
|
|
6
|
+
import LandingPage from './components/Landing/LandingPage';
|
|
7
|
+
import LoginPage from './components/Auth/LoginPage';
|
|
5
8
|
|
|
6
9
|
function DashboardError() {
|
|
7
10
|
return (
|
|
@@ -22,7 +25,46 @@ function DashboardError() {
|
|
|
22
25
|
);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
29
|
+
const [status, setStatus] = useState<'checking' | 'ok' | 'denied'>('checking');
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
// Check if credentials are configured at all
|
|
35
|
+
const cfgRes = await fetch('/api/auth/configured');
|
|
36
|
+
const cfg = await cfgRes.json();
|
|
37
|
+
if (!cfg.configured) {
|
|
38
|
+
// First run — allow access (onboarding hasn't happened yet)
|
|
39
|
+
setStatus('ok');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Credentials exist — verify session
|
|
44
|
+
const meRes = await fetch('/api/auth/me');
|
|
45
|
+
const me = await meRes.json();
|
|
46
|
+
setStatus(me.authenticated ? 'ok' : 'denied');
|
|
47
|
+
} catch {
|
|
48
|
+
// Worker not ready — allow (onboarding scenario)
|
|
49
|
+
setStatus('ok');
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
if (status === 'checking') {
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex items-center justify-center h-dvh bg-background">
|
|
57
|
+
<div className="h-8 w-8 border-2 border-muted-foreground/30 border-t-foreground rounded-full animate-spin" />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (status === 'denied') return <Navigate to="/login" replace />;
|
|
63
|
+
|
|
64
|
+
return <>{children}</>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Dashboard() {
|
|
26
68
|
const [showOnboard, setShowOnboard] = useState(false);
|
|
27
69
|
const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
|
|
28
70
|
const [buildError, setBuildError] = useState('');
|
|
@@ -36,14 +78,12 @@ export default function App() {
|
|
|
36
78
|
.catch(() => {});
|
|
37
79
|
}, []);
|
|
38
80
|
|
|
39
|
-
// Listen for rebuild events from Fluxy iframe via postMessage
|
|
40
81
|
useEffect(() => {
|
|
41
82
|
let safetyTimer: ReturnType<typeof setTimeout>;
|
|
42
83
|
const handler = (e: MessageEvent) => {
|
|
43
84
|
if (e.data?.type === 'fluxy:rebuilding') {
|
|
44
85
|
setRebuildState('rebuilding');
|
|
45
86
|
setBuildError('');
|
|
46
|
-
// Safety: auto-reload after 60s in case app:rebuilt message is lost
|
|
47
87
|
clearTimeout(safetyTimer);
|
|
48
88
|
safetyTimer = setTimeout(() => location.reload(), 60_000);
|
|
49
89
|
} else if (e.data?.type === 'fluxy:rebuilt') {
|
|
@@ -59,7 +99,6 @@ export default function App() {
|
|
|
59
99
|
setShowOnboard(false);
|
|
60
100
|
} else if (e.data?.type === 'fluxy:hmr-update') {
|
|
61
101
|
console.log('[dashboard] File changed — reloading...');
|
|
62
|
-
// Preserve widget open state so chat isn't disrupted
|
|
63
102
|
const panel = document.getElementById('fluxy-widget-panel');
|
|
64
103
|
if (panel?.classList.contains('open')) {
|
|
65
104
|
sessionStorage.setItem('fluxy_widget_open', '1');
|
|
@@ -107,3 +146,21 @@ export default function App() {
|
|
|
107
146
|
</>
|
|
108
147
|
);
|
|
109
148
|
}
|
|
149
|
+
|
|
150
|
+
export default function App() {
|
|
151
|
+
return (
|
|
152
|
+
<Routes>
|
|
153
|
+
<Route path="/" element={<LandingPage />} />
|
|
154
|
+
<Route path="/login" element={<LoginPage />} />
|
|
155
|
+
<Route
|
|
156
|
+
path="/dashboard"
|
|
157
|
+
element={
|
|
158
|
+
<ProtectedRoute>
|
|
159
|
+
<Dashboard />
|
|
160
|
+
</ProtectedRoute>
|
|
161
|
+
}
|
|
162
|
+
/>
|
|
163
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
164
|
+
</Routes>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
export default function LoginPage() {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
const [username, setUsername] = useState('');
|
|
7
|
+
const [password, setPassword] = useState('');
|
|
8
|
+
const [error, setError] = useState('');
|
|
9
|
+
const [loading, setLoading] = useState(false);
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
setError('');
|
|
14
|
+
setLoading(true);
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('/api/auth/login', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({ username, password }),
|
|
20
|
+
});
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
if (data.ok) {
|
|
23
|
+
navigate('/dashboard');
|
|
24
|
+
} else {
|
|
25
|
+
setError(data.error || 'Login failed');
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
setError('Connection error');
|
|
29
|
+
} finally {
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="min-h-dvh flex flex-col items-center justify-center bg-background px-6">
|
|
36
|
+
<div className="w-full max-w-sm">
|
|
37
|
+
<div className="flex flex-col items-center mb-8">
|
|
38
|
+
<img src="/fluxy.png" alt="Fluxy" className="h-12 w-auto mb-4" />
|
|
39
|
+
<h1 className="text-xl font-semibold">Welcome Back</h1>
|
|
40
|
+
<p className="text-sm text-muted-foreground mt-1">Sign in to access the dashboard</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
|
|
44
|
+
<input
|
|
45
|
+
type="text"
|
|
46
|
+
placeholder="Username"
|
|
47
|
+
value={username}
|
|
48
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
49
|
+
autoComplete="username"
|
|
50
|
+
autoFocus
|
|
51
|
+
className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
|
|
52
|
+
/>
|
|
53
|
+
<input
|
|
54
|
+
type="password"
|
|
55
|
+
placeholder="Password"
|
|
56
|
+
value={password}
|
|
57
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
58
|
+
autoComplete="current-password"
|
|
59
|
+
className="w-full bg-white/[0.05] border border-white/[0.08] rounded-xl px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground outline-none input-glow"
|
|
60
|
+
/>
|
|
61
|
+
{error && (
|
|
62
|
+
<div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2 text-xs text-red-400">
|
|
63
|
+
{error}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
<button
|
|
67
|
+
type="submit"
|
|
68
|
+
disabled={loading || !username || !password}
|
|
69
|
+
className="w-full bg-gradient-brand text-white font-medium rounded-full py-3 text-sm hover:opacity-90 transition-opacity disabled:opacity-50"
|
|
70
|
+
>
|
|
71
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
72
|
+
</button>
|
|
73
|
+
</form>
|
|
74
|
+
|
|
75
|
+
<div className="mt-6 text-center">
|
|
76
|
+
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
|
77
|
+
Back to home
|
|
78
|
+
</Link>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
export default function LandingPage() {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
const [botName, setBotName] = useState('Fluxy');
|
|
7
|
+
const [isAuthed, setIsAuthed] = useState(false);
|
|
8
|
+
const [checking, setChecking] = useState(true);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
Promise.all([
|
|
12
|
+
fetch('/api/settings').then((r) => r.json()).catch(() => null),
|
|
13
|
+
fetch('/api/auth/me').then((r) => r.json()).catch(() => null),
|
|
14
|
+
]).then(([settings, me]) => {
|
|
15
|
+
if (settings?.agent_name) setBotName(settings.agent_name);
|
|
16
|
+
if (me?.authenticated) setIsAuthed(true);
|
|
17
|
+
setChecking(false);
|
|
18
|
+
});
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="min-h-dvh flex flex-col bg-background">
|
|
23
|
+
{/* Header */}
|
|
24
|
+
<header className="flex items-center justify-between px-6 py-4">
|
|
25
|
+
<div className="flex items-center gap-2">
|
|
26
|
+
<img src="/fluxy.png" alt={botName} className="h-6 w-auto" />
|
|
27
|
+
<span className="text-sm font-semibold">{botName}</span>
|
|
28
|
+
</div>
|
|
29
|
+
{!checking && (
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => navigate(isAuthed ? '/dashboard' : '/login')}
|
|
32
|
+
className="text-sm font-medium px-4 py-2 rounded-full border border-white/[0.08] hover:bg-white/[0.06] transition-colors"
|
|
33
|
+
>
|
|
34
|
+
{isAuthed ? 'Dashboard' : 'Sign In'}
|
|
35
|
+
</button>
|
|
36
|
+
)}
|
|
37
|
+
</header>
|
|
38
|
+
|
|
39
|
+
{/* Hero */}
|
|
40
|
+
<main className="flex-1 flex flex-col items-center justify-center px-6 text-center">
|
|
41
|
+
<video
|
|
42
|
+
src="/fluxy_say_hi.webm"
|
|
43
|
+
autoPlay
|
|
44
|
+
loop
|
|
45
|
+
muted
|
|
46
|
+
playsInline
|
|
47
|
+
className="h-28 w-28 rounded-full object-cover mb-8"
|
|
48
|
+
/>
|
|
49
|
+
<h1 className="text-3xl sm:text-4xl font-bold mb-3">
|
|
50
|
+
<span className="text-gradient">{botName}</span>
|
|
51
|
+
</h1>
|
|
52
|
+
<p className="text-muted-foreground text-base max-w-md">
|
|
53
|
+
Your personal AI assistant, always ready to help.
|
|
54
|
+
</p>
|
|
55
|
+
</main>
|
|
56
|
+
|
|
57
|
+
{/* Footer */}
|
|
58
|
+
<footer className="py-4 text-center">
|
|
59
|
+
<p className="text-xs text-muted-foreground/60">Powered by Fluxy</p>
|
|
60
|
+
</footer>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
3
4
|
import App from './App';
|
|
4
5
|
import './styles/globals.css';
|
|
5
6
|
|
|
6
7
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
7
8
|
<React.StrictMode>
|
|
8
|
-
<
|
|
9
|
+
<BrowserRouter>
|
|
10
|
+
<App />
|
|
11
|
+
</BrowserRouter>
|
|
9
12
|
</React.StrictMode>,
|
|
10
13
|
);
|