Your Name commited on
Commit
d7c622e
·
1 Parent(s): 5e99860

Deploy AutoForge to Hugging Face Space

Browse files
backend/api/auth.py CHANGED
@@ -45,6 +45,26 @@ async def login(
45
  return TokenResponse(access_token=token)
46
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  @router.get("/me", response_model=UserResponse)
49
  async def me(current_user: User = Depends(get_current_user)) -> User:
50
  return current_user
 
45
  return TokenResponse(access_token=token)
46
 
47
 
48
+ @router.post("/guest", response_model=TokenResponse)
49
+ async def guest_login(
50
+ repo: WorkflowRepository = Depends(get_repository),
51
+ ) -> TokenResponse:
52
+ import uuid
53
+ guest_id = str(uuid.uuid4())
54
+ guest_email = f"guest-{guest_id}@autoforge.local"
55
+ guest_username = f"Guest_{guest_id[:8]}"
56
+
57
+ # Auto-create the guest user with a random password
58
+ user = await repo.create_user(
59
+ email=guest_email,
60
+ username=guest_username,
61
+ password_hash=hash_password(guest_id),
62
+ )
63
+
64
+ token = create_access_token(str(user.id))
65
+ return TokenResponse(access_token=token)
66
+
67
+
68
  @router.get("/me", response_model=UserResponse)
69
  async def me(current_user: User = Depends(get_current_user)) -> User:
70
  return current_user
frontend/src/App.tsx CHANGED
@@ -1,17 +1,14 @@
1
  import { Navigate, Route, Routes } from "react-router-dom";
2
  import { AuthProvider, useAuth } from "./context/AuthContext";
3
  import Layout from "./components/Layout";
4
- import LoginPage from "./pages/LoginPage";
5
- import SignupPage from "./pages/SignupPage";
6
  import DashboardPage from "./pages/DashboardPage";
7
- import NewWorkflowPage from "./pages/NewWorkflowPage";
8
  import WorkflowPage from "./pages/WorkflowPage";
9
  import SettingsPage from "./pages/SettingsPage";
10
 
11
- function PrivateRoute({ children }: { children: React.ReactNode }) {
12
  const { user, loading } = useAuth();
13
- if (loading) return <p>Loading…</p>;
14
- if (!user) return <Navigate to="/login" replace />;
15
  return <>{children}</>;
16
  }
17
 
@@ -19,19 +16,16 @@ export default function App() {
19
  return (
20
  <AuthProvider>
21
  <Routes>
22
- <Route path="/login" element={<LoginPage />} />
23
- <Route path="/signup" element={<SignupPage />} />
24
  <Route
25
  path="/"
26
  element={
27
- <PrivateRoute>
28
  <Layout />
29
- </PrivateRoute>
30
  }
31
  >
32
  <Route index element={<Navigate to="/dashboard" replace />} />
33
  <Route path="dashboard" element={<DashboardPage />} />
34
- <Route path="workflows/new" element={<NewWorkflowPage />} />
35
  <Route path="workflows/:id" element={<WorkflowPage />} />
36
  <Route path="settings" element={<SettingsPage />} />
37
  </Route>
 
1
  import { Navigate, Route, Routes } from "react-router-dom";
2
  import { AuthProvider, useAuth } from "./context/AuthContext";
3
  import Layout from "./components/Layout";
 
 
4
  import DashboardPage from "./pages/DashboardPage";
 
5
  import WorkflowPage from "./pages/WorkflowPage";
6
  import SettingsPage from "./pages/SettingsPage";
7
 
8
+ function AuthWrapper({ children }: { children: React.ReactNode }) {
9
  const { user, loading } = useAuth();
10
+ if (loading) return <div className="loading-fullscreen"><div className="spinner spinner-lg"></div><p>Initializing...</p></div>;
11
+ if (!user) return <p>Failed to initialize guest session.</p>;
12
  return <>{children}</>;
13
  }
14
 
 
16
  return (
17
  <AuthProvider>
18
  <Routes>
 
 
19
  <Route
20
  path="/"
21
  element={
22
+ <AuthWrapper>
23
  <Layout />
24
+ </AuthWrapper>
25
  }
26
  >
27
  <Route index element={<Navigate to="/dashboard" replace />} />
28
  <Route path="dashboard" element={<DashboardPage />} />
 
29
  <Route path="workflows/:id" element={<WorkflowPage />} />
30
  <Route path="settings" element={<SettingsPage />} />
31
  </Route>
frontend/src/api/client.ts CHANGED
@@ -55,6 +55,10 @@ export const api = {
55
  method: "POST",
56
  body: JSON.stringify({ email, password }),
57
  }),
 
 
 
 
58
  me: () => request<User>("/auth/me"),
59
  listProviders: () =>
60
  request<{ providers: ProviderInfo[] }>("/settings/providers"),
 
55
  method: "POST",
56
  body: JSON.stringify({ email, password }),
57
  }),
58
+ guestLogin: () =>
59
+ request<{ access_token: string }>("/auth/guest", {
60
+ method: "POST",
61
+ }),
62
  me: () => request<User>("/auth/me"),
63
  listProviders: () =>
64
  request<{ providers: ProviderInfo[] }>("/settings/providers"),
frontend/src/components/WorkflowTimeline.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import type { WorkflowEvent } from "../types";
2
 
3
  const EVENT_ICONS: Record<string, string> = {
@@ -26,19 +27,69 @@ const EVENT_ICONS: Record<string, string> = {
26
  };
27
 
28
  export default function WorkflowTimeline({ events }: { events: WorkflowEvent[] }) {
 
 
 
 
 
 
 
 
29
  return (
30
- <ul className="timeline">
31
- {events.map((event) => (
32
- <li key={event.id}>
33
- <span className="icon">{EVENT_ICONS[event.event_type] || "•"}</span>
34
- <div>
35
- <strong>{event.event_type.replace(/_/g, " ")}</strong>
36
- <div>{event.message}</div>
37
- <div className="time">{new Date(event.timestamp).toLocaleString()}</div>
38
- </div>
39
- </li>
40
- ))}
41
- {events.length === 0 && <li>No events yet. Start the workflow to see live updates.</li>}
42
- </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  );
44
  }
 
1
+ import { useState, useEffect } from "react";
2
  import type { WorkflowEvent } from "../types";
3
 
4
  const EVENT_ICONS: Record<string, string> = {
 
27
  };
28
 
29
  export default function WorkflowTimeline({ events }: { events: WorkflowEvent[] }) {
30
+ const [isOpen, setIsOpen] = useState(true);
31
+
32
+ const isComplete = events.some(e => e.event_type === "WORKFLOW_COMPLETED" || e.event_type === "WORKFLOW_FAILED");
33
+
34
+ useEffect(() => {
35
+ if (isComplete) setIsOpen(false);
36
+ }, [isComplete]);
37
+
38
  return (
39
+ <details
40
+ open={isOpen}
41
+ onToggle={(e) => setIsOpen(e.currentTarget.open)}
42
+ style={{
43
+ background: "var(--bg-elevated)",
44
+ borderRadius: "var(--radius-md)",
45
+ border: "1px solid var(--border-subtle)",
46
+ marginBottom: "2rem",
47
+ overflow: "hidden"
48
+ }}
49
+ >
50
+ <summary style={{
51
+ padding: "1rem 1.5rem",
52
+ cursor: "pointer",
53
+ fontWeight: 600,
54
+ display: "flex",
55
+ alignItems: "center",
56
+ gap: "0.5rem",
57
+ listStyle: "none",
58
+ borderBottom: isOpen ? "1px solid var(--border-subtle)" : "none",
59
+ userSelect: "none"
60
+ }}>
61
+ <span style={{ transition: "transform 0.2s", transform: isOpen ? "rotate(90deg)" : "rotate(0deg)" }}>▶</span>
62
+ {isComplete ? "Agent finished thinking" : "Agent is thinking..."}
63
+ </summary>
64
+
65
+ <div style={{ padding: "1.5rem", maxHeight: "400px", overflowY: "auto" }}>
66
+ <ul className="timeline">
67
+ {events.map((event) => (
68
+ <li key={event.id} style={{ marginBottom: "1rem", display: "flex", gap: "1rem", alignItems: "flex-start" }}>
69
+ <span className="icon" style={{
70
+ background: "var(--bg-surface)",
71
+ width: "32px", height: "32px",
72
+ display: "flex", alignItems: "center", justifyContent: "center",
73
+ borderRadius: "50%",
74
+ border: "1px solid var(--border-subtle)",
75
+ flexShrink: 0
76
+ }}>
77
+ {EVENT_ICONS[event.event_type] || "•"}
78
+ </span>
79
+ <div>
80
+ <strong style={{ display: "block", color: "var(--text-primary)", fontSize: "0.9rem" }}>
81
+ {event.event_type.replace(/_/g, " ")}
82
+ </strong>
83
+ <div style={{ color: "var(--text-secondary)", fontSize: "0.85rem", marginTop: "0.2rem" }}>{event.message}</div>
84
+ <div className="time" style={{ color: "var(--text-muted)", fontSize: "0.75rem", marginTop: "0.2rem" }}>
85
+ {new Date(event.timestamp).toLocaleTimeString()}
86
+ </div>
87
+ </div>
88
+ </li>
89
+ ))}
90
+ {events.length === 0 && <li>Initializing workflow engine...</li>}
91
+ </ul>
92
+ </div>
93
+ </details>
94
  );
95
  }
frontend/src/context/AuthContext.tsx CHANGED
@@ -5,9 +5,6 @@ import type { User } from "../types";
5
  interface AuthContextValue {
6
  user: User | null;
7
  loading: boolean;
8
- login: (email: string, password: string) => Promise<void>;
9
- signup: (email: string, username: string, password: string) => Promise<void>;
10
- logout: () => void;
11
  }
12
 
13
  const AuthContext = createContext<AuthContextValue | null>(null);
@@ -17,34 +14,25 @@ export function AuthProvider({ children }: { children: ReactNode }) {
17
  const [loading, setLoading] = useState(true);
18
 
19
  useEffect(() => {
20
- if (!getToken()) {
21
- setLoading(false);
22
- return;
23
- }
24
- api.me()
25
- .then(setUser)
26
- .catch(() => setToken(null))
27
- .finally(() => setLoading(false));
 
 
 
 
 
 
 
28
  }, []);
29
 
30
- const login = async (email: string, password: string) => {
31
- const { access_token } = await api.login(email, password);
32
- setToken(access_token);
33
- setUser(await api.me());
34
- };
35
-
36
- const signup = async (email: string, username: string, password: string) => {
37
- await api.signup(email, username, password);
38
- await login(email, password);
39
- };
40
-
41
- const logout = () => {
42
- setToken(null);
43
- setUser(null);
44
- };
45
-
46
  return (
47
- <AuthContext.Provider value={{ user, loading, login, signup, logout }}>
48
  {children}
49
  </AuthContext.Provider>
50
  );
 
5
  interface AuthContextValue {
6
  user: User | null;
7
  loading: boolean;
 
 
 
8
  }
9
 
10
  const AuthContext = createContext<AuthContextValue | null>(null);
 
14
  const [loading, setLoading] = useState(true);
15
 
16
  useEffect(() => {
17
+ const initializeAuth = async () => {
18
+ try {
19
+ if (!getToken()) {
20
+ const { access_token } = await api.guestLogin();
21
+ setToken(access_token);
22
+ }
23
+ setUser(await api.me());
24
+ } catch (err) {
25
+ console.error("Failed to authenticate guest", err);
26
+ setToken(null);
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ };
31
+ initializeAuth();
32
  }, []);
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return (
35
+ <AuthContext.Provider value={{ user, loading }}>
36
  {children}
37
  </AuthContext.Provider>
38
  );
frontend/src/index.css CHANGED
@@ -3,34 +3,34 @@
3
  ============================================================ */
4
 
5
  :root {
6
- /* Color palette */
7
- --bg-base: #0b0e14;
8
- --bg-surface: #111827;
9
- --bg-elevated: #1a2235;
10
- --bg-sidebar: #0d1117;
11
- --bg-input: #0f1723;
12
-
13
- --border-subtle: #1e2d42;
14
- --border-default: #253347;
15
- --border-strong: #3b5270;
16
-
17
- --text-primary: #e2eaf5;
18
- --text-secondary: #8aa0bd;
19
- --text-muted: #56718e;
20
- --text-inverse: #0b0e14;
21
-
22
- --accent-primary: #3b82f6;
23
- --accent-primary-hover: #2563eb;
24
- --accent-primary-glow: rgba(59, 130, 246, 0.18);
25
-
26
- --accent-success: #10b981;
27
- --accent-success-bg: rgba(16, 185, 129, 0.12);
28
- --accent-warning: #f59e0b;
29
- --accent-warning-bg: rgba(245, 158, 11, 0.12);
30
- --accent-danger: #ef4444;
31
- --accent-danger-bg: rgba(239, 68, 68, 0.12);
32
- --accent-info: #6366f1;
33
- --accent-info-bg: rgba(99, 102, 241, 0.12);
34
 
35
  --sidebar-width: 248px;
36
  --radius-sm: 6px;
@@ -38,10 +38,10 @@
38
  --radius-lg: 14px;
39
  --radius-xl: 20px;
40
 
41
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
42
- --shadow-md: 0 4px 16px rgba(0,0,0,0.4);
43
- --shadow-lg: 0 12px 40px rgba(0,0,0,0.5);
44
- --shadow-glow: 0 0 24px var(--accent-primary-glow);
45
 
46
  --transition-fast: 120ms ease;
47
  --transition-base: 200ms ease;
 
3
  ============================================================ */
4
 
5
  :root {
6
+ /* Color palette - Light Theme */
7
+ --bg-base: #f9fafb;
8
+ --bg-surface: #ffffff;
9
+ --bg-elevated: #f3f4f6;
10
+ --bg-sidebar: #ffffff;
11
+ --bg-input: #ffffff;
12
+
13
+ --border-subtle: #e5e7eb;
14
+ --border-default: #d1d5db;
15
+ --border-strong: #9ca3af;
16
+
17
+ --text-primary: #111827;
18
+ --text-secondary: #374151;
19
+ --text-muted: #6b7280;
20
+ --text-inverse: #ffffff;
21
+
22
+ --accent-primary: #000000;
23
+ --accent-primary-hover: #374151;
24
+ --accent-primary-glow: rgba(0, 0, 0, 0.08);
25
+
26
+ --accent-success: #059669;
27
+ --accent-success-bg: rgba(5, 150, 105, 0.12);
28
+ --accent-warning: #d97706;
29
+ --accent-warning-bg: rgba(217, 119, 6, 0.12);
30
+ --accent-danger: #dc2626;
31
+ --accent-danger-bg: rgba(220, 38, 38, 0.12);
32
+ --accent-info: #4f46e5;
33
+ --accent-info-bg: rgba(79, 70, 229, 0.12);
34
 
35
  --sidebar-width: 248px;
36
  --radius-sm: 6px;
 
38
  --radius-lg: 14px;
39
  --radius-xl: 20px;
40
 
41
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.05);
42
+ --shadow-md: 0 4px 16px rgba(0,0,0,0.08);
43
+ --shadow-lg: 0 12px 40px rgba(0,0,0,0.12);
44
+ --shadow-glow: 0 0 24px rgba(0,0,0,0.06);
45
 
46
  --transition-fast: 120ms ease;
47
  --transition-base: 200ms ease;
frontend/src/pages/DashboardPage.tsx CHANGED
@@ -1,45 +1,59 @@
1
- import { useEffect, useState } from "react";
2
- import { Link } from "react-router-dom";
3
  import { api } from "../api/client";
4
- import type { Workflow } from "../types";
5
 
6
  export default function DashboardPage() {
7
- const [workflows, setWorkflows] = useState<Workflow[]>([]);
 
8
  const [error, setError] = useState("");
 
 
9
 
10
- useEffect(() => {
11
- api.listWorkflows()
12
- .then((data) => setWorkflows(data.workflows))
13
- .catch((err) => setError(err.message));
14
- }, []);
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  return (
17
- <div>
18
- <h2>Workflow History</h2>
19
- {error && <p className="error">{error}</p>}
20
- <div className="card">
21
- <table className="table">
22
- <thead>
23
- <tr>
24
- <th>ID</th>
25
- <th>Prompt</th>
26
- <th>Status</th>
27
- <th>Created</th>
28
- </tr>
29
- </thead>
30
- <tbody>
31
- {workflows.map((wf) => (
32
- <tr key={wf.workflow_id}>
33
- <td><Link to={`/workflows/${wf.workflow_id}`}>{wf.workflow_id}</Link></td>
34
- <td>{wf.user_prompt.slice(0, 60)}{wf.user_prompt.length > 60 ? "…" : ""}</td>
35
- <td><span className={`status ${wf.status}`}>{wf.status}</span></td>
36
- <td>{new Date(wf.created_at).toLocaleString()}</td>
37
- </tr>
38
- ))}
39
- </tbody>
40
- </table>
41
- {workflows.length === 0 && <p>No workflows yet. <Link to="/workflows/new">Create one</Link></p>}
42
- </div>
43
  </div>
44
  );
45
  }
 
1
+ import { useState } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
  import { api } from "../api/client";
 
4
 
5
  export default function DashboardPage() {
6
+ const [prompt, setPrompt] = useState("");
7
+ const [apiKey, setApiKey] = useState("");
8
  const [error, setError] = useState("");
9
+ const [loading, setLoading] = useState(false);
10
+ const navigate = useNavigate();
11
 
12
+ const handleSubmit = async (e: React.FormEvent) => {
13
+ e.preventDefault();
14
+ if (!prompt.trim()) return setError("Prompt cannot be empty");
15
+ if (!apiKey.trim()) return setError("API Key is required");
16
+
17
+ setLoading(true);
18
+ setError("");
19
+
20
+ try {
21
+ const { workflow_id } = await api.createWorkflow(prompt.trim(), "gemini", apiKey.trim());
22
+ await api.startWorkflow(workflow_id);
23
+ navigate(`/workflows/${workflow_id}`);
24
+ } catch (err: any) {
25
+ setError(err.message || "Failed to start workflow");
26
+ setLoading(false);
27
+ }
28
+ };
29
 
30
  return (
31
+ <div style={{ maxWidth: "800px", margin: "4rem auto", textAlign: "center" }}>
32
+ <h1 style={{ fontSize: "2rem", marginBottom: "1rem", fontWeight: 600 }}>What would you like to build?</h1>
33
+ <p style={{ color: "var(--text-secondary)", marginBottom: "3rem" }}>AutoForge AI will autonomously plan, write, and test your application.</p>
34
+
35
+ {error && <div className="alert alert-error">{error}</div>}
36
+
37
+ <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
38
+ <input
39
+ type="password"
40
+ placeholder="Enter your Gemini API Key..."
41
+ value={apiKey}
42
+ onChange={(e) => setApiKey(e.target.value)}
43
+ style={{ padding: "1rem", fontSize: "1rem", borderRadius: "var(--radius-lg)", boxShadow: "var(--shadow-sm)" }}
44
+ />
45
+
46
+ <textarea
47
+ placeholder="E.g. Create a simple Python FastAPI server that serves a React to-do app..."
48
+ value={prompt}
49
+ onChange={(e) => setPrompt(e.target.value)}
50
+ style={{ minHeight: "150px", padding: "1rem", fontSize: "1.1rem", borderRadius: "var(--radius-lg)", boxShadow: "var(--shadow-md)" }}
51
+ />
52
+
53
+ <button type="submit" className="primary" disabled={loading} style={{ alignSelf: "flex-end", padding: "0.8rem 2rem", fontSize: "1.1rem" }}>
54
+ {loading ? "Starting..." : "Generate Code"}
55
+ </button>
56
+ </form>
57
  </div>
58
  );
59
  }
frontend/src/pages/LoginPage.tsx DELETED
@@ -1,41 +0,0 @@
1
- import { useState } from "react";
2
- import { Link, useNavigate } from "react-router-dom";
3
- import { useAuth } from "../context/AuthContext";
4
-
5
- export default function LoginPage() {
6
- const { login } = useAuth();
7
- const navigate = useNavigate();
8
- const [email, setEmail] = useState("");
9
- const [password, setPassword] = useState("");
10
- const [error, setError] = useState("");
11
-
12
- const submit = async (e: React.FormEvent) => {
13
- e.preventDefault();
14
- setError("");
15
- try {
16
- await login(email, password);
17
- navigate("/dashboard");
18
- } catch (err) {
19
- setError(err instanceof Error ? err.message : "Login failed");
20
- }
21
- };
22
-
23
- return (
24
- <div className="auth-page">
25
- <div className="card auth-card">
26
- <h2>Login to AutoForge</h2>
27
- {error && <p className="error">{error}</p>}
28
- <form onSubmit={submit}>
29
- <label>Email</label>
30
- <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
31
- <label>Password</label>
32
- <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
33
- <button className="primary" type="submit">Login</button>
34
- </form>
35
- <p style={{ marginTop: "1rem" }}>
36
- No account? <Link to="/signup">Sign up</Link>
37
- </p>
38
- </div>
39
- </div>
40
- );
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/NewWorkflowPage.tsx DELETED
@@ -1,100 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import { api } from "../api/client";
4
- import type { ProviderInfo } from "../types";
5
-
6
- export default function NewWorkflowPage() {
7
- const navigate = useNavigate();
8
- const [providers, setProviders] = useState<ProviderInfo[]>([]);
9
- const [provider, setProvider] = useState("openai");
10
- const [model, setModel] = useState("");
11
- const [apiKey, setApiKey] = useState("");
12
- const [prompt, setPrompt] = useState("");
13
- const [error, setError] = useState("");
14
- const [loading, setLoading] = useState(false);
15
-
16
- useEffect(() => {
17
- api.listProviders().then((data) => {
18
- setProviders(data.providers);
19
- if (data.providers.length) {
20
- setProvider(data.providers[0].id);
21
- setModel(data.providers[0].models[0] || "");
22
- }
23
- });
24
- }, []);
25
-
26
- const selectedProvider = providers.find((p) => p.id === provider);
27
-
28
- const submit = async (e: React.FormEvent) => {
29
- e.preventDefault();
30
- setLoading(true);
31
- setError("");
32
- try {
33
- const wf = await api.createWorkflow({
34
- user_prompt: prompt,
35
- llm_provider: provider,
36
- llm_model: model,
37
- api_key: apiKey || undefined,
38
- });
39
- await api.startWorkflow(wf.workflow_id, apiKey || undefined);
40
- navigate(`/workflows/${wf.workflow_id}`);
41
- } catch (err) {
42
- setError(err instanceof Error ? err.message : "Failed to create workflow");
43
- } finally {
44
- setLoading(false);
45
- }
46
- };
47
-
48
- return (
49
- <div>
50
- <h2>New Workflow</h2>
51
- <div className="card">
52
- {error && <p className="error">{error}</p>}
53
- <form onSubmit={submit}>
54
- <label>Prompt</label>
55
- <textarea
56
- value={prompt}
57
- onChange={(e) => setPrompt(e.target.value)}
58
- placeholder="Build a FastAPI calculator with add and subtract endpoints..."
59
- required
60
- />
61
-
62
- <div className="grid-2">
63
- <div>
64
- <label>LLM Provider</label>
65
- <select value={provider} onChange={(e) => {
66
- setProvider(e.target.value);
67
- const p = providers.find((x) => x.id === e.target.value);
68
- setModel(p?.models[0] || "");
69
- }}>
70
- {providers.map((p) => (
71
- <option key={p.id} value={p.id}>{p.name}</option>
72
- ))}
73
- </select>
74
- </div>
75
- <div>
76
- <label>Model</label>
77
- <select value={model} onChange={(e) => setModel(e.target.value)} required>
78
- {(selectedProvider?.models || []).map((m) => (
79
- <option key={m} value={m}>{m}</option>
80
- ))}
81
- </select>
82
- </div>
83
- </div>
84
-
85
- <label>API Key (optional if saved in settings)</label>
86
- <input
87
- type="password"
88
- value={apiKey}
89
- onChange={(e) => setApiKey(e.target.value)}
90
- placeholder="sk-..."
91
- />
92
-
93
- <button className="primary" type="submit" disabled={loading}>
94
- {loading ? "Starting…" : "Start Workflow"}
95
- </button>
96
- </form>
97
- </div>
98
- </div>
99
- );
100
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/pages/SignupPage.tsx DELETED
@@ -1,44 +0,0 @@
1
- import { useState } from "react";
2
- import { Link, useNavigate } from "react-router-dom";
3
- import { useAuth } from "../context/AuthContext";
4
-
5
- export default function SignupPage() {
6
- const { signup } = useAuth();
7
- const navigate = useNavigate();
8
- const [email, setEmail] = useState("");
9
- const [username, setUsername] = useState("");
10
- const [password, setPassword] = useState("");
11
- const [error, setError] = useState("");
12
-
13
- const submit = async (e: React.FormEvent) => {
14
- e.preventDefault();
15
- setError("");
16
- try {
17
- await signup(email, username, password);
18
- navigate("/dashboard");
19
- } catch (err) {
20
- setError(err instanceof Error ? err.message : "Signup failed");
21
- }
22
- };
23
-
24
- return (
25
- <div className="auth-page">
26
- <div className="card auth-card">
27
- <h2>Create Account</h2>
28
- {error && <p className="error">{error}</p>}
29
- <form onSubmit={submit}>
30
- <label>Email</label>
31
- <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
32
- <label>Username</label>
33
- <input value={username} onChange={(e) => setUsername(e.target.value)} required />
34
- <label>Password</label>
35
- <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required minLength={8} />
36
- <button className="primary" type="submit">Sign Up</button>
37
- </form>
38
- <p style={{ marginTop: "1rem" }}>
39
- Have an account? <Link to="/login">Login</Link>
40
- </p>
41
- </div>
42
- </div>
43
- );
44
- }