Spaces:
Running
Running
| import { useState } from "react"; | |
| import { BrainCircuit, FileSearch, Gauge, Github, Sparkles, Wrench } from "lucide-react"; | |
| import { api, setTokens } from "../lib/api.js"; | |
| export default function AuthPage({ onAuthed, appInfo }) { | |
| const [mode, setMode] = useState("login"); | |
| const [email, setEmail] = useState(""); | |
| const [username, setUsername] = useState(""); | |
| const [password, setPassword] = useState(""); | |
| const [error, setError] = useState(""); | |
| const [busy, setBusy] = useState(false); | |
| const [demoBusy, setDemoBusy] = useState(false); | |
| const isDemo = Boolean(appInfo?.demo_mode); | |
| async function startDemo() { | |
| setError(""); | |
| setDemoBusy(true); | |
| try { | |
| const response = await fetch("/api/auth/demo", { method: "POST" }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error( | |
| typeof data.detail === "string" | |
| ? data.detail | |
| : "Could not start the demo. Please try again." | |
| ); | |
| } | |
| setTokens(data); | |
| onAuthed(await api("/api/auth/me")); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setDemoBusy(false); | |
| } | |
| } | |
| async function submit(event) { | |
| event.preventDefault(); | |
| setError(""); | |
| setBusy(true); | |
| try { | |
| const path = mode === "login" ? "/api/auth/login" : "/api/auth/register"; | |
| const body = | |
| mode === "login" ? { email, password } : { email, username, password }; | |
| const response = await fetch(path, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(body), | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error( | |
| typeof data.detail === "string" | |
| ? data.detail | |
| : "Please check your details and try again." | |
| ); | |
| } | |
| setTokens(data); | |
| onAuthed(await api("/api/auth/me")); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setBusy(false); | |
| } | |
| } | |
| return ( | |
| <div className="auth-page"> | |
| <div className="auth-hero"> | |
| <div className="brand" style={{ padding: 0 }}> | |
| <div className="brand-logo"> | |
| <BrainCircuit size={18} /> | |
| </div> | |
| Synapse | |
| </div> | |
| <h1> | |
| Your agentic AI assistant, | |
| <br /> | |
| with real superpowers. | |
| </h1> | |
| <p> | |
| Streaming chat backed by an autonomous tool-calling agent, hybrid | |
| retrieval over your documents, and full cost observability. | |
| </p> | |
| <div className="hero-feature"> | |
| <div className="bullet"><Wrench size={16} /></div> | |
| <div> | |
| <b>Agentic tool use</b> | |
| <span>Web search, weather, calculator and document retrieval, chosen autonomously.</span> | |
| </div> | |
| </div> | |
| <div className="hero-feature"> | |
| <div className="bullet"><FileSearch size={16} /></div> | |
| <div> | |
| <b>Hybrid RAG with citations</b> | |
| <span>BM25 + dense vectors fused with RRF, reranked, and cited inline.</span> | |
| </div> | |
| </div> | |
| <div className="hero-feature"> | |
| <div className="bullet"><Gauge size={16} /></div> | |
| <div> | |
| <b>Built-in observability</b> | |
| <span>Token, cost and latency analytics for every conversation.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="auth-form-side"> | |
| <form className="auth-card fade-in" onSubmit={submit}> | |
| {isDemo ? ( | |
| <> | |
| <h2>Try the live demo</h2> | |
| <p className="sub"> | |
| One click, no signup. You get a private sandbox preloaded with a | |
| sample report so document retrieval works immediately. | |
| </p> | |
| {error && <div className="auth-error">{error}</div>} | |
| <button | |
| type="button" | |
| className="btn primary demo-cta" | |
| onClick={startDemo} | |
| disabled={demoBusy} | |
| > | |
| <Sparkles size={17} /> | |
| {demoBusy ? "Preparing your sandbox..." : "Launch demo"} | |
| </button> | |
| <ul className="demo-points"> | |
| <li>Ask about the weather or search the web, the agent picks its own tools</li> | |
| <li>Ask about the preloaded report to see hybrid RAG with citations</li> | |
| <li>Upload your own PDF, or open Analytics for live token and cost metrics</li> | |
| </ul> | |
| {appInfo?.repo_url && ( | |
| <a className="repo-link" href={appInfo.repo_url} target="_blank" rel="noreferrer"> | |
| <Github size={15} /> View the source on GitHub | |
| </a> | |
| )} | |
| <div className="auth-divider"><span>or use an account</span></div> | |
| </> | |
| ) : ( | |
| <> | |
| <h2>{mode === "login" ? "Welcome back" : "Create your account"}</h2> | |
| <p className="sub"> | |
| {mode === "login" | |
| ? "Sign in to continue your conversations." | |
| : "A minute from now you will be chatting with Synapse."} | |
| </p> | |
| {error && <div className="auth-error">{error}</div>} | |
| </> | |
| )} | |
| {mode === "register" && ( | |
| <div className="field"> | |
| <label htmlFor="username">Name</label> | |
| <input | |
| id="username" | |
| value={username} | |
| onChange={(e) => setUsername(e.target.value)} | |
| placeholder="What should we call you?" | |
| required | |
| minLength={2} | |
| /> | |
| </div> | |
| )} | |
| <div className="field"> | |
| <label htmlFor="email">Email</label> | |
| <input | |
| id="email" | |
| type="email" | |
| value={email} | |
| onChange={(e) => setEmail(e.target.value)} | |
| placeholder="you@example.com" | |
| required | |
| /> | |
| </div> | |
| <div className="field"> | |
| <label htmlFor="password">Password</label> | |
| <input | |
| id="password" | |
| type="password" | |
| value={password} | |
| onChange={(e) => setPassword(e.target.value)} | |
| placeholder={mode === "register" ? "At least 8 characters" : "Your password"} | |
| required | |
| minLength={8} | |
| /> | |
| </div> | |
| <button className="btn primary" style={{ width: "100%" }} disabled={busy}> | |
| {busy ? "Please wait..." : mode === "login" ? "Sign in" : "Create account"} | |
| </button> | |
| <div className="auth-switch"> | |
| {mode === "login" ? "New to Synapse?" : "Already have an account?"}{" "} | |
| <button | |
| type="button" | |
| onClick={() => { | |
| setMode(mode === "login" ? "register" : "login"); | |
| setError(""); | |
| }} | |
| > | |
| {mode === "login" ? "Create an account" : "Sign in"} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } | |