diff --git a/packages/workflow-dashboard/package.json b/packages/workflow-dashboard/package.json
index 0ec2e1e..c6027d7 100644
--- a/packages/workflow-dashboard/package.json
+++ b/packages/workflow-dashboard/package.json
@@ -17,6 +17,7 @@
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-markdown": "^10.1.0",
+ "react-router": "^7.15.1",
"shiki": "^4.0.2"
},
"devDependencies": {
diff --git a/packages/workflow-dashboard/src/app.tsx b/packages/workflow-dashboard/src/app.tsx
index b96d11e..e0710d1 100644
--- a/packages/workflow-dashboard/src/app.tsx
+++ b/packages/workflow-dashboard/src/app.tsx
@@ -1,74 +1,35 @@
import { useState } from "react";
+import { Navigate, Outlet, useParams } from "react-router";
import { clearApiKey, hasApiKey } from "./api.ts";
-import { LoginPage } from "./components/login.tsx";
import { RunDialog } from "./components/run-dialog.tsx";
import { Sidebar } from "./components/sidebar.tsx";
import { StatusBar } from "./components/status-bar.tsx";
-import { ThreadDetail } from "./components/thread-detail.tsx";
-import { ThreadList } from "./components/thread-list.tsx";
-import { WorkflowDetail } from "./components/workflow-detail.tsx";
-import { WorkflowList } from "./components/workflow-list.tsx";
-import { useHashRoute } from "./use-hash-route.ts";
-export function App() {
+export function Layout() {
const [authed, setAuthed] = useState(hasApiKey());
- const { view, client, threadId, workflowName, setView, setClient, setThreadId, setWorkflowName } =
- useHashRoute();
+ const { client } = useParams();
const [showRun, setShowRun] = useState(false);
if (!authed) {
- return setAuthed(true)} />;
+ return ;
}
return (
{
clearApiKey();
setAuthed(false);
}}
/>
- setShowRun(true)} />
+ setShowRun(true)} />
- {!client && (
-
-
- Select an client from the sidebar to get started.
-
-
- )}
- {client && view === "threads" && threadId === null && (
-
- )}
- {client && view === "threads" && threadId !== null && (
-
setThreadId(null)} />
- )}
- {client && view === "workflows" && workflowName === null && (
-
- )}
- {client && view === "workflows" && workflowName !== null && (
- setWorkflowName(null)}
- />
- )}
+
{showRun && client && (
- setShowRun(false)}
- onCreated={(id) => {
- setShowRun(false);
- setThreadId(id);
- }}
- />
+ setShowRun(false)} />
)}
);
diff --git a/packages/workflow-dashboard/src/components/client-redirect.tsx b/packages/workflow-dashboard/src/components/client-redirect.tsx
new file mode 100644
index 0000000..a38ae34
--- /dev/null
+++ b/packages/workflow-dashboard/src/components/client-redirect.tsx
@@ -0,0 +1,27 @@
+import { Navigate } from "react-router";
+import { listClients } from "../api.ts";
+import { useFetch } from "../hooks.ts";
+
+export function ClientRedirect() {
+ const { status, data } = useFetch(() => listClients(), []);
+
+ if (status === "loading") {
+ return (
+
+ );
+ }
+
+ if (status === "ok" && data.length > 0) {
+ return ;
+ }
+
+ return (
+
+
+ Select a client from the sidebar to get started.
+
+
+ );
+}
diff --git a/packages/workflow-dashboard/src/components/login.tsx b/packages/workflow-dashboard/src/components/login.tsx
index 6ee1afb..9fff4b2 100644
--- a/packages/workflow-dashboard/src/components/login.tsx
+++ b/packages/workflow-dashboard/src/components/login.tsx
@@ -1,11 +1,9 @@
import { useState } from "react";
+import { useNavigate } from "react-router";
import { setApiKey } from "../api.ts";
-type Props = {
- onLogin: () => void;
-};
-
-export function LoginPage({ onLogin }: Props) {
+export function LoginPage() {
+ const navigate = useNavigate();
const [key, setKey] = useState("");
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
@@ -40,7 +38,7 @@ export function LoginPage({ onLogin }: Props) {
}
setApiKey(key.trim());
- onLogin();
+ navigate("/", { replace: true });
}
return (
diff --git a/packages/workflow-dashboard/src/components/run-dialog.tsx b/packages/workflow-dashboard/src/components/run-dialog.tsx
index 843ca13..ba32fc5 100644
--- a/packages/workflow-dashboard/src/components/run-dialog.tsx
+++ b/packages/workflow-dashboard/src/components/run-dialog.tsx
@@ -1,14 +1,15 @@
import { useState } from "react";
+import { useNavigate } from "react-router";
import { listWorkflows, runThread } from "../api.ts";
import { useFetch } from "../hooks.ts";
type Props = {
client: string;
onClose: () => void;
- onCreated: (threadId: string) => void;
};
-export function RunDialog({ client, onClose, onCreated }: Props) {
+export function RunDialog({ client, onClose }: Props) {
+ const navigate = useNavigate();
const workflows = useFetch(() => listWorkflows(client), [client]);
const [workflow, setWorkflow] = useState("");
const [prompt, setPrompt] = useState("");
@@ -22,7 +23,8 @@ export function RunDialog({ client, onClose, onCreated }: Props) {
setError(null);
try {
const result = await runThread(client, workflow, prompt);
- onCreated(result.threadId);
+ onClose();
+ navigate(`/${client}/threads/${result.threadId}`);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setSubmitting(false);
diff --git a/packages/workflow-dashboard/src/components/sidebar.tsx b/packages/workflow-dashboard/src/components/sidebar.tsx
index b23aa94..c702597 100644
--- a/packages/workflow-dashboard/src/components/sidebar.tsx
+++ b/packages/workflow-dashboard/src/components/sidebar.tsx
@@ -1,27 +1,21 @@
-import { useEffect } from "react";
+import { useLocation, useNavigate, useParams } from "react-router";
import type { ClientEndpoint } from "../api.ts";
import { listClients } from "../api.ts";
import { useFetch } from "../hooks.ts";
type Props = {
- view: "threads" | "workflows";
- client: string | null;
- onViewChange: (v: "threads" | "workflows") => void;
- onClientChange: (a: string | null) => void;
onLogout: () => void;
};
-export function Sidebar({ view, client, onViewChange, onClientChange, onLogout }: Props) {
+export function Sidebar({ onLogout }: Props) {
+ const { client } = useParams();
+ const navigate = useNavigate();
+ const location = useLocation();
const { status, data } = useFetch(() => listClients(), []);
const clients: ClientEndpoint[] = status === "ok" ? data : [];
- // Auto-select first client when none is selected
- useEffect(() => {
- if (client === null && clients.length > 0) {
- onClientChange(clients[0].name);
- }
- }, [client, clients, onClientChange]);
+ const view = location.pathname.includes("/workflows") ? "workflows" : "threads";
const viewItems = [
{ key: "threads" as const, label: "Threads", icon: "⚡" },
@@ -42,7 +36,6 @@ export function Sidebar({ view, client, onViewChange, onClientChange, onLogout }
- {/* Client selector */}
- {/* View navigation */}