SPB Git

spb/autoscholar

Public
4.2 KB · 121 lines tsx
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="title-2 mt-1">Runs</h1>34        </div>35        <nav aria-label="Filter" className="flex flex-wrap 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      <ul className="divide-y hairline md:hidden">53        {runs.length === 0 && <li className="muted py-8 text-center text-sm">No runs match.</li>}54        {runs.map((r) => (55          <li key={r.id} className="py-3">56            <Link href={`/admin/runs/${r.id}`} className="block font-medium no-underline hover:underline">57              {r.title}58            </Link>59            <div className="mt-1 flex flex-wrap items-center gap-2 text-xs">60              <StatusBadge status={r.status} />61              <span className="font-mono">{r.currentStage ?? "—"}</span>62              <span className="muted">{modeLabel(r.researchMode)}</span>63              <span className="muted">64                {r.currentAttemptNo}/{r.maxAttempts}65              </span>66              <span className="muted">{fmtUsd(r.totalCostUsd)}</span>67              <span className="subtle">{ago(r.updatedAt)}</span>68            </div>69          </li>70        ))}71      </ul>72      <div className="table-wrap hidden md:block">73        <table className="table">74          <thead>75            <tr>76              <th>Title</th>77              <th>Mode</th>78              <th>Status</th>79              <th>Stage</th>80              <th>Attempt</th>81              <th>Cost</th>82              <th>Auto-publish</th>83              <th>Updated</th>84            </tr>85          </thead>86          <tbody>87            {runs.length === 0 && (88              <tr>89                <td colSpan={8} className="muted py-8 text-center">90                  No runs match.91                </td>92              </tr>93            )}94            {runs.map((r) => (95              <tr key={r.id}>96                <td>97                  <Link href={`/admin/runs/${r.id}`}>{r.title}</Link>98                  <p className="subtle font-mono text-[10px]">{r.id}</p>99                </td>100                <td>{modeLabel(r.researchMode)}</td>101                <td>102                  <StatusBadge status={r.status} />103                </td>104                <td className="font-mono text-xs">{r.currentStage ?? "—"}</td>105                <td>106                  {r.currentAttemptNo}/{r.maxAttempts}107                </td>108                <td>109                  {fmtUsd(r.totalCostUsd)} <span className="subtle text-xs">/ {fmtUsd(r.maxBudgetUsd)}</span>110                </td>111                <td>{r.autoPublish ? "yes" : "no"}</td>112                <td className="subtle text-xs">{ago(r.updatedAt)}</td>113              </tr>114            ))}115          </tbody>116        </table>117      </div>118    </div>119  );120}121
Files · main
Menu

Theme