omniroute / src /shared /components /ActiveRequestsPanel.tsx
igorkurgin's picture
Deploy OmniRoute to Hugging Face Space
35743bd verified
Raw
History Blame Contribute Delete
7.99 kB
"use client";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
type ActiveRequestRow = {
model: string;
provider: string;
account: string;
startedAt: number;
runningTimeMs: number;
count: number;
clientEndpoint?: string | null;
clientRequest?: unknown;
providerRequest?: unknown;
providerUrl?: string | null;
};
function formatDuration(ms: number): string {
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes <= 0) return `${seconds}s`;
if (minutes < 60) return `${minutes}m ${seconds}s`;
const hours = Math.floor(minutes / 60);
return `${hours}h ${minutes % 60}m`;
}
export default function ActiveRequestsPanel() {
const t = useTranslations("logs");
const [rows, setRows] = useState<ActiveRequestRow[]>([]);
const [loading, setLoading] = useState(true);
const [selectedRow, setSelectedRow] = useState<ActiveRequestRow | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
const res = await fetch("/api/logs/active", { cache: "no-store" });
if (!res.ok) return;
const data = await res.json();
if (!cancelled) {
setRows(Array.isArray(data.activeRequests) ? data.activeRequests : []);
}
} catch (error) {
if (!cancelled) {
console.error("Failed to load active requests:", error);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
load();
const interval = setInterval(load, 3000);
return () => {
cancelled = true;
clearInterval(interval);
};
}, []);
const handleClearAll = async () => {
if (!window.confirm(t("confirmClearActiveRequests") || "Clear all active requests?")) return;
try {
const res = await fetch("/api/logs/active", { method: "DELETE" });
if (res.ok) {
setRows([]);
setSelectedRow(null);
}
} catch (error) {
console.error("Failed to clear active requests:", error);
}
};
if (!loading && rows.length === 0) {
return null;
}
return (
<div className="rounded-xl border border-border bg-surface">
<div className="flex items-center justify-between gap-4 border-b border-border px-4 py-3">
<div>
<h3 className="text-sm font-semibold uppercase tracking-wide text-text-main">
{t("runningRequests")}
</h3>
<p className="text-xs text-text-muted">{t("runningRequestsDesc")}</p>
</div>
<div className="flex items-center gap-3">
<div className="inline-flex items-center gap-2 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-300">
<span className="h-2 w-2 rounded-full bg-emerald-400 animate-pulse" />
{loading ? t("loading") : t("activeCount", { count: rows.length })}
</div>
{rows.length > 0 && (
<button
onClick={handleClearAll}
className="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-1 text-xs font-medium text-red-400 transition-colors hover:bg-red-500/20 hover:text-red-300"
>
{t("clearAll") || "Clear All"}
</button>
)}
</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full text-sm">
<thead className="bg-sidebar/40 text-left text-xs uppercase tracking-wide text-text-muted">
<tr>
<th className="px-4 py-3 font-medium">{t("model")}</th>
<th className="px-4 py-3 font-medium">{t("provider")}</th>
<th className="px-4 py-3 font-medium">{t("account")}</th>
<th className="px-4 py-3 font-medium">{t("elapsed")}</th>
<th className="px-4 py-3 font-medium">{t("count")}</th>
<th className="px-4 py-3 font-medium">{t("payloads")}</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr
key={`${row.account}:${row.provider}:${row.model}:${row.startedAt}`}
className="border-t border-border/60"
>
<td className="px-4 py-3 font-medium text-text-main">{row.model}</td>
<td className="px-4 py-3 text-text-muted">{row.provider}</td>
<td className="px-4 py-3 text-text-muted">{row.account}</td>
<td className="px-4 py-3 text-text-main">{formatDuration(row.runningTimeMs)}</td>
<td className="px-4 py-3 text-text-main">{row.count}</td>
<td className="px-4 py-3">
<button
type="button"
onClick={() => setSelectedRow(row)}
className="rounded-md border border-border px-3 py-1.5 text-xs font-medium text-text-main transition-colors hover:bg-sidebar/40"
>
{t("viewPayloads")}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{selectedRow && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/55 px-4 py-6 backdrop-blur-sm">
<div className="flex max-h-[85vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-border bg-surface shadow-2xl">
<div className="flex items-start justify-between gap-4 border-b border-border px-5 py-4">
<div>
<h4 className="text-lg font-semibold text-text-main">
{selectedRow.provider} / {selectedRow.model}
</h4>
<p className="mt-1 text-sm text-text-muted">
{t("runningRequestDetailMeta", {
account: selectedRow.account,
elapsed: formatDuration(selectedRow.runningTimeMs),
})}
</p>
</div>
<button
type="button"
onClick={() => setSelectedRow(null)}
className="rounded-full border border-border p-2 text-text-muted transition-colors hover:bg-sidebar/40 hover:text-text-main"
aria-label={t("close")}
>
<span className="material-symbols-outlined text-[18px]">close</span>
</button>
</div>
<div className="grid gap-4 overflow-y-auto px-5 py-5 md:grid-cols-2">
<section className="rounded-xl border border-border bg-bg-subtle p-4">
<div className="mb-3">
<h5 className="text-sm font-semibold text-text-main">{t("clientPayload")}</h5>
<p className="mt-1 text-xs text-text-muted">
{selectedRow.clientEndpoint || t("notAvailable")}
</p>
</div>
<pre className="overflow-x-auto rounded-lg border border-border/70 bg-bg p-3 text-xs text-text-muted">
{JSON.stringify(selectedRow.clientRequest || {}, null, 2)}
</pre>
</section>
<section className="rounded-xl border border-border bg-bg-subtle p-4">
<div className="mb-3">
<h5 className="text-sm font-semibold text-text-main">{t("upstreamPayload")}</h5>
<p className="mt-1 break-all text-xs text-text-muted">
{selectedRow.providerUrl || t("upstreamNotSentYet")}
</p>
</div>
<pre className="overflow-x-auto rounded-lg border border-border/70 bg-bg p-3 text-xs text-text-muted">
{JSON.stringify(selectedRow.providerRequest || {}, null, 2)}
</pre>
</section>
</div>
</div>
</div>
)}
</div>
);
}