spb/autoscholar
Public
1import { listRuns } from "@autoscholar/core";2import type { RunStatus } from "@autoscholar/schemas";3import Link from "next/link";4import { StatusBadge } from "@/components/StatusBadge";5import { ago, fmtUsd, modeLabel } from "@/lib/format";6import { db } from "@/lib/server/db";78export const dynamic = "force-dynamic";9const GROUPS: Record<string, string[]> = {10 active: [11 "queued",12 "provisioning",13 "running",14 "quality_check",15 "revising",16 "retrying",17 "publishing",18 "cancelling",19 ],20 blocked: ["quality_failed", "awaiting_approval", "ready_to_publish"],21 done: ["published", "failed", "cancelled"],22};2324export default async function RunsPage({ searchParams }: { searchParams: Promise<{ filter?: string }> }) {25 const { filter } = await searchParams;26 const statuses = filter && GROUPS[filter] ? (GROUPS[filter] as RunStatus[]) : undefined;27 const runs = await listRuns(db(), { status: statuses, limit: 200 });28 return (29 <div className="space-y-6">30 <header className="flex flex-wrap items-end justify-between gap-3">31 <div>32 <p className="eyebrow">Orchestration</p>33 <h1 className="serif mt-1 text-2xl font-semibold">Runs</h1>34 </div>35 <nav aria-label="Filter" className="flex gap-1">36 {[37 ["", "All"],38 ["active", "Active"],39 ["blocked", "Needs attention"],40 ["done", "Finished"],41 ].map(([k, l]) => (42 <Link43 key={k}44 href={k ? `/admin/runs?filter=${k}` : "/admin/runs"}45 className={`btn btn-sm ${(filter ?? "") === k ? "btn-primary" : ""}`}46 >47 {l}48 </Link>49 ))}50 </nav>51 </header>52 <table className="table">53 <thead>54 <tr>55 <th>Title</th>56 <th>Mode</th>57 <th>Status</th>58 <th>Stage</th>59 <th>Attempt</th>60 <th>Cost</th>61 <th>Auto-publish</th>62 <th>Updated</th>63 </tr>64 </thead>65 <tbody>66 {runs.length === 0 && (67 <tr>68 <td colSpan={8} className="muted py-8 text-center">69 No runs match.70 </td>71 </tr>72 )}73 {runs.map((r) => (74 <tr key={r.id}>75 <td>76 <Link href={`/admin/runs/${r.id}`}>{r.title}</Link>77 <p className="subtle font-mono text-[10px]">{r.id}</p>78 </td>79 <td>{modeLabel(r.researchMode)}</td>80 <td>81 <StatusBadge status={r.status} />82 </td>83 <td className="font-mono text-xs">{r.currentStage ?? "—"}</td>84 <td>85 {r.currentAttemptNo}/{r.maxAttempts}86 </td>87 <td>88 {fmtUsd(r.totalCostUsd)} <span className="subtle text-xs">/ {fmtUsd(r.maxBudgetUsd)}</span>89 </td>90 <td>{r.autoPublish ? "yes" : "no"}</td>91 <td className="subtle text-xs">{ago(r.updatedAt)}</td>92 </tr>93 ))}94 </tbody>95 </table>96 </div>97 );98}99