Unified · Split
| @@ -4,6 +4,9 @@ · 3 unmodified lines | |||
| 4 | <meta charset="UTF-8" /> | 4 | <meta charset="UTF-8" /> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | <title>Executor</title> | 6 | <title>Executor</title> |
| 7 | <link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
| 8 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||
| 9 | <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,300..700&family=Instrument+Serif&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" /> | ||
| 7 | </head> | 10 | </head> |
| 8 | <body> | 11 | <body> |
| 9 | <div id="root"></div> | 12 | <div id="root"></div> |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | "type": "module", | 5 | "type": "module", |
| 6 | "dependencies": { | 6 | "dependencies": { |
| 7 | "@executor/plugin-openapi": "workspace:*", | 7 | "@executor/plugin-openapi": "workspace:*", |
| 8 | "tailwindcss": "^4.1.10", | ||
| 8 | "@executor/react": "workspace:*", | 9 | "@executor/react": "workspace:*", |
| 9 | "@executor/server": "workspace:*", | 10 | "@executor/server": "workspace:*", |
| 10 | "@tanstack/react-router": "^1.168.10", | 11 | "@tanstack/react-router": "^1.168.10", |
| @@ -15,6 +16,7 @@ · 4 unmodified lines | |||
| 15 | "@types/react": "^19.1.0", | 16 | "@types/react": "^19.1.0", |
| 16 | "@types/react-dom": "^19.1.0", | 17 | "@types/react-dom": "^19.1.0", |
| 17 | "@types/node": "catalog:", | 18 | "@types/node": "catalog:", |
| 19 | "@tailwindcss/vite": "^4.1.10", | ||
| 18 | "@vitejs/plugin-react": "^4.5.2", | 20 | "@vitejs/plugin-react": "^4.5.2", |
| 19 | "typescript": "^5.9.3", | 21 | "typescript": "^5.9.3", |
| 20 | "vite": "^6.3.5" | 22 | "vite": "^6.3.5" |
| @@ -1,5 +1,6 @@ | |||
| 1 | import React from "react"; | 1 | import React from "react"; |
| 2 | import { createRoot } from "react-dom/client"; | 2 | import { createRoot } from "react-dom/client"; |
| 3 | import "./styles.css"; | ||
| 3 | import { App } from "./App"; | 4 | import { App } from "./App"; |
| 4 | 5 | ||
| 5 | const root = createRoot(document.getElementById("root")!); | 6 | const root = createRoot(document.getElementById("root")!); |
| @@ -3,25 +3,45 @@ import { useAtomValue, Result, secretsAtom } from "@executor/react"; · 2 unmodified lines | |||
| 3 | export function SecretsPage() { | 3 | export function SecretsPage() { |
| 4 | const secrets = useAtomValue(secretsAtom()); | 4 | const secrets = useAtomValue(secretsAtom()); |
| 5 | 5 | ||
| 6 | return Result.match(secrets, { | 6 | return ( |
| 7 | onInitial: () => <p>Loading secrets…</p>, | 7 | <div> |
| 8 | onFailure: () => <p style={{ color: "red" }}>Failed to load secrets</p>, | 8 | <h1 className="text-xl font-semibold text-foreground">Secrets</h1> |
| 9 | onSuccess: ({ value }) => ( | 9 | |
| 10 | <div> | 10 | {Result.match(secrets, { |
| 11 | <h2>Secrets ({value.length})</h2> | 11 | onInitial: () => ( |
| 12 | {value.length === 0 ? ( | 12 | <p className="mt-4 text-sm text-muted-foreground">Loading secrets…</p> |
| 13 | <p style={{ color: "#888" }}>No secrets stored yet.</p> | 13 | ), |
| 14 | ) : ( | 14 | onFailure: () => ( |
| 15 | <ul> | 15 | <p className="mt-4 text-sm text-destructive">Failed to load secrets</p> |
| 16 | {value.map((s) => ( | 16 | ), |
| 17 | <li key={s.id}> | 17 | onSuccess: ({ value }) => |
| 18 | <strong>{s.name}</strong> | 18 | value.length === 0 ? ( |
| 19 | {s.purpose && <span> — {s.purpose}</span>} | 19 | <p className="mt-4 text-sm text-muted-foreground"> |
| 20 | </li> | 20 | No secrets stored yet. Secrets are created when you configure authentication for a source. |
| 21 | ))} | 21 | </p> |
| 22 | </ul> | 22 | ) : ( |
| 23 | )} | 23 | <div className="mt-4 grid gap-2"> |
| 24 | </div> | 24 | {value.map((s) => ( |
| 25 | ), | 25 | <div |
| 26 | }); | 26 | key={s.id} |
| 27 | className="flex items-center justify-between rounded-lg border border-border bg-card p-3" | ||
| 28 | > | ||
| 29 | <div className="min-w-0"> | ||
| 30 | <p className="text-sm font-medium text-card-foreground">{s.name}</p> | ||
| 31 | {s.purpose && ( | ||
| 32 | <p className="mt-0.5 text-xs text-muted-foreground">{s.purpose}</p> | ||
| 33 | )} | ||
| 34 | </div> | ||
| 35 | {s.provider && ( | ||
| 36 | <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"> | ||
| 37 | {s.provider} | ||
| 38 | </span> | ||
| 39 | )} | ||
| 40 | </div> | ||
| 41 | ))} | ||
| 42 | </div> | ||
| 43 | ), | ||
| 44 | })} | ||
| 45 | </div> | ||
| 46 | ); | ||
| 27 | } | 47 | } |
| @@ -18,12 +18,10 @@ export function SourcesPage() { · 17 unmodified lines | |||
| 18 | const tools = useAtomValue(toolsAtom()); | 18 | const tools = useAtomValue(toolsAtom()); |
| 19 | const refreshTools = useAtomRefresh(toolsAtom()); | 19 | const refreshTools = useAtomRefresh(toolsAtom()); |
| 20 | 20 | ||
| 21 | // Group tools by namespace (second tag after "openapi") | ||
| 22 | const sources = useMemo(() => { | 21 | const sources = useMemo(() => { |
| 23 | if (tools._tag !== "Success") return []; | 22 | if (tools._tag !== "Success") return []; |
| 24 | const namespaces = new Map<string, number>(); | 23 | const namespaces = new Map<string, number>(); |
| 25 | for (const tool of tools.value) { | 24 | for (const tool of tools.value) { |
| 26 | // Tools are tagged ["openapi", namespace, ...] | ||
| 27 | const ns = tool.tags.find( | 25 | const ns = tool.tags.find( |
| 28 | (t) => t !== "openapi" && !sourcePlugins.some((p) => p.key === t), | 26 | (t) => t !== "openapi" && !sourcePlugins.some((p) => p.key === t), |
| 29 | ); | 27 | ); |
| @@ -44,7 +42,7 @@ export function SourcesPage() { · 14 unmodified lines | |||
| 44 | if (plugin) { | 42 | if (plugin) { |
| 45 | const AddComponent = plugin.add; | 43 | const AddComponent = plugin.add; |
| 46 | return ( | 44 | return ( |
| 47 | <Suspense fallback={<p>Loading…</p>}> | 45 | <Suspense fallback={<p className="text-sm text-muted-foreground">Loading…</p>}> |
| 48 | <AddComponent | 46 | <AddComponent |
| 49 | onComplete={() => { | 47 | onComplete={() => { |
| 50 | setAdding(null); | 48 | setAdding(null); |
| @@ -58,35 +56,65 @@ export function SourcesPage() { · 7 unmodified lines | |||
| 58 | 56 | ||
| 59 | return ( | 57 | return ( |
| 60 | <div> | 58 | <div> |
| 61 | <h2>Sources</h2> | 59 | <div className="flex items-center justify-between"> |
| 60 | <h1 className="text-xl font-semibold text-foreground">Sources</h1> | ||
| 61 | <div className="flex gap-2"> | ||
| 62 | {sourcePlugins.map((p) => ( | ||
| 63 | <button | ||
| 64 | key={p.key} | ||
| 65 | onClick={() => setAdding(p.key)} | ||
| 66 | className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" | ||
| 67 | > | ||
| 68 | <svg viewBox="0 0 16 16" fill="none" className="size-3.5"> | ||
| 69 | <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" /> | ||
| 70 | </svg> | ||
| 71 | Add {p.label} | ||
| 72 | </button> | ||
| 73 | ))} | ||
| 74 | </div> | ||
| 75 | </div> | ||
| 62 | 76 | ||
| 63 | {Result.match(tools, { | 77 | {Result.match(tools, { |
| 64 | onInitial: () => <p>Loading…</p>, | 78 | onInitial: () => ( |
| 65 | onFailure: () => <p style={{ color: "red" }}>Failed to load sources</p>, | 79 | <p className="mt-4 text-sm text-muted-foreground">Loading…</p> |
| 80 | ), | ||
| 81 | onFailure: () => ( | ||
| 82 | <p className="mt-4 text-sm text-destructive">Failed to load sources</p> | ||
| 83 | ), | ||
| 66 | onSuccess: () => | 84 | onSuccess: () => |
| 67 | sources.length === 0 ? ( | 85 | sources.length === 0 ? ( |
| 68 | <p style={{ color: "#888" }}>No sources configured yet.</p> | 86 | <div className="mt-8 flex flex-col items-center justify-center text-center"> |
| 87 | <div className="rounded-full bg-muted p-4"> | ||
| 88 | <svg viewBox="0 0 24 24" fill="none" className="size-8 text-muted-foreground/50"> | ||
| 89 | <path d="M12 6v12M6 12h12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> | ||
| 90 | </svg> | ||
| 91 | </div> | ||
| 92 | <p className="mt-3 text-sm font-medium text-foreground">No sources configured</p> | ||
| 93 | <p className="mt-1 text-xs text-muted-foreground"> | ||
| 94 | Add an API source to start discovering tools. | ||
| 95 | </p> | ||
| 96 | </div> | ||
| 69 | ) : ( | 97 | ) : ( |
| 70 | <ul> | 98 | <div className="mt-4 grid gap-2"> |
| 71 | {sources.map((s) => ( | 99 | {sources.map((s) => ( |
| 72 | <li key={s.namespace}> | 100 | <div |
| 73 | <strong>{s.namespace}</strong> — {s.toolCount} tools | 101 | key={s.namespace} |
| 74 | </li> | 102 | className="flex items-center justify-between rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent/50" |
| 103 | > | ||
| 104 | <div> | ||
| 105 | <p className="text-sm font-medium text-card-foreground">{s.namespace}</p> | ||
| 106 | <p className="mt-0.5 text-xs text-muted-foreground"> | ||
| 107 | {s.toolCount} tool{s.toolCount !== 1 ? "s" : ""} | ||
| 108 | </p> | ||
| 109 | </div> | ||
| 110 | <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"> | ||
| 111 | openapi | ||
| 112 | </span> | ||
| 113 | </div> | ||
| 75 | ))} | 114 | ))} |
| 76 | </ul> | 115 | </div> |
| 77 | ), | 116 | ), |
| 78 | })} | 117 | })} |
| 79 | |||
| 80 | <div style={{ marginTop: "1rem" }}> | ||
| 81 | <h3>Add a source</h3> | ||
| 82 | <div style={{ display: "flex", gap: "0.5rem" }}> | ||
| 83 | {sourcePlugins.map((p) => ( | ||
| 84 | <button key={p.key} onClick={() => setAdding(p.key)}> | ||
| 85 | {p.label} | ||
| 86 | </button> | ||
| 87 | ))} | ||
| 88 | </div> | ||
| 89 | </div> | ||
| 90 | </div> | 118 | </div> |
| 91 | ); | 119 | ); |
| 92 | } | 120 | } |
| @@ -3,25 +3,63 @@ import { useAtomValue, Result, toolsAtom } from "@executor/react"; · 2 unmodified lines | |||
| 3 | export function ToolsPage() { | 3 | export function ToolsPage() { |
| 4 | const tools = useAtomValue(toolsAtom()); | 4 | const tools = useAtomValue(toolsAtom()); |
| 5 | 5 | ||
| 6 | return Result.match(tools, { | 6 | return ( |
| 7 | onInitial: () => <p>Loading tools…</p>, | 7 | <div> |
| 8 | onFailure: () => <p style={{ color: "red" }}>Failed to load tools</p>, | 8 | <h1 className="text-xl font-semibold text-foreground">Tools</h1> |
| 9 | onSuccess: ({ value }) => ( | 9 | |
| 10 | <div> | 10 | {Result.match(tools, { |
| 11 | <h2>Tools ({value.length})</h2> | 11 | onInitial: () => ( |
| 12 | {value.length === 0 ? ( | 12 | <p className="mt-4 text-sm text-muted-foreground">Loading tools…</p> |
| 13 | <p style={{ color: "#888" }}>No tools registered yet.</p> | 13 | ), |
| 14 | ) : ( | 14 | onFailure: () => ( |
| 15 | <ul> | 15 | <p className="mt-4 text-sm text-destructive">Failed to load tools</p> |
| 16 | {value.map((t) => ( | 16 | ), |
| 17 | <li key={t.id}> | 17 | onSuccess: ({ value }) => |
| 18 | <strong>{t.name}</strong> | 18 | value.length === 0 ? ( |
| 19 | {t.description && <span> — {t.description}</span>} | 19 | <p className="mt-4 text-sm text-muted-foreground"> |
| 20 | </li> | 20 | No tools registered yet. Add a source to get started. |
| 21 | ))} | 21 | </p> |
| 22 | </ul> | 22 | ) : ( |
| 23 | )} | 23 | <div className="mt-4"> |
| 24 | </div> | 24 | <p className="text-sm text-muted-foreground mb-3"> |
| 25 | ), | 25 | {value.length} tool{value.length !== 1 ? "s" : ""} available |
| 26 | }); | 26 | </p> |
| 27 | <div className="grid gap-2"> | ||
| 28 | {value.map((t) => ( | ||
| 29 | <div | ||
| 30 | key={t.id} | ||
| 31 | className="rounded-lg border border-border bg-card p-3 transition-colors hover:bg-accent/50" | ||
| 32 | > | ||
| 33 | <div className="flex items-start justify-between gap-2"> | ||
| 34 | <div className="min-w-0"> | ||
| 35 | <p className="text-sm font-medium text-card-foreground truncate"> | ||
| 36 | {t.name} | ||
| 37 | </p> | ||
| 38 | {t.description && ( | ||
| 39 | <p className="mt-0.5 text-xs text-muted-foreground line-clamp-2"> | ||
| 40 | {t.description} | ||
| 41 | </p> | ||
| 42 | )} | ||
| 43 | </div> | ||
| 44 | {t.tags.length > 0 && ( | ||
| 45 | <div className="flex shrink-0 gap-1"> | ||
| 46 | {t.tags.slice(0, 2).map((tag) => ( | ||
| 47 | <span | ||
| 48 | key={tag} | ||
| 49 | className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground" | ||
| 50 | > | ||
| 51 | {tag} | ||
| 52 | </span> | ||
| 53 | ))} | ||
| 54 | </div> | ||
| 55 | )} | ||
| 56 | </div> | ||
| 57 | </div> | ||
| 58 | ))} | ||
| 59 | </div> | ||
| 60 | </div> | ||
| 61 | ), | ||
| 62 | })} | ||
| 63 | </div> | ||
| 64 | ); | ||
| 27 | } | 65 | } |
| @@ -1,31 +1,141 @@ | |||
| 1 | import { Link } from "@tanstack/react-router"; | 1 | import { Link, useLocation } from "@tanstack/react-router"; |
| 2 | import { useMemo } from "react"; | ||
| 3 | import { useAtomValue, toolsAtom, Result } from "@executor/react"; | ||
| 2 | import type { ReactNode } from "react"; | 4 | import type { ReactNode } from "react"; |
| 3 | 5 | ||
| 4 | export function Shell({ children }: { children: ReactNode }) { | 6 | // --------------------------------------------------------------------------- |
| 7 | // Nav item | ||
| 8 | // --------------------------------------------------------------------------- | ||
| 9 | |||
| 10 | function NavItem(props: { to: string; label: string; active: boolean }) { | ||
| 5 | return ( | 11 | return ( |
| 6 | <div style={{ display: "flex", minHeight: "100vh", fontFamily: "system-ui" }}> | 12 | <Link |
| 7 | <nav | 13 | to={props.to} |
| 8 | style={{ | 14 | className={[ |
| 9 | width: 200, | 15 | "flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors", |
| 10 | padding: "1rem", | 16 | props.active |
| 11 | borderRight: "1px solid #eee", | 17 | ? "bg-sidebar-active text-foreground font-medium" |
| 12 | display: "flex", | 18 | : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground", |
| 13 | flexDirection: "column", | 19 | ].join(" ")} |
| 14 | gap: "0.5rem", | 20 | > |
| 15 | }} | 21 | {props.label} |
| 16 | > | 22 | </Link> |
| 17 | <h2 style={{ margin: 0, fontSize: "1.2rem" }}>Executor</h2> | 23 | ); |
| 18 | <Link to="/tools" style={{ textDecoration: "none", color: "#333" }}> | 24 | } |
| 19 | Tools | 25 | |
| 20 | </Link> | 26 | // --------------------------------------------------------------------------- |
| 21 | <Link to="/sources" style={{ textDecoration: "none", color: "#333" }}> | 27 | // Source list in sidebar |
| 22 | Sources | 28 | // --------------------------------------------------------------------------- |
| 23 | </Link> | 29 | |
| 24 | <Link to="/secrets" style={{ textDecoration: "none", color: "#333" }}> | 30 | function SourceList() { |
| 25 | Secrets | 31 | const tools = useAtomValue(toolsAtom()); |
| 32 | |||
| 33 | const sources = useMemo(() => { | ||
| 34 | if (tools._tag !== "Success") return []; | ||
| 35 | const namespaces = new Map<string, number>(); | ||
| 36 | for (const tool of tools.value) { | ||
| 37 | const ns = tool.tags.find((t) => t !== "openapi"); | ||
| 38 | if (ns && ns !== tool.tags[0]) { | ||
| 39 | namespaces.set(ns, (namespaces.get(ns) ?? 0) + 1); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | return [...namespaces.entries()].map(([ns, count]) => ({ namespace: ns, toolCount: count })); | ||
| 43 | }, [tools]); | ||
| 44 | |||
| 45 | if (tools._tag === "Initial" || tools.waiting) { | ||
| 46 | return <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">Loading…</div>; | ||
| 47 | } | ||
| 48 | |||
| 49 | if (sources.length === 0) { | ||
| 50 | return <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">No sources yet</div>; | ||
| 51 | } | ||
| 52 | |||
| 53 | return ( | ||
| 54 | <div className="flex flex-col gap-px"> | ||
| 55 | {sources.map((s) => ( | ||
| 56 | <Link | ||
| 57 | key={s.namespace} | ||
| 58 | to="/sources" | ||
| 59 | className="group flex items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground transition-colors" | ||
| 60 | > | ||
| 61 | <span className="flex-1 truncate">{s.namespace}</span> | ||
| 62 | <span className="text-[10px] tabular-nums text-muted-foreground/50">{s.toolCount}</span> | ||
| 26 | </Link> | 63 | </Link> |
| 27 | </nav> | 64 | ))} |
| 28 | <main style={{ flex: 1, padding: "1.5rem" }}>{children}</main> | 65 | </div> |
| 66 | ); | ||
| 67 | } | ||
| 68 | |||
| 69 | // --------------------------------------------------------------------------- | ||
| 70 | // Shell | ||
| 71 | // --------------------------------------------------------------------------- | ||
| 72 | |||
| 73 | export function Shell({ children }: { children: ReactNode }) { | ||
| 74 | const location = useLocation(); | ||
| 75 | const pathname = location.pathname; | ||
| 76 | |||
| 77 | return ( | ||
| 78 | <div className="flex h-screen overflow-hidden"> | ||
| 79 | {/* Sidebar */} | ||
| 80 | <aside className="hidden w-52 shrink-0 border-r border-sidebar-border bg-sidebar md:flex md:flex-col lg:w-56"> | ||
| 81 | {/* Brand */} | ||
| 82 | <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4"> | ||
| 83 | <Link to="/" className="flex items-center gap-1.5"> | ||
| 84 | <span className="font-display text-base tracking-tight text-foreground"> | ||
| 85 | executor | ||
| 86 | </span> | ||
| 87 | <span className="rounded bg-primary/15 px-1.5 py-0.5 text-[10px] font-medium text-primary"> | ||
| 88 | v4 | ||
| 89 | </span> | ||
| 90 | </Link> | ||
| 91 | </div> | ||
| 92 | |||
| 93 | {/* Nav */} | ||
| 94 | <nav className="flex flex-1 flex-col overflow-y-auto p-2"> | ||
| 95 | <NavItem to="/" label="Tools" active={pathname === "/" || pathname === "/tools"} /> | ||
| 96 | <NavItem to="/secrets" label="Secrets" active={pathname === "/secrets"} /> | ||
| 97 | |||
| 98 | {/* Sources section */} | ||
| 99 | <div className="mt-5 mb-1 px-2.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground/50"> | ||
| 100 | <div className="flex items-center justify-between gap-2"> | ||
| 101 | <span>Sources</span> | ||
| 102 | <Link | ||
| 103 | to="/sources" | ||
| 104 | className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-[10px] font-medium normal-case tracking-normal text-primary transition-colors hover:bg-sidebar-active hover:text-foreground" | ||
| 105 | > | ||
| 106 | <svg viewBox="0 0 16 16" fill="none" className="size-3"> | ||
| 107 | <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" /> | ||
| 108 | </svg> | ||
| 109 | Add | ||
| 110 | </Link> | ||
| 111 | </div> | ||
| 112 | </div> | ||
| 113 | |||
| 114 | <SourceList /> | ||
| 115 | </nav> | ||
| 116 | |||
| 117 | {/* Footer */} | ||
| 118 | <div className="shrink-0 border-t border-sidebar-border px-4 py-2.5"> | ||
| 119 | <div className="flex items-center justify-between text-[10px] leading-none"> | ||
| 120 | <span className="text-muted-foreground/70 tabular-nums">v4.0.0-dev</span> | ||
| 121 | <a | ||
| 122 | href="https://github.com" | ||
| 123 | target="_blank" | ||
| 124 | rel="noreferrer" | ||
| 125 | className="text-muted-foreground/70 transition-colors hover:text-foreground" | ||
| 126 | > | ||
| 127 | GitHub | ||
| 128 | </a> | ||
| 129 | </div> | ||
| 130 | </div> | ||
| 131 | </aside> | ||
| 132 | |||
| 133 | {/* Main content */} | ||
| 134 | <main className="flex min-h-0 flex-1 flex-col min-w-0 overflow-auto"> | ||
| 135 | <div className="flex-1 p-6 max-w-5xl"> | ||
| 136 | {children} | ||
| 137 | </div> | ||
| 138 | </main> | ||
| 29 | </div> | 139 | </div> |
| 30 | ); | 140 | ); |
| 31 | } | 141 | } |
| @@ -0,0 +1,128 @@ | |||
| 1 | @import "tailwindcss"; | ||
| 2 | |||
| 3 | @source "../../../packages/clients/react/src/**/*.{ts,tsx}"; | ||
| 4 | @source "../../../packages/plugins/openapi/src/react/**/*.{ts,tsx}"; | ||
| 5 | |||
| 6 | @theme inline { | ||
| 7 | --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif; | ||
| 8 | --font-mono: "JetBrains Mono", ui-monospace, monospace; | ||
| 9 | --font-display: "Instrument Serif", Georgia, serif; | ||
| 10 | |||
| 11 | --color-background: var(--background); | ||
| 12 | --color-foreground: var(--foreground); | ||
| 13 | --color-card: var(--card); | ||
| 14 | --color-card-foreground: var(--card-foreground); | ||
| 15 | --color-primary: var(--primary); | ||
| 16 | --color-primary-foreground: var(--primary-foreground); | ||
| 17 | --color-secondary: var(--secondary); | ||
| 18 | --color-secondary-foreground: var(--secondary-foreground); | ||
| 19 | --color-muted: var(--muted); | ||
| 20 | --color-muted-foreground: var(--muted-foreground); | ||
| 21 | --color-accent: var(--accent); | ||
| 22 | --color-accent-foreground: var(--accent-foreground); | ||
| 23 | --color-destructive: var(--destructive); | ||
| 24 | --color-border: var(--border); | ||
| 25 | --color-input: var(--input); | ||
| 26 | --color-ring: var(--ring); | ||
| 27 | |||
| 28 | --color-sidebar: var(--sidebar); | ||
| 29 | --color-sidebar-foreground: var(--sidebar-foreground); | ||
| 30 | --color-sidebar-border: var(--sidebar-border); | ||
| 31 | --color-sidebar-active: var(--sidebar-active); | ||
| 32 | |||
| 33 | --radius-sm: calc(var(--radius) * 0.6); | ||
| 34 | --radius-md: calc(var(--radius) * 0.8); | ||
| 35 | --radius-lg: var(--radius); | ||
| 36 | --radius-xl: calc(var(--radius) * 1.4); | ||
| 37 | } | ||
| 38 | |||
| 39 | /* ——— Light theme ——— */ | ||
| 40 | :root { | ||
| 41 | color-scheme: light dark; | ||
| 42 | --radius: 0.625rem; | ||
| 43 | |||
| 44 | --background: oklch(0.985 0.002 260); | ||
| 45 | --foreground: oklch(0.145 0.005 260); | ||
| 46 | --card: oklch(0.99 0.001 260); | ||
| 47 | --card-foreground: oklch(0.145 0.005 260); | ||
| 48 | --primary: oklch(0.55 0.17 175); | ||
| 49 | --primary-foreground: oklch(0.98 0.005 175); | ||
| 50 | --secondary: oklch(0.94 0.005 260); | ||
| 51 | --secondary-foreground: oklch(0.32 0.008 260); | ||
| 52 | --muted: oklch(0.955 0.004 260); | ||
| 53 | --muted-foreground: oklch(0.50 0.01 250); | ||
| 54 | --accent: oklch(0.94 0.006 260); | ||
| 55 | --accent-foreground: oklch(0.145 0.005 260); | ||
| 56 | --destructive: oklch(0.55 0.22 25); | ||
| 57 | --border: oklch(0.88 0.006 260); | ||
| 58 | --input: oklch(0.88 0.006 260); | ||
| 59 | --ring: oklch(0.55 0.17 175); | ||
| 60 | |||
| 61 | --sidebar: oklch(0.97 0.003 260); | ||
| 62 | --sidebar-foreground: oklch(0.32 0.008 260); | ||
| 63 | --sidebar-border: oklch(0.90 0.005 260); | ||
| 64 | --sidebar-active: oklch(0.935 0.006 260); | ||
| 65 | |||
| 66 | --scrollbar-thumb: oklch(0.78 0.005 260); | ||
| 67 | --scrollbar-thumb-hover: oklch(0.68 0.005 260); | ||
| 68 | } | ||
| 69 | |||
| 70 | /* ——— Dark theme ——— */ | ||
| 71 | @media (prefers-color-scheme: dark) { | ||
| 72 | :root { | ||
| 73 | --background: oklch(0.115 0.005 260); | ||
| 74 | --foreground: oklch(0.90 0.008 250); | ||
| 75 | --card: oklch(0.145 0.005 260); | ||
| 76 | --card-foreground: oklch(0.90 0.008 250); | ||
| 77 | --primary: oklch(0.72 0.15 175); | ||
| 78 | --primary-foreground: oklch(0.12 0.01 175); | ||
| 79 | --secondary: oklch(0.195 0.007 260); | ||
| 80 | --secondary-foreground: oklch(0.78 0.008 250); | ||
| 81 | --muted: oklch(0.175 0.005 260); | ||
| 82 | --muted-foreground: oklch(0.56 0.01 250); | ||
| 83 | --accent: oklch(0.195 0.008 260); | ||
| 84 | --accent-foreground: oklch(0.90 0.008 250); | ||
| 85 | --destructive: oklch(0.62 0.22 25); | ||
| 86 | --border: oklch(0.235 0.007 260); | ||
| 87 | --input: oklch(0.195 0.007 260); | ||
| 88 | --ring: oklch(0.72 0.15 175); | ||
| 89 | |||
| 90 | --sidebar: oklch(0.105 0.005 260); | ||
| 91 | --sidebar-foreground: oklch(0.72 0.008 250); | ||
| 92 | --sidebar-border: oklch(0.20 0.006 260); | ||
| 93 | --sidebar-active: oklch(0.18 0.008 260); | ||
| 94 | |||
| 95 | --scrollbar-thumb: oklch(0.30 0.005 260); | ||
| 96 | --scrollbar-thumb-hover: oklch(0.38 0.005 260); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | @layer base { | ||
| 101 | * { | ||
| 102 | @apply border-border; | ||
| 103 | } | ||
| 104 | |||
| 105 | html, body, #root { | ||
| 106 | min-height: 100%; | ||
| 107 | height: 100%; | ||
| 108 | } | ||
| 109 | |||
| 110 | body { | ||
| 111 | @apply bg-background font-sans text-foreground antialiased; | ||
| 112 | } | ||
| 113 | |||
| 114 | ::-webkit-scrollbar { | ||
| 115 | width: 6px; | ||
| 116 | height: 6px; | ||
| 117 | } | ||
| 118 | ::-webkit-scrollbar-track { | ||
| 119 | background: transparent; | ||
| 120 | } | ||
| 121 | ::-webkit-scrollbar-thumb { | ||
| 122 | background: var(--scrollbar-thumb); | ||
| 123 | border-radius: 3px; | ||
| 124 | } | ||
| 125 | ::-webkit-scrollbar-thumb:hover { | ||
| 126 | background: var(--scrollbar-thumb-hover); | ||
| 127 | } | ||
| 128 | } | ||
| @@ -1,5 +1,6 @@ | |||
| 1 | import { defineConfig } from "vite"; | 1 | import { defineConfig } from "vite"; |
| 2 | import react from "@vitejs/plugin-react"; | 2 | import react from "@vitejs/plugin-react"; |
| 3 | import tailwindcss from "@tailwindcss/vite"; | ||
| 3 | import { createApiHandler } from "@executor/server"; | 4 | import { createApiHandler } from "@executor/server"; |
| 4 | 5 | ||
| 5 | const api = createApiHandler(); | 6 | const api = createApiHandler(); |
| @@ -7,6 +8,7 @@ const api = createApiHandler(); · 1 unmodified line | |||
| 7 | export default defineConfig({ | 8 | export default defineConfig({ |
| 8 | plugins: [ | 9 | plugins: [ |
| 9 | react(), | 10 | react(), |
| 11 | tailwindcss(), | ||
| 10 | { | 12 | { |
| 11 | name: "executor-api", | 13 | name: "executor-api", |
| 12 | configureServer(server) { | 14 | configureServer(server) { |
| @@ -37,8 +37,10 @@ · 36 unmodified lines | |||
| 37 | "@tanstack/react-router": "^1.168.10", | 37 | "@tanstack/react-router": "^1.168.10", |
| 38 | "react": "^19.1.0", | 38 | "react": "^19.1.0", |
| 39 | "react-dom": "^19.1.0", | 39 | "react-dom": "^19.1.0", |
| 40 | "tailwindcss": "^4.1.10", | ||
| 40 | }, | 41 | }, |
| 41 | "devDependencies": { | 42 | "devDependencies": { |
| 43 | "@tailwindcss/vite": "^4.1.10", | ||
| 42 | "@types/node": "catalog:", | 44 | "@types/node": "catalog:", |
| 43 | "@types/react": "^19.1.0", | 45 | "@types/react": "^19.1.0", |
| 44 | "@types/react-dom": "^19.1.0", | 46 | "@types/react-dom": "^19.1.0", |
| @@ -521,6 +523,36 @@ · 476 unmodified lines | |||
| 521 | 523 | ||
| 522 | "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], | 524 | "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], |
| 523 | 525 | ||
| 526 | "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], | ||
| 527 | |||
| 528 | "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], | ||
| 529 | |||
| 530 | "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], | ||
| 531 | |||
| 532 | "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], | ||
| 533 | |||
| 534 | "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], | ||
| 535 | |||
| 536 | "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], | ||
| 537 | |||
| 538 | "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], | ||
| 539 | |||
| 540 | "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], | ||
| 541 | |||
| 542 | "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], | ||
| 543 | |||
| 544 | "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], | ||
| 545 | |||
| 546 | "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], | ||
| 547 | |||
| 548 | "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], | ||
| 549 | |||
| 550 | "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], | ||
| 551 | |||
| 552 | "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], | ||
| 553 | |||
| 554 | "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], | ||
| 555 | |||
| 524 | "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], | 556 | "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], |
| 525 | 557 | ||
| 526 | "@tanstack/react-router": ["@tanstack/react-router@1.168.10", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.9", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA=="], | 558 | "@tanstack/react-router": ["@tanstack/react-router@1.168.10", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.9", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA=="], |
| @@ -623,6 +655,8 @@ · 96 unmodified lines | |||
| 623 | 655 | ||
| 624 | "electron-to-chromium": ["electron-to-chromium@1.5.330", "", {}, "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA=="], | 656 | "electron-to-chromium": ["electron-to-chromium@1.5.330", "", {}, "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA=="], |
| 625 | 657 | ||
| 658 | "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], | ||
| 659 | |||
| 626 | "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], | 660 | "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], |
| 627 | 661 | ||
| 628 | "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], | 662 | "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], |
| @@ -647,12 +681,16 @@ · 18 unmodified lines | |||
| 647 | 681 | ||
| 648 | "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], | 682 | "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], |
| 649 | 683 | ||
| 684 | "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], | ||
| 685 | |||
| 650 | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], | 686 | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], |
| 651 | 687 | ||
| 652 | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], | 688 | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], |
| 653 | 689 | ||
| 654 | "isbot": ["isbot@5.1.37", "", {}, "sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ=="], | 690 | "isbot": ["isbot@5.1.37", "", {}, "sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ=="], |
| 655 | 691 | ||
| 692 | "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], | ||
| 693 | |||
| 656 | "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], | 694 | "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], |
| 657 | 695 | ||
| 658 | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], | 696 | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], |
| @@ -665,6 +703,30 @@ · 6 unmodified lines | |||
| 665 | 703 | ||
| 666 | "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], | 704 | "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], |
| 667 | 705 | ||
| 706 | "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], | ||
| 707 | |||
| 708 | "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], | ||
| 709 | |||
| 710 | "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], | ||
| 711 | |||
| 712 | "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], | ||
| 713 | |||
| 714 | "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], | ||
| 715 | |||
| 716 | "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], | ||
| 717 | |||
| 718 | "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], | ||
| 719 | |||
| 720 | "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], | ||
| 721 | |||
| 722 | "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], | ||
| 723 | |||
| 724 | "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], | ||
| 725 | |||
| 726 | "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], | ||
| 727 | |||
| 728 | "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], | ||
| 729 | |||
| 668 | "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], | 730 | "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], |
| 669 | 731 | ||
| 670 | "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], | 732 | "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], |
| @@ -737,6 +799,10 @@ · 66 unmodified lines | |||
| 737 | 799 | ||
| 738 | "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], | 800 | "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], |
| 739 | 801 | ||
| 802 | "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], | ||
| 803 | |||
| 804 | "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], | ||
| 805 | |||
| 740 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], | 806 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], |
| 741 | 807 | ||
| 742 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], | 808 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], |
| @@ -781,6 +847,18 @@ · 38 unmodified lines | |||
| 781 | 847 | ||
| 782 | "@executor/sdk/typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], | 848 | "@executor/sdk/typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], |
| 783 | 849 | ||
| 850 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], | ||
| 851 | |||
| 852 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], | ||
| 853 | |||
| 854 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], | ||
| 855 | |||
| 856 | "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], | ||
| 857 | |||
| 858 | "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], | ||
| 859 | |||
| 860 | "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | ||
| 861 | |||
| 784 | "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], | 862 | "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], |
| 785 | 863 | ||
| 786 | "vite-node/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], | 864 | "vite-node/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], |
| @@ -7,14 +7,14 @@ import { · 6 unmodified lines | |||
| 7 | Outlet, | 7 | Outlet, |
| 8 | RouterProvider, | 8 | RouterProvider, |
| 9 | } from "@tanstack/react-router"; | 9 | } from "@tanstack/react-router"; |
| 10 | import { useAtomSet, Result } from "@effect-atom/atom-react"; | 10 | import { useAtomSet } from "@effect-atom/atom-react"; |
| 11 | import { Option } from "effect"; | 11 | import { Option } from "effect"; |
| 12 | 12 | ||
| 13 | import { previewOpenApiSpec, addOpenApiSpec } from "./atoms"; | 13 | import { previewOpenApiSpec, addOpenApiSpec } from "./atoms"; |
| 14 | import type { SpecPreview } from "../sdk/preview"; | 14 | import type { SpecPreview } from "../sdk/preview"; |
| 15 | 15 | ||
| 16 | // --------------------------------------------------------------------------- | 16 | // --------------------------------------------------------------------------- |
| 17 | // Shared state via context | 17 | // Context |
| 18 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
| 19 | 19 | ||
| 20 | type Navigate = (to: string) => void; | 20 | type Navigate = (to: string) => void; |
| @@ -24,9 +24,12 @@ const useNav = () => useContext(NavContext); · 3 unmodified lines | |||
| 24 | interface AddState { | 24 | interface AddState { |
| 25 | specUrl: string; | 25 | specUrl: string; |
| 26 | setSpecUrl: (v: string) => void; | 26 | setSpecUrl: (v: string) => void; |
| 27 | preview: Result.Result<SpecPreview, unknown> | null; | 27 | preview: SpecPreview | null; |
| 28 | doPreview: (spec: string) => void; | 28 | setPreview: (p: SpecPreview | null) => void; |
| 29 | doAdd: (spec: string, baseUrl?: string, headers?: Record<string, unknown>) => void; | 29 | loading: boolean; |
| 30 | setLoading: (v: boolean) => void; | ||
| 31 | error: string | null; | ||
| 32 | setError: (v: string | null) => void; | ||
| 30 | onComplete: () => void; | 33 | onComplete: () => void; |
| 31 | onCancel: () => void; | 34 | onCancel: () => void; |
| 32 | } | 35 | } |
| @@ -39,30 +42,57 @@ const useAddState = () => useContext(AddStateContext); · 6 unmodified lines | |||
| 39 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
| 40 | 43 | ||
| 41 | function UrlStep() { | 44 | function UrlStep() { |
| 42 | const { specUrl, setSpecUrl, onCancel, doPreview } = useAddState(); | 45 | const { specUrl, setSpecUrl, onCancel, setPreview, setLoading, setError, loading } = useAddState(); |
| 43 | const nav = useNav(); | 46 | const nav = useNav(); |
| 47 | const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promise" }); | ||
| 44 | 48 | ||
| 45 | const handleNext = () => { | 49 | const handleNext = async () => { |
| 46 | doPreview(specUrl); | 50 | setLoading(true); |
| 47 | nav("/auth"); | 51 | setError(null); |
| 52 | try { | ||
| 53 | const result = await doPreview({ | ||
| 54 | path: { scopeId: "default" as never }, | ||
| 55 | payload: { spec: specUrl }, | ||
| 56 | }); | ||
| 57 | setPreview(result); | ||
| 58 | nav("/auth"); | ||
| 59 | } catch (e) { | ||
| 60 | setError(e instanceof Error ? e.message : "Failed to parse spec"); | ||
| 61 | } finally { | ||
| 62 | setLoading(false); | ||
| 63 | } | ||
| 48 | }; | 64 | }; |
| 49 | 65 | ||
| 50 | return ( | 66 | return ( |
| 51 | <div> | 67 | <div className="space-y-4"> |
| 52 | <label> | 68 | <div> |
| 53 | Spec URL or paste JSON/YAML: | 69 | <label className="block text-sm font-medium text-foreground mb-1.5"> |
| 70 | OpenAPI Spec | ||
| 71 | </label> | ||
| 54 | <textarea | 72 | <textarea |
| 55 | value={specUrl} | 73 | value={specUrl} |
| 56 | onChange={(e) => setSpecUrl((e.target as HTMLTextAreaElement).value)} | 74 | onChange={(e) => setSpecUrl((e.target as HTMLTextAreaElement).value)} |
| 57 | placeholder={'https://api.example.com/openapi.json\n\nor paste spec content here...'} | 75 | placeholder={"https://api.example.com/openapi.json\n\nor paste spec content here..."} |
| 58 | rows={6} | 76 | rows={6} |
| 59 | style={{ width: "100%", fontFamily: "monospace", fontSize: "0.85rem" }} | 77 | className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-y" |
| 60 | /> | 78 | /> |
| 61 | </label> | 79 | <p className="mt-1 text-xs text-muted-foreground"> |
| 62 | <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}> | 80 | Paste a URL or the full OpenAPI JSON/YAML content. |
| 63 | <button onClick={onCancel}>Cancel</button> | 81 | </p> |
| 64 | <button disabled={!specUrl.trim()} onClick={handleNext}> | 82 | </div> |
| 65 | Next | 83 | <div className="flex gap-2"> |
| 84 | <button | ||
| 85 | onClick={onCancel} | ||
| 86 | className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent" | ||
| 87 | > | ||
| 88 | Cancel | ||
| 89 | </button> | ||
| 90 | <button | ||
| 91 | disabled={!specUrl.trim() || loading} | ||
| 92 | onClick={handleNext} | ||
| 93 | className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed" | ||
| 94 | > | ||
| 95 | {loading ? "Analyzing…" : "Next"} | ||
| 66 | </button> | 96 | </button> |
| 67 | </div> | 97 | </div> |
| 68 | </div> | 98 | </div> |
| @@ -70,64 +100,128 @@ function UrlStep() { · 1 unmodified line | |||
| 70 | } | 100 | } |
| 71 | 101 | ||
| 72 | function AuthStep() { | 102 | function AuthStep() { |
| 73 | const { preview } = useAddState(); | 103 | const { preview, error: stateError } = useAddState(); |
| 74 | const nav = useNav(); | 104 | const nav = useNav(); |
| 75 | 105 | ||
| 76 | return ( | 106 | return ( |
| 77 | <div> | 107 | <div className="space-y-4"> |
| 78 | {preview && Result.match(preview, { | 108 | {preview && ( |
| 79 | onInitial: () => <p>Analyzing spec…</p>, | 109 | <div className="rounded-lg border border-border bg-card p-4"> |
| 80 | onSuccess: ({ value }) => ( | 110 | <div className="flex items-start justify-between"> |
| 81 | <div> | 111 | <div> |
| 82 | <h4>{Option.getOrElse(value.title, () => "API")} {Option.getOrElse(value.version, () => "")}</h4> | 112 | <p className="text-sm font-medium text-card-foreground"> |
| 83 | <p>{value.operationCount} operations found</p> | 113 | {Option.getOrElse(preview.title, () => "API")} |
| 84 | {value.headerPresets.length > 0 && ( | 114 | </p> |
| 85 | <div> | 115 | <p className="text-xs text-muted-foreground"> |
| 86 | <p>Authentication options:</p> | 116 | {Option.getOrElse(preview.version, () => "")} · {preview.operationCount} operations |
| 87 | <ul> | 117 | </p> |
| 88 | {value.headerPresets.map((p, i) => ( | 118 | </div> |
| 89 | <li key={i}>{p.label}</li> | ||
| 90 | ))} | ||
| 91 | </ul> | ||
| 92 | </div> | ||
| 93 | )} | ||
| 94 | </div> | 119 | </div> |
| 95 | ), | 120 | |
| 96 | onFailure: () => <p style={{ color: "red" }}>Failed to parse spec</p>, | 121 | {preview.tags.length > 0 && ( |
| 97 | })} | 122 | <div className="mt-3 flex flex-wrap gap-1"> |
| 98 | <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}> | 123 | {preview.tags.slice(0, 10).map((tag) => ( |
| 99 | <button onClick={() => nav("/")}>Back</button> | 124 | <span |
| 100 | <button onClick={() => nav("/confirm")}>Next</button> | 125 | key={tag} |
| 126 | className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground" | ||
| 127 | > | ||
| 128 | {tag} | ||
| 129 | </span> | ||
| 130 | ))} | ||
| 131 | {preview.tags.length > 10 && ( | ||
| 132 | <span className="text-[10px] text-muted-foreground"> | ||
| 133 | +{preview.tags.length - 10} more | ||
| 134 | </span> | ||
| 135 | )} | ||
| 136 | </div> | ||
| 137 | )} | ||
| 138 | |||
| 139 | {preview.headerPresets.length > 0 && ( | ||
| 140 | <div className="mt-3 border-t border-border pt-3"> | ||
| 141 | <p className="text-xs font-medium text-muted-foreground mb-1.5"> | ||
| 142 | Authentication | ||
| 143 | </p> | ||
| 144 | {preview.headerPresets.map((p, i) => ( | ||
| 145 | <div key={i} className="flex items-center gap-2 py-1"> | ||
| 146 | <span className="size-1.5 rounded-full bg-primary/50" /> | ||
| 147 | <span className="text-xs text-foreground">{p.label}</span> | ||
| 148 | </div> | ||
| 149 | ))} | ||
| 150 | </div> | ||
| 151 | )} | ||
| 152 | </div> | ||
| 153 | )} | ||
| 154 | |||
| 155 | {stateError && ( | ||
| 156 | <p className="text-sm text-destructive">{stateError}</p> | ||
| 157 | )} | ||
| 158 | |||
| 159 | <div className="flex gap-2"> | ||
| 160 | <button | ||
| 161 | onClick={() => nav("/")} | ||
| 162 | className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent" | ||
| 163 | > | ||
| 164 | Back | ||
| 165 | </button> | ||
| 166 | <button | ||
| 167 | onClick={() => nav("/confirm")} | ||
| 168 | className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" | ||
| 169 | > | ||
| 170 | Next | ||
| 171 | </button> | ||
| 101 | </div> | 172 | </div> |
| 102 | </div> | 173 | </div> |
| 103 | ); | 174 | ); |
| 104 | } | 175 | } |
| 105 | 176 | ||
| 106 | function ConfirmStep() { | 177 | function ConfirmStep() { |
| 107 | const { specUrl, doAdd, onComplete } = useAddState(); | 178 | const { specUrl, preview, onComplete, setError, error } = useAddState(); |
| 108 | const [adding, setAdding] = useState(false); | ||
| 109 | const [error, setError] = useState<string | null>(null); | ||
| 110 | const nav = useNav(); | 179 | const nav = useNav(); |
| 180 | const [adding, setAdding] = useState(false); | ||
| 181 | const doAdd = useAtomSet(addOpenApiSpec, { mode: "promise" }); | ||
| 111 | 182 | ||
| 112 | const handleAdd = async () => { | 183 | const handleAdd = async () => { |
| 113 | setAdding(true); | 184 | setAdding(true); |
| 114 | setError(null); | 185 | setError(null); |
| 115 | try { | 186 | try { |
| 116 | doAdd(specUrl); | 187 | await doAdd({ |
| 188 | path: { scopeId: "default" as never }, | ||
| 189 | payload: { spec: specUrl }, | ||
| 190 | }); | ||
| 117 | onComplete(); | 191 | onComplete(); |
| 118 | } catch (e) { | 192 | } catch (e) { |
| 119 | setError(e instanceof Error ? e.message : String(e)); | 193 | setError(e instanceof Error ? e.message : "Failed to add source"); |
| 120 | setAdding(false); | 194 | setAdding(false); |
| 121 | } | 195 | } |
| 122 | }; | 196 | }; |
| 123 | 197 | ||
| 124 | return ( | 198 | return ( |
| 125 | <div> | 199 | <div className="space-y-4"> |
| 126 | <p>Ready to add spec.</p> | 200 | <div className="rounded-lg border border-border bg-card p-4"> |
| 127 | {error && <p style={{ color: "red" }}>{error}</p>} | 201 | <p className="text-sm font-medium text-card-foreground">Ready to add source</p> |
| 128 | <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}> | 202 | <p className="mt-1 text-xs text-muted-foreground"> |
| 129 | <button onClick={() => nav("/auth")} disabled={adding}>Back</button> | 203 | {preview |
| 130 | <button onClick={handleAdd} disabled={adding}> | 204 | ? `${Option.getOrElse(preview.title, () => "API")} — ${preview.operationCount} operations will be registered as tools.` |
| 205 | : `Source from: ${specUrl.slice(0, 60)}${specUrl.length > 60 ? "…" : ""}` | ||
| 206 | } | ||
| 207 | </p> | ||
| 208 | </div> | ||
| 209 | |||
| 210 | {error && <p className="text-sm text-destructive">{error}</p>} | ||
| 211 | |||
| 212 | <div className="flex gap-2"> | ||
| 213 | <button | ||
| 214 | onClick={() => nav("/auth")} | ||
| 215 | disabled={adding} | ||
| 216 | className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent disabled:opacity-50" | ||
| 217 | > | ||
| 218 | Back | ||
| 219 | </button> | ||
| 220 | <button | ||
| 221 | onClick={handleAdd} | ||
| 222 | disabled={adding} | ||
| 223 | className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed" | ||
| 224 | > | ||
| 131 | {adding ? "Adding…" : "Add Source"} | 225 | {adding ? "Adding…" : "Add Source"} |
| 132 | </button> | 226 | </button> |
| 133 | </div> | 227 | </div> |
| @@ -144,16 +238,15 @@ export default function AddOpenApiSource(props: { · 10 unmodified lines | |||
| 144 | onCancel: () => void; | 238 | onCancel: () => void; |
| 145 | }) { | 239 | }) { |
| 146 | const [specUrl, setSpecUrl] = useState(""); | 240 | const [specUrl, setSpecUrl] = useState(""); |
| 147 | const [preview] = useState<Result.Result<SpecPreview, unknown> | null>(null); | 241 | const [preview, setPreview] = useState<SpecPreview | null>(null); |
| 148 | 242 | const [loading, setLoading] = useState(false); | |
| 149 | const doPreviewMutation = useAtomSet(previewOpenApiSpec, { mode: "promise" }); | 243 | const [error, setError] = useState<string | null>(null); |
| 150 | const doAddMutation = useAtomSet(addOpenApiSpec, { mode: "promise" }); | ||
| 151 | 244 | ||
| 152 | const router = useMemo(() => { | 245 | const router = useMemo(() => { |
| 153 | const rootRoute = createRootRoute({ | 246 | const rootRoute = createRootRoute({ |
| 154 | component: () => ( | 247 | component: () => ( |
| 155 | <div> | 248 | <div> |
| 156 | <h3>Add OpenAPI Source</h3> | 249 | <h1 className="text-xl font-semibold text-foreground mb-4">Add OpenAPI Source</h1> |
| 157 | <Outlet /> | 250 | <Outlet /> |
| 158 | </div> | 251 | </div> |
| 159 | ), | 252 | ), |
| @@ -176,18 +269,10 @@ export default function AddOpenApiSource(props: { · 16 unmodified lines | |||
| 176 | }; | 269 | }; |
| 177 | 270 | ||
| 178 | const state: AddState = { | 271 | const state: AddState = { |
| 179 | specUrl, | 272 | specUrl, setSpecUrl, |
| 180 | setSpecUrl, | 273 | preview, setPreview, |
| 181 | preview, | 274 | loading, setLoading, |
| 182 | doPreview: (spec) => { | 275 | error, setError, |
| 183 | void doPreviewMutation({ path: { scopeId: "default" as never }, payload: { spec } }); | ||
| 184 | }, | ||
| 185 | doAdd: (spec, baseUrl, headers) => { | ||
| 186 | void doAddMutation({ | ||
| 187 | path: { scopeId: "default" as never }, | ||
| 188 | payload: { spec, baseUrl, headers }, | ||
| 189 | }); | ||
| 190 | }, | ||
| 191 | onComplete: props.onComplete, | 276 | onComplete: props.onComplete, |
| 192 | onCancel: props.onCancel, | 277 | onCancel: props.onCancel, |
| 193 | }; | 278 | }; |