import "./ConversationHistory.css"; import type { PlanningQuestion } from "@fusion/core"; import { useState } from "react"; import type { ConversationHistoryEntry } from "../api"; interface ConversationHistoryProps { entries: ConversationHistoryEntry[]; defaultShowThinking?: boolean; } interface NumberedEntry extends ConversationHistoryEntry { questionNumber: number | null; } function getResponseValue(entry: ConversationHistoryEntry): unknown { const { question, response } = entry; if (!question) return response; if (response && typeof response === "object" && !Array.isArray(response)) { const record = response as Record; if (question.id in record) { return record[question.id]; } } return response; } function formatResponse(question: PlanningQuestion, responseValue: unknown): string { switch (question.type) { case "text": { if (typeof responseValue === "string") return responseValue; return responseValue == null ? "" : String(responseValue); } case "single_select": { if (typeof responseValue === "string") { const selected = question.options?.find((option) => option.id === responseValue); return selected?.label ?? responseValue; } return responseValue == null ? "" : String(responseValue); } case "multi_select": { if (Array.isArray(responseValue)) { return responseValue .map((value) => { if (typeof value !== "string") { return String(value); } const selected = question.options?.find((option) => option.id === value); return selected?.label ?? value; }) .join(", "); } return responseValue == null ? "" : String(responseValue); } case "confirm": { if (responseValue === true) return "Yes"; if (responseValue === false) return "No"; return responseValue == null ? "" : String(responseValue); } default: return responseValue == null ? "" : JSON.stringify(responseValue); } } function normalizeEntries(entries: ConversationHistoryEntry[]): NumberedEntry[] { let questionCounter = 0; const normalized: NumberedEntry[] = []; for (const entry of entries) { if (entry.question) { questionCounter += 1; normalized.push({ ...entry, questionNumber: questionCounter }); continue; } if (entry.thinkingOutput) { normalized.push({ ...entry, questionNumber: null }); } } return normalized; } export function ConversationHistory({ entries, defaultShowThinking = false }: ConversationHistoryProps) { const [expandedThinking, setExpandedThinking] = useState>({}); const normalizedEntries = normalizeEntries(entries); if (normalizedEntries.length === 0) { return null; } return (
{normalizedEntries.map((entry, index) => { const hasQuestion = Boolean(entry.question); const hasThinking = Boolean(entry.thinkingOutput); const isExpanded = expandedThinking[index] ?? defaultShowThinking; const responseValue = hasQuestion ? getResponseValue(entry) : undefined; const formattedResponse = entry.question && responseValue !== undefined ? formatResponse(entry.question, responseValue) : ""; const responseRecord = entry.response && typeof entry.response === "object" && !Array.isArray(entry.response) ? (entry.response as Record) : undefined; const comment = typeof responseRecord?._comment === "string" ? responseRecord._comment.trim() : ""; return (
{hasQuestion ? (
Q{entry.questionNumber}

{entry.question?.question}

) : (
AI Reasoning
)} {hasQuestion && (
Your response

{formattedResponse || "—"}

{comment &&

💬 {comment}

}
)} {hasThinking && (
{isExpanded &&
{entry.thinkingOutput}
}
)}
); })}
); }