import { useState, useCallback, useEffect } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { TimelineList } from "../components/history/TimelineList";
import { ConversationView } from "../components/history/ConversationView";
import { CheckpointList } from "../components/history/CheckpointList";
import { HistoryFilters } from "../components/history/HistoryFilters";
import { ResumeControls } from "../components/history/ResumeControls";
import { useAuth } from "../contexts/AuthContext";
import type {
HistoryEvent,
TaskConversationResponse,
SupervisorConversationResponse,
TaskCheckpoint,
ContractSummary,
} from "../lib/api";
import {
getTimeline,
getTaskConversation,
getSupervisorConversation,
getTaskCheckpoints,
listContracts,
} from "../lib/api";
// Detail view modes
type DetailMode = "conversation" | "checkpoints";
export default function HistoryPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
// Timeline state
const [events, setEvents] = useState<HistoryEvent[]>([]);
const [totalCount, setTotalCount] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Filters
const [contracts, setContracts] = useState<ContractSummary[]>([]);
const [selectedContractId, setSelectedContractId] = useState<string | null>(null);
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
const [dateFrom, setDateFrom] = useState<string>("");
const [dateTo, setDateTo] = useState<string>("");
// Selected event and detail
const [selectedEvent, setSelectedEvent] = useState<HistoryEvent | null>(null);
const [detailMode, setDetailMode] = useState<DetailMode>("conversation");
const [conversation, setConversation] = useState<
TaskConversationResponse | SupervisorConversationResponse | null
>(null);
const [checkpoints, setCheckpoints] = useState<TaskCheckpoint[]>([]);
const [detailLoading, setDetailLoading] = useState(false);
// Redirect to login if not authenticated
useEffect(() => {
if (!authLoading && isAuthConfigured && !isAuthenticated) {
navigate("/login");
}
}, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
// Load contracts for filter dropdown
useEffect(() => {
async function loadContracts() {
try {
const response = await listContracts();
setContracts(response.contracts);
} catch (e) {
console.error("Failed to load contracts:", e);
}
}
if (isAuthenticated || !isAuthConfigured) {
loadContracts();
}
}, [isAuthenticated, isAuthConfigured]);
// Load timeline
const loadTimeline = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await getTimeline({
contractId: selectedContractId || undefined,
taskId: selectedTaskId || undefined,
from: dateFrom || undefined,
to: dateTo || undefined,
limit: 100,
});
setEvents(response.entries);
setTotalCount(response.totalCount);
} catch (e) {
console.error("Failed to load timeline:", e);
setError(e instanceof Error ? e.message : "Failed to load timeline");
} finally {
setLoading(false);
}
}, [selectedContractId, selectedTaskId, dateFrom, dateTo]);
// Load timeline on mount and filter change
useEffect(() => {
if (isAuthenticated || !isAuthConfigured) {
loadTimeline();
}
}, [loadTimeline, isAuthenticated, isAuthConfigured]);
// Load detail when event selected
const handleSelectEvent = useCallback(async (event: HistoryEvent) => {
setSelectedEvent(event);
setDetailLoading(true);
try {
// Determine if this is a task or supervisor event
if (event.taskId) {
// Load task conversation and checkpoints
const [conv, cps] = await Promise.all([
getTaskConversation(event.taskId, {
includeToolCalls: true,
includeToolResults: true,
}),
getTaskCheckpoints(event.taskId).catch(() => []),
]);
setConversation(conv);
setCheckpoints(cps);
} else if (event.contractId) {
// Load supervisor conversation
const conv = await getSupervisorConversation(event.contractId);
setConversation(conv);
setCheckpoints([]);
}
} catch (e) {
console.error("Failed to load event details:", e);
} finally {
setDetailLoading(false);
}
}, []);
// Handle URL param for direct navigation
useEffect(() => {
if (id && events.length > 0) {
const event = events.find((e) => e.taskId === id || e.contractId === id);
if (event && event !== selectedEvent) {
handleSelectEvent(event);
}
}
}, [id, events, selectedEvent, handleSelectEvent]);
// Clear selection
const handleClearSelection = useCallback(() => {
setSelectedEvent(null);
setConversation(null);
setCheckpoints([]);
navigate("/history");
}, [navigate]);
// Handle filter changes
const handleContractChange = useCallback((contractId: string | null) => {
setSelectedContractId(contractId);
setSelectedTaskId(null); // Reset task filter when contract changes
}, []);
// Handle actions completed
const handleActionComplete = useCallback(() => {
// Refresh timeline and detail after action
loadTimeline();
if (selectedEvent?.taskId) {
handleSelectEvent(selectedEvent);
}
}, [loadTimeline, selectedEvent, handleSelectEvent]);
if (authLoading) {
return (
<div className="relative z-10 min-h-screen bg-[#0a1628] flex flex-col">
<Masthead />
<div className="flex-1 flex items-center justify-center">
<div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
</div>
</div>
);
}
return (
<div className="relative z-10 min-h-screen bg-[#0a1628] flex flex-col">
<Masthead />
<main className="flex-1 flex flex-col overflow-hidden p-4 gap-4">
{/* Filters */}
<HistoryFilters
contracts={contracts}
selectedContractId={selectedContractId}
onContractChange={handleContractChange}
dateFrom={dateFrom}
dateTo={dateTo}
onDateFromChange={setDateFrom}
onDateToChange={setDateTo}
totalCount={totalCount}
/>
{/* Main content area */}
<div className="flex-1 flex gap-4 min-h-0 overflow-hidden">
{/* Timeline list */}
<div className="w-1/3 min-w-[300px] max-w-[400px] flex flex-col">
<TimelineList
events={events}
loading={loading}
error={error}
selectedEvent={selectedEvent}
onSelectEvent={handleSelectEvent}
onRefresh={loadTimeline}
/>
</div>
{/* Detail panel */}
<div className="flex-1 flex flex-col min-h-0 overflow-hidden panel">
{selectedEvent ? (
<>
{/* Detail header */}
<div className="shrink-0 p-3 border-b border-[rgba(117,170,252,0.15)] flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={handleClearSelection}
className="text-[#7788aa] hover:text-[#9bc3ff] transition-colors"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<div>
<div className="font-mono text-xs text-[#9bc3ff] uppercase">
{selectedEvent.eventType}
{selectedEvent.eventSubtype && ` / ${selectedEvent.eventSubtype}`}
</div>
<div className="font-mono text-[10px] text-[#7788aa]">
{new Date(selectedEvent.createdAt).toLocaleString()}
</div>
</div>
</div>
{/* Mode toggle (only if task has checkpoints) */}
{checkpoints.length > 0 && (
<div className="flex gap-1">
<button
onClick={() => setDetailMode("conversation")}
className={`px-3 py-1 font-mono text-[10px] uppercase border transition-colors ${
detailMode === "conversation"
? "border-[#3f6fb3] text-[#9bc3ff] bg-[rgba(63,111,179,0.2)]"
: "border-[rgba(117,170,252,0.25)] text-[#7788aa] hover:border-[rgba(117,170,252,0.35)]"
}`}
>
Conversation
</button>
<button
onClick={() => setDetailMode("checkpoints")}
className={`px-3 py-1 font-mono text-[10px] uppercase border transition-colors ${
detailMode === "checkpoints"
? "border-[#3f6fb3] text-[#9bc3ff] bg-[rgba(63,111,179,0.2)]"
: "border-[rgba(117,170,252,0.25)] text-[#7788aa] hover:border-[rgba(117,170,252,0.35)]"
}`}
>
Checkpoints ({checkpoints.length})
</button>
</div>
)}
</div>
{/* Detail content */}
<div className="flex-1 overflow-auto">
{detailLoading ? (
<div className="flex items-center justify-center h-full">
<div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
</div>
) : detailMode === "conversation" && conversation ? (
<ConversationView conversation={conversation} />
) : detailMode === "checkpoints" && checkpoints.length > 0 ? (
<CheckpointList
checkpoints={checkpoints}
taskId={selectedEvent.taskId!}
onActionComplete={handleActionComplete}
/>
) : (
<div className="flex items-center justify-center h-full">
<div className="font-mono text-[#7788aa] text-xs">
No {detailMode} data available
</div>
</div>
)}
</div>
{/* Resume controls */}
{selectedEvent.taskId && (
<ResumeControls
taskId={selectedEvent.taskId}
contractId={selectedEvent.contractId}
checkpoints={checkpoints}
onActionComplete={handleActionComplete}
/>
)}
</>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="font-mono text-[#7788aa] text-sm mb-2">
Select an event to view details
</div>
<div className="font-mono text-[#556677] text-xs">
View conversation history, checkpoints, and more
</div>
</div>
</div>
)}
</div>
</div>
</main>
</div>
);
}