diff --git a/packages/workflow-dashboard/src/components/login.tsx b/packages/workflow-dashboard/src/components/login.tsx
index 9fff4b2..7e6eeb9 100644
--- a/packages/workflow-dashboard/src/components/login.tsx
+++ b/packages/workflow-dashboard/src/components/login.tsx
@@ -1,12 +1,18 @@
+import { AlertCircle, Loader2, Moon, Settings, Sun } from "lucide-react";
import { useState } from "react";
import { useNavigate } from "react-router";
import { setApiKey } from "../api.ts";
+import { useTheme } from "../hooks/use-theme.tsx";
+import { Button } from "./ui/button.tsx";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card.tsx";
+import { Input } from "./ui/input.tsx";
export function LoginPage() {
const navigate = useNavigate();
const [key, setKey] = useState("");
const [error, setError] = useState
(null);
const [loading, setLoading] = useState(false);
+ const { theme, toggleTheme } = useTheme();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
@@ -15,7 +21,6 @@ export function LoginPage() {
setLoading(true);
setError(null);
- // Test the key by hitting the endpoints list
const gatewayUrl = import.meta.env.VITE_GATEWAY_URL || "";
try {
const res = await fetch(`${gatewayUrl}/api/gateway/endpoints`, {
@@ -42,52 +47,55 @@ export function LoginPage() {
}
return (
-
-
+
+ {theme === "dark" ?
:
}
+
+
+
+
+
+ Workflow Dashboard
+
+ Enter your API key to continue
+
+
+
+
+
);
}
diff --git a/packages/workflow-dashboard/src/components/markdown.tsx b/packages/workflow-dashboard/src/components/markdown.tsx
index d369e39..4407e90 100644
--- a/packages/workflow-dashboard/src/components/markdown.tsx
+++ b/packages/workflow-dashboard/src/components/markdown.tsx
@@ -52,19 +52,23 @@ function CodeBlock({ className, children }: { className?: string; children?: Rea
if (html !== null) {
return (
-
+
+ {lang !== "text" && (
+
+ {lang}
+
+ )}
+
+
);
}
return (
-
+
{code}
);
@@ -80,8 +84,7 @@ export function Markdown({ content }: { content: string }) {
if (isInline) {
return (
{children}
@@ -91,7 +94,7 @@ export function Markdown({ content }: { content: string }) {
return {children};
},
p({ children }) {
- return {children}
;
+ return {children}
;
},
ul({ children }) {
return ;
@@ -100,20 +103,25 @@ export function Markdown({ content }: { content: string }) {
return {children}
;
},
h1({ children }) {
- return {children}
;
+ return (
+
+ {children}
+
+ );
},
h2({ children }) {
- return {children}
;
+ return (
+
+ {children}
+
+ );
},
h3({ children }) {
return {children}
;
},
blockquote({ children }) {
return (
-
+
{children}
);
diff --git a/packages/workflow-dashboard/src/components/record-card.tsx b/packages/workflow-dashboard/src/components/record-card.tsx
index 5127135..1c9e007 100644
--- a/packages/workflow-dashboard/src/components/record-card.tsx
+++ b/packages/workflow-dashboard/src/components/record-card.tsx
@@ -1,14 +1,26 @@
+import { CheckCircle2, Clock, MessageSquare, Rocket, User, XCircle } from "lucide-react";
import type { RoleRecord, ThreadRecord, ThreadStartRecord, WorkflowResultRecord } from "../api.ts";
+import { cn } from "../lib/utils.ts";
import { Markdown } from "./markdown.tsx";
+import { Badge } from "./ui/badge.tsx";
+import { Card } from "./ui/card.tsx";
-const ROLE_COLORS: Record = {
- preparer: "#8b5cf6",
- client: "#3b82f6",
- extractor: "#f59e0b",
-};
+const ROLE_HUES = [262, 210, 35, 150, 330, 180, 15, 280, 55, 195, 345, 120, 240, 75, 305];
-function roleColor(role: string): string {
- return ROLE_COLORS[role] ?? "var(--color-accent)";
+function roleHue(role: string): number {
+ let hash = 0;
+ for (let i = 0; i < role.length; i++) {
+ hash = (hash * 31 + role.charCodeAt(i)) | 0;
+ }
+ return ROLE_HUES[Math.abs(hash) % ROLE_HUES.length];
+}
+
+function roleBadgeStyle(role: string): { backgroundColor: string; borderColor: string } {
+ const hue = roleHue(role);
+ return {
+ backgroundColor: `oklch(0.58 0.12 ${hue} / 0.85)`,
+ borderColor: `oklch(0.58 0.12 ${hue} / 0.25)`,
+ };
}
function formatTime(ts: number | null): string | null {
@@ -18,99 +30,86 @@ function formatTime(ts: number | null): string | null {
function StartCard({ record }: { record: ThreadStartRecord }) {
return (
-
+
+
- π
-
- {record.workflow}
-
-
+
+ {record.workflow}
+
{record.status}
-
+
{record.prompt !== null && (
-
-
+
);
}
function RoleMessage({ record, highlighted }: { record: RoleRecord; highlighted: boolean }) {
- const color = roleColor(record.role);
+ const style = roleBadgeStyle(record.role);
return (
-
+
{record.role}
{formatTime(record.timestamp) !== null && (
-
+
+
{formatTime(record.timestamp)}
)}
-
+
);
}
function ResultCard({ record }: { record: WorkflowResultRecord }) {
const success = record.returnCode === 0;
return (
-
- {success ? "β
" : "β"}
+ {success ? (
+
+ ) : (
+
+ )}
{success ? "Completed" : "Failed"}
-
+
exit {record.returnCode}
-
+
{formatTime(record.timestamp) !== null && (
-
+
+
{formatTime(record.timestamp)}
)}
-
+
);
}
diff --git a/packages/workflow-dashboard/src/components/run-dialog.tsx b/packages/workflow-dashboard/src/components/run-dialog.tsx
index ba32fc5..6d94050 100644
--- a/packages/workflow-dashboard/src/components/run-dialog.tsx
+++ b/packages/workflow-dashboard/src/components/run-dialog.tsx
@@ -2,13 +2,25 @@ import { useState } from "react";
import { useNavigate } from "react-router";
import { listWorkflows, runThread } from "../api.ts";
import { useFetch } from "../hooks.ts";
+import { Button } from "./ui/button.tsx";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "./ui/dialog.tsx";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select.tsx";
+import { Textarea } from "./ui/textarea.tsx";
type Props = {
client: string;
- onClose: () => void;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
};
-export function RunDialog({ client, onClose }: Props) {
+export function RunDialog({ client, open, onOpenChange }: Props) {
const navigate = useNavigate();
const workflows = useFetch(() => listWorkflows(client), [client]);
const [workflow, setWorkflow] = useState("");
@@ -23,7 +35,7 @@ export function RunDialog({ client, onClose }: Props) {
setError(null);
try {
const result = await runThread(client, workflow, prompt);
- onClose();
+ onOpenChange(false);
navigate(`/${client}/threads/${result.threadId}`);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
@@ -32,95 +44,54 @@ export function RunDialog({ client, onClose }: Props) {
}
return (
-
-
-
Run Thread on {client}
+
-
+
+
);
}
diff --git a/packages/workflow-dashboard/src/components/sidebar.tsx b/packages/workflow-dashboard/src/components/sidebar.tsx
index c702597..ed278cb 100644
--- a/packages/workflow-dashboard/src/components/sidebar.tsx
+++ b/packages/workflow-dashboard/src/components/sidebar.tsx
@@ -1,13 +1,20 @@
+import { Loader2, LogOut, Moon, Package, Sun, Zap } from "lucide-react";
import { useLocation, useNavigate, useParams } from "react-router";
import type { ClientEndpoint } from "../api.ts";
import { listClients } from "../api.ts";
import { useFetch } from "../hooks.ts";
+import { cn } from "../lib/utils.ts";
+import { Button } from "./ui/button.tsx";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select.tsx";
+import { Separator } from "./ui/separator.tsx";
type Props = {
onLogout: () => void;
+ theme: "light" | "dark";
+ onToggleTheme: () => void;
};
-export function Sidebar({ onLogout }: Props) {
+export function Sidebar({ onLogout, theme, onToggleTheme }: Props) {
const { client } = useParams();
const navigate = useNavigate();
const location = useLocation();
@@ -18,93 +25,107 @@ export function Sidebar({ onLogout }: Props) {
const view = location.pathname.includes("/workflows") ? "workflows" : "threads";
const viewItems = [
- { key: "threads" as const, label: "Threads", icon: "β‘" },
- { key: "workflows" as const, label: "Workflows", icon: "π¦" },
+ { key: "threads" as const, label: "Threads", icon: Zap },
+ { key: "workflows" as const, label: "Workflows", icon: Package },
];
return (
-