Pipeline/frontend/src/components/claude/SessionHeroHeader.tsx

111 lines
4.6 KiB
TypeScript

"use client";
import Link from "next/link";
import {
ArrowLeft,
Bot,
Clock3,
Coins,
GitBranch,
MessagesSquare,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { type ClaudeSession } from "@/lib/api/claude-code";
import { formatTimestamp, truncateText } from "@/lib/formatters";
type SessionHeroHeaderProps = {
session: ClaudeSession;
};
function formatCost(value: number) {
return new Intl.NumberFormat(undefined, {
style: "currency",
currency: "USD",
maximumFractionDigits: value < 1 ? 4 : 2,
}).format(value);
}
export function SessionHeroHeader({ session }: SessionHeroHeaderProps) {
const title = session.title?.trim() || truncateText(session.session_id, 18);
const model = session.models[0] ?? "Model unavailable";
return (
<section className="relative overflow-hidden border-b border-[color:var(--border)] bg-[color:var(--surface)]">
<div className="absolute inset-x-0 top-0 h-1 bg-[linear-gradient(90deg,#06b6d4,#8b5cf6,#22c55e)]" />
<div className="px-4 py-5 md:px-8 md:py-7">
<div className="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
<div className="min-w-0">
<Link href="/claude-code">
<Button variant="ghost" size="sm" className="mb-4 px-2">
<ArrowLeft className="h-4 w-4" />
Back to Claude Code
</Button>
</Link>
<div className="flex flex-wrap items-center gap-2">
<Badge variant={session.is_active ? "success" : "outline"}>
{session.is_active ? "Active" : "Complete"}
</Badge>
{session.git_branch ? (
<Badge variant="outline" className="normal-case tracking-normal">
<GitBranch className="mr-1 h-3 w-3" />
{session.git_branch}
</Badge>
) : null}
<Badge variant="accent" className="normal-case tracking-normal">
{model}
</Badge>
</div>
<h1 className="mt-4 max-w-4xl break-words font-heading text-3xl font-semibold tracking-tight text-[color:var(--text)] md:text-4xl">
{title}
</h1>
<p className="mt-2 max-w-4xl break-words text-sm text-[color:var(--text-muted)]">
{session.cwd ?? session.project_dir}
</p>
</div>
<div className="grid min-w-0 grid-cols-2 gap-3 sm:grid-cols-4 lg:min-w-[520px]">
<div className="rounded-xl border border-[color:var(--border)] bg-[color:var(--bg)] p-3">
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--text-muted)]">
<Coins className="h-3.5 w-3.5" />
Cost
</div>
<p className="mt-2 truncate text-lg font-semibold text-[color:var(--text)]">
{formatCost(session.cost_usd)}
</p>
</div>
<div className="rounded-xl border border-[color:var(--border)] bg-[color:var(--bg)] p-3">
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--text-muted)]">
<Bot className="h-3.5 w-3.5" />
Tokens
</div>
<p className="mt-2 truncate text-lg font-semibold text-[color:var(--text)]">
{session.tokens.total.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-[color:var(--border)] bg-[color:var(--bg)] p-3">
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--text-muted)]">
<MessagesSquare className="h-3.5 w-3.5" />
Turns
</div>
<p className="mt-2 truncate text-lg font-semibold text-[color:var(--text)]">
{session.message_count.toLocaleString()}
</p>
</div>
<div className="rounded-xl border border-[color:var(--border)] bg-[color:var(--bg)] p-3">
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-[color:var(--text-muted)]">
<Clock3 className="h-3.5 w-3.5" />
Last seen
</div>
<p className="mt-2 truncate text-sm font-semibold text-[color:var(--text)]">
{formatTimestamp(session.last_message_at)}
</p>
</div>
</div>
</div>
</div>
</section>
);
}