From 205ab8a223ddf6591a3e8bfc9108506502977c11 Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 16 Jan 2026 12:23:49 +0000 Subject: Fixup: use default api.makima.jp URL and fix default branch detection Also add checkpointing/history --- makima/frontend/src/routes/contracts.tsx | 2 +- makima/frontend/src/routes/history.tsx | 325 +++++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 makima/frontend/src/routes/history.tsx (limited to 'makima/frontend/src/routes') diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx index 8ed4ab5..cd385f9 100644 --- a/makima/frontend/src/routes/contracts.tsx +++ b/makima/frontend/src/routes/contracts.tsx @@ -612,7 +612,7 @@ function ContractsPageContent() {
- {suggestion.repositoryUrl || suggestion.localPath} + {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
))} diff --git a/makima/frontend/src/routes/history.tsx b/makima/frontend/src/routes/history.tsx new file mode 100644 index 0000000..fc88f0e --- /dev/null +++ b/makima/frontend/src/routes/history.tsx @@ -0,0 +1,325 @@ +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([]); + const [totalCount, setTotalCount] = useState(0); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Filters + const [contracts, setContracts] = useState([]); + const [selectedContractId, setSelectedContractId] = useState(null); + const [selectedTaskId, setSelectedTaskId] = useState(null); + const [dateFrom, setDateFrom] = useState(""); + const [dateTo, setDateTo] = useState(""); + + // Selected event and detail + const [selectedEvent, setSelectedEvent] = useState(null); + const [detailMode, setDetailMode] = useState("conversation"); + const [conversation, setConversation] = useState< + TaskConversationResponse | SupervisorConversationResponse | null + >(null); + const [checkpoints, setCheckpoints] = useState([]); + 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 ( +
+ +
+
Loading...
+
+
+ ); + } + + return ( +
+ + +
+ {/* Filters */} + + + {/* Main content area */} +
+ {/* Timeline list */} +
+ +
+ + {/* Detail panel */} +
+ {selectedEvent ? ( + <> + {/* Detail header */} +
+
+ +
+
+ {selectedEvent.eventType} + {selectedEvent.eventSubtype && ` / ${selectedEvent.eventSubtype}`} +
+
+ {new Date(selectedEvent.createdAt).toLocaleString()} +
+
+
+ + {/* Mode toggle (only if task has checkpoints) */} + {checkpoints.length > 0 && ( +
+ + +
+ )} +
+ + {/* Detail content */} +
+ {detailLoading ? ( +
+
Loading...
+
+ ) : detailMode === "conversation" && conversation ? ( + + ) : detailMode === "checkpoints" && checkpoints.length > 0 ? ( + + ) : ( +
+
+ No {detailMode} data available +
+
+ )} +
+ + {/* Resume controls */} + {selectedEvent.taskId && ( + + )} + + ) : ( +
+
+
+ Select an event to view details +
+
+ View conversation history, checkpoints, and more +
+
+
+ )} +
+
+
+
+ ); +} -- cgit v1.2.3