"use client"; /* Forge home screens: Dashboard, Project Overview, Onboarding wizard. */ import { useState } from "react"; import { Icon } from "../icons"; import { Sparkline, StatusPill, Tile, Field, Toggle, EmptyState } from "../primitives"; import { DashboardStats } from "@/lib/api"; import { fmtUSD } from "@/lib/data"; const fmtLatencyMs = (ms: number) => (ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`); export interface ProjectCard { id: string; name: string; slug: string; status: string; workflows: number; tools: number; runs7d: number; spark: number[]; edited: string; } /* ============ DASHBOARD ============ */ export function DashboardScreen({ projects = [], loaded = false, stats = null, onOpenProject, onNewProject, onDeleteProject, }: { projects?: ProjectCard[]; loaded?: boolean; // Fetched once by the parent (App) and shared - avoids a second /stats/dashboard call. stats?: DashboardStats | null; onOpenProject: (id: string) => void; onNewProject: () => void; onDeleteProject?: (project: { id: string; name: string }) => Promise | void; }) { const empty = loaded && projects.length === 0; const [deletingId, setDeletingId] = useState(null); const fmtLatency = (ms: number) => (ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`); const kpis = [ { label: "Runs · 7 days", value: stats ? stats.runs_7d.toLocaleString() : "-", sub: stats ? `${stats.total_runs.toLocaleString()} all-time` : "" }, { label: "Success rate", value: stats && stats.runs_7d ? `${stats.success_rate}%` : "-", sub: "completed runs" }, { label: "Avg latency", value: stats && stats.runs_7d ? fmtLatency(stats.avg_latency_ms) : "-", sub: "per run" }, { label: "Spend · 7 days", value: stats ? fmtUSD(stats.spend_7d) : "-", sub: "tracked cost" }, ]; return (
Welcome to Forge
Self-hosted agent platform · {projects.length} project{projects.length === 1 ? "" : "s"}
{empty ? (
New project} />
) : !loaded ? (
Loading…
) : ( <>
{kpis.map((k, i) => (
{k.label}
{k.value}
{k.sub}
))}
Projects
{projects.map((p) => (
onOpenProject(p.id)}>
{p.name}
{p.workflows} workflows{p.tools} toolsedited {p.edited}
{p.runs7d.toLocaleString()} runs / 7d
{onDeleteProject && ( )}
))}
Recent runs
live
{(stats?.recent || []).map((r, i, arr) => (
{r.workflow}
{r.project} · {r.status}
{r.tokens.toLocaleString()} tok
{r.latency_ms}ms{r.started_at ? " · " + r.started_at.slice(11, 16) : ""}
))} {(!stats || stats.recent.length === 0) && (
No runs yet. Run a workflow in the Playground.
)}
{/* All-time usage by project (incl. Forge Assistant share) */}
Reports
{(stats?.reports || []).map((r, i) => ( ))} {(!stats || !stats.reports?.length) && }
ProjectRunsTokensAvg latencyAssistantTotal cost
{r.project}
{r.runs.toLocaleString()} {r.tokens.toLocaleString()} {fmtLatencyMs(r.avg_latency_ms)} {r.assistant_turns ? `${fmtUSD(r.assistant_cost_usd)} · ${r.assistant_turns} turns` : "-"} {fmtUSD(r.cost_usd)}
No usage yet.
)}
); } /* ============ ONBOARDING WIZARD ============ */ export function OnboardingScreen({ onCreate, onCancel }: { onCreate: (p: { name: string; template: string; keys: Record }) => void; onCancel: () => void }) { const [step, setStep] = useState(0); const [name, setName] = useState(""); const [tmpl, setTmpl] = useState("blank"); const [busy, setBusy] = useState(false); const [models, setModels] = useState>({ anthropic: true, openai: false, google: false }); const [keys, setKeys] = useState>({}); const provId: Record = { anthropic: "anthropic", openai: "openai", google: "google_genai" }; const steps = ["Project", "Models", "Create"]; const templates = [ { id: "blank", name: "Blank canvas", desc: "Start from an empty graph", icon: "workflows" }, { id: "support", name: "Support agent", desc: "Router → agent → tools → HITL", icon: "agents" }, { id: "rag", name: "RAG Q&A", desc: "Retrieval + grounded answers", icon: "knowledge" }, { id: "mcp", name: "MCP toolbox", desc: "Expose tools over MCP", icon: "connect" }, ]; return (
New project
{steps.map((s, i) => (
{i < step ? : i + 1}
{s} {i < steps.length - 1 &&
}
))}
{step === 0 && (
setName(e.target.value)} placeholder="e.g. Customer Support" />
Start from
{templates.map((t) => ( ))}
)} {step === 1 && (
Connect at least one model provider. Keys are stored encrypted in your secret store - they never leave your instance.
{[["anthropic", "Anthropic", "claude-sonnet-4-6, haiku-4-2"], ["openai", "OpenAI", "gpt-5.4, gpt-5.4-mini"], ["google", "Google", "gemini-3.1-pro, 3.5-flash"]].map((p) => (
{p[1]}
{p[2]}
{models[p[0]] && setKeys((k) => ({ ...k, [provId[p[0]]]: e.target.value }))} />} setModels((m) => ({ ...m, [p[0]]: v }))} />
))}
)} {step === 2 && (
Create “{name || "Untitled"}”
We’ll create an empty project so you can register tools, add knowledge, and build your first workflow from scratch.
)}
); }