SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
7.4 KB · 156 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import Link from "next/link";4import { CheckCircle2, XCircle, CircleStop, ChevronRight } from "lucide-react";5import { Badge } from "@/components/ui/badge";6import { Tooltip } from "@/components/ui/tooltip";7import { ProviderIcon } from "@/components/brand/provider-icon";8import { providerName } from "@/lib/client/providers";9import { formatUsd, formatTokens, formatMs, formatRelative, cn } from "@/lib/utils";10import { formatDateTime } from "@/lib/client/humanize";11import type { RecentRecord } from "./types";1213function StatusBadge({ r }: { r: RecentRecord }) {14  if (r.status === "ok")15    return (16      <Badge variant="success">17        <CheckCircle2 /> ok18      </Badge>19    );20  if (r.status === "stopped")21    return (22      <Badge>23        <CircleStop /> stopped24      </Badge>25    );26  return (27    <Tooltip content={r.errorCode ?? undefined}>28      <Badge variant="danger">29        <XCircle /> {r.errorCode?.toLowerCase().replace(/_/g, " ") ?? "error"}30      </Badge>31    </Tooltip>32  );33}3435/** Phone: stacked rows (tap → conversation). Desktop (md+): table. */36export function RecentActivity({ rows, modelLabel, className }: { rows: RecentRecord[]; modelLabel: (key: string) => string; className?: string }) {37  return (38    <section className={cn("panel overflow-hidden", className)} aria-label="Recent requests">39      <header className="flex items-center justify-between px-4 py-3">40        <h3 className="text-[13px] font-semibold tracking-tight">Recent requests</h3>41        <span className="text-[11px] text-fg-subtle">Last {rows.length}</span>42      </header>43      {rows.length === 0 ? (44        <p className="px-4 pb-6 text-center text-xs text-fg-subtle">No requests in this period.</p>45      ) : (46        <>47          {/* Phone rows */}48          <ul className="divide-y divide-hairline md:hidden">49            {rows.map((r) => {50              const inner = (51                <>52                  <ProviderIcon provider={r.provider} size={16} className="mt-0.5" />53                  <div className="min-w-0 flex-1">54                    <div className="flex items-center justify-between gap-2">55                      <p className="truncate text-[14px] font-medium">{modelLabel(r.modelKey)}</p>56                      <span className="shrink-0 font-mono text-[13px] tabular-nums">{formatUsd(r.costUsd, { precise: true })}</span>57                    </div>58                    <p className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-[12px] text-fg-muted">59                      <span>{formatRelative(r.createdAt)}</span>60                      <span aria-hidden>·</span>61                      <span className="capitalize">{r.kind}</span>62                      <span aria-hidden>·</span>63                      <span className="font-mono tabular-nums">64                        {formatTokens(r.inputTokens)}/{formatTokens(r.outputTokens)}65                      </span>66                      {r.latencyMs ? (67                        <>68                          <span aria-hidden>·</span>69                          <span className="font-mono tabular-nums">{formatMs(r.latencyMs)}</span>70                        </>71                      ) : null}72                    </p>73                    <div className="mt-1.5 flex items-center justify-between gap-2">74                      <StatusBadge r={r} />75                      {r.conversationTitle ? <span className="truncate text-[11px] text-fg-subtle">{r.conversationTitle}</span> : null}76                    </div>77                  </div>78                  {r.conversationId ? <ChevronRight className="mt-3 size-4 shrink-0 text-fg-subtle" /> : null}79                </>80              );81              const cls = "flex min-h-[64px] items-start gap-3 px-4 py-3 active:bg-bg-muted/60";82              return (83                <li key={r.id}>84                  {r.conversationId && r.kind !== "arena" ? (85                    <Link href={`/app/chat/${r.conversationId}`} className={cls}>86                      {inner}87                    </Link>88                  ) : (89                    <div className={cls}>{inner}</div>90                  )}91                </li>92              );93            })}94          </ul>9596          {/* Desktop table */}97          <div className="hidden overflow-x-auto md:block">98            <table className="w-full text-[13px]">99              <thead>100                <tr className="border-y border-border bg-bg-subtle/60 text-left text-[11px] font-medium uppercase tracking-wide text-fg-subtle">101                  <th className="px-4 py-2">Time</th>102                  <th className="px-3 py-2">Model</th>103                  <th className="px-3 py-2">Conversation</th>104                  <th className="px-3 py-2">Kind</th>105                  <th className="px-3 py-2 text-right">Tokens in / out</th>106                  <th className="px-3 py-2 text-right">Est. cost</th>107                  <th className="px-3 py-2 text-right">TTFT</th>108                  <th className="px-3 py-2 text-right">Latency</th>109                  <th className="px-4 py-2 text-right">Status</th>110                </tr>111              </thead>112              <tbody>113                {rows.map((r) => (114                  <tr key={r.id} className="border-b border-hairline last:border-b-0 hover:bg-bg-subtle/50">115                    <td className="whitespace-nowrap px-4 py-2 text-fg-muted">116                      <Tooltip content={formatDateTime(r.createdAt, { year: "numeric", second: "2-digit" })}>117                        <span>{formatRelative(r.createdAt)}</span>118                      </Tooltip>119                    </td>120                    <td className="px-3 py-2">121                      <span className="flex items-center gap-1.5">122                        <ProviderIcon provider={r.provider} size={13} />123                        <span className="truncate font-medium">{modelLabel(r.modelKey)}</span>124                        <span className="hidden text-[11px] text-fg-subtle xl:inline">{providerName(r.provider)}</span>125                      </span>126                    </td>127                    <td className="max-w-[220px] truncate px-3 py-2 text-fg-muted">128                      {r.conversationId && r.kind !== "arena" ? (129                        <Link href={`/app/chat/${r.conversationId}`} className="hover:text-fg hover:underline">130                          {r.conversationTitle ?? "Open"}131                        </Link>132                      ) : (133                        <span className="text-fg-subtle">{r.kind === "arena" ? "Arena" : "—"}</span>134                      )}135                    </td>136                    <td className="px-3 py-2 capitalize text-fg-muted">{r.kind}</td>137                    <td className="px-3 py-2 text-right font-mono tabular-nums text-fg-muted">138                      {formatTokens(r.inputTokens)} / {formatTokens(r.outputTokens)}139                    </td>140                    <td className="px-3 py-2 text-right font-mono tabular-nums">{formatUsd(r.costUsd, { precise: true })}</td>141                    <td className="px-3 py-2 text-right font-mono tabular-nums text-fg-muted">{formatMs(r.ttftMs)}</td>142                    <td className="px-3 py-2 text-right font-mono tabular-nums text-fg-muted">{formatMs(r.latencyMs)}</td>143                    <td className="px-4 py-2 text-right">144                      <StatusBadge r={r} />145                    </td>146                  </tr>147                ))}148              </tbody>149            </table>150          </div>151        </>152      )}153    </section>154  );155}156