145 lines
5.7 KiB
TypeScript
145 lines
5.7 KiB
TypeScript
import { DashboardSection } from "./DashboardSection";
|
|
import { DashboardEmptyState } from "./DashboardEmptyState";
|
|
|
|
export type SessionSummary = {
|
|
key: string;
|
|
title: string;
|
|
subtitle: string;
|
|
usage: string;
|
|
lastSeenAt: string | null;
|
|
isMain: boolean;
|
|
/** Enriched from runtime usage when session IDs match */
|
|
costUsd?: number | null;
|
|
totalTokens?: number | null;
|
|
model?: string | null;
|
|
};
|
|
|
|
interface SessionsSectionProps {
|
|
sessions: SessionSummary[];
|
|
activeSessions: number;
|
|
hasConfiguredGateways: boolean;
|
|
isLoading: boolean;
|
|
gatewayUnavailableCount: number;
|
|
gatewayTargetsCount: number;
|
|
formatCount: (n: number) => string;
|
|
formatRelative: (ts: string) => string;
|
|
dash: string;
|
|
}
|
|
|
|
export function SessionsSection({
|
|
sessions,
|
|
activeSessions,
|
|
hasConfiguredGateways,
|
|
isLoading,
|
|
gatewayUnavailableCount,
|
|
gatewayTargetsCount,
|
|
formatCount,
|
|
formatRelative,
|
|
dash,
|
|
}: SessionsSectionProps) {
|
|
return (
|
|
<DashboardSection
|
|
title="Sessions"
|
|
action={{ label: formatCount(activeSessions), href: "#" }}
|
|
tone={gatewayUnavailableCount > 0 ? "warning" : "success"}
|
|
>
|
|
<div className="max-h-[310px] space-y-2 overflow-x-hidden overflow-y-auto pr-1">
|
|
{!hasConfiguredGateways ? (
|
|
<DashboardEmptyState message="No gateways are configured for any board yet." />
|
|
) : isLoading ? (
|
|
<div className="space-y-2">
|
|
{[0, 1].map((i) => (
|
|
<div
|
|
key={i}
|
|
className="overflow-hidden rounded-lg border border-l-4 border-[color:rgba(52,211,153,0.28)] border-l-[color:var(--success)] bg-[color:var(--surface-muted)] px-3 py-2"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="min-w-0 flex-1">
|
|
<div className="mb-1 flex items-center gap-2">
|
|
<span className="h-2 w-2 rounded-full bg-[color:var(--success)] opacity-40" />
|
|
<div className="h-3.5 w-36 animate-pulse rounded bg-[color:var(--surface-strong)]" />
|
|
</div>
|
|
<div className="h-3 w-28 animate-pulse rounded bg-[color:var(--surface-strong)] opacity-60" />
|
|
</div>
|
|
<div className="min-w-0 max-w-[45%] space-y-1 text-right">
|
|
<div className="ml-auto h-3.5 w-16 animate-pulse rounded bg-[color:var(--surface-strong)]" />
|
|
<div className="ml-auto h-3 w-24 animate-pulse rounded bg-[color:var(--surface-strong)] opacity-60" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : sessions.length > 0 ? (
|
|
<>
|
|
{gatewayUnavailableCount > 0 && (
|
|
<DashboardEmptyState
|
|
tone="warning"
|
|
message={`${formatCount(gatewayUnavailableCount)} gateway${gatewayUnavailableCount === 1 ? "" : "s"} unavailable; showing sessions from reachable gateways.`}
|
|
/>
|
|
)}
|
|
{sessions.map((session) => (
|
|
<div
|
|
key={session.key}
|
|
className={`overflow-hidden rounded-lg border border-l-4 bg-[color:var(--surface-muted)] px-3 py-2 ${
|
|
session.isMain
|
|
? "border-[color:rgba(52,211,153,0.28)] border-l-[color:var(--success)]"
|
|
: "border-[color:var(--border)] border-l-[color:var(--border-strong)]"
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-medium text-strong">
|
|
<span
|
|
className={`mr-2 inline-block h-2 w-2 rounded-full ${
|
|
session.isMain
|
|
? "bg-[color:var(--success)]"
|
|
: "bg-[color:var(--border-strong)]"
|
|
}`}
|
|
/>
|
|
{session.title}
|
|
</p>
|
|
<p className="mt-0.5 truncate text-xs text-muted">
|
|
{session.subtitle}
|
|
</p>
|
|
</div>
|
|
<div className="min-w-0 max-w-[45%] text-right">
|
|
<p className="truncate text-xs font-medium text-strong">
|
|
{session.costUsd != null
|
|
? session.costUsd === 0
|
|
? "$0.00"
|
|
: session.costUsd < 0.01
|
|
? `$${session.costUsd.toFixed(4)}`
|
|
: `$${session.costUsd.toFixed(2)}`
|
|
: session.usage === dash
|
|
? "Usage unavailable"
|
|
: session.usage}
|
|
</p>
|
|
<p className="truncate text-[11px] text-muted">
|
|
{session.model
|
|
? session.model.includes("/")
|
|
? session.model.split("/")[1]
|
|
: session.model
|
|
: null}
|
|
{session.model && session.lastSeenAt ? " · " : null}
|
|
{session.lastSeenAt
|
|
? formatRelative(session.lastSeenAt)
|
|
: "Activity unavailable"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</>
|
|
) : gatewayUnavailableCount === gatewayTargetsCount ? (
|
|
<DashboardEmptyState
|
|
tone="danger"
|
|
message="Session data is unavailable for all configured gateways."
|
|
/>
|
|
) : (
|
|
<DashboardEmptyState message="No active sessions detected." />
|
|
)}
|
|
</div>
|
|
</DashboardSection>
|
|
);
|
|
}
|