/** The shared conversation, grouped for reading rather than event delivery. */ import { useEffect, useId, useRef, useState } from "react"; import { SignInIcon } from "@phosphor-icons/react"; import { AgentFace, Face, MotionDisclosure, MotionDisclosureIcon } from "@chopin/editor"; import { MessageMarkdown } from "./markdown"; import { capitalize, displayText, duration, group, summarize, toolCopy } from "./model"; import { motionContract } from "../motion-contract"; import { motionImmediately } from "../motion-input"; import toolChevronRight from "../assets/icons/tool-chevron-right.svg"; import toolLoader from "../assets/icons/tool-loader.svg"; import type { Chat } from "@chopin/protocol"; import type { Group, Message } from "./model"; function when(ts: number): string { return new Date(ts * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } function ToolRun({ tools }: { tools: Chat.Activity[] }) { let [open, setOpen] = useState(false); let contentId = useId(); let summary = summarize(tools); let motion = motionContract("collapse"); if (summary.state === "running") { return (
{summary.name} {summary.completed} done
); } return (
); } function SystemEntry( { arrived, item }: { arrived: boolean; item: Extract }, ) { return (

{displayText(item.text)}

); } function MessageBody( { handle, message, onWithdraw }: { handle: string; message: Message; onWithdraw: (id: string) => void; }, ) { let text = displayText(message.text) ? message.text : message.author.kind === "member" ? "Ask Planner" : ""; return (
{text && (
{message.streaming && ▍}
{message.queued && message.author.kind === "member" && message.author.handle === handle && ( )}
)} {message.tools && message.tools.length > 0 && }
); } function MessageGroup( { arrived, group: item, handle, onWithdraw }: { arrived: ReadonlySet; group: Extract; handle: string; onWithdraw: (id: string) => void; }, ) { let first = item.messages[0]!; let name = item.author.kind === "agent" ? "Planner" : capitalize(item.author.handle); return (
arrived.has(message.id)) ? "animate-enter" : "" }`} data-chat-entry data-chat-state={item.queued ? "queued" : undefined} >
{item.author.kind === "agent" ? : }
{name} {item.queued ? "queued" : when(first.ts!)}
{item.messages.map(message => ( ))}
); } export function Transcript( { active, arrived, entries, handle, onWithdraw, queued, working, }: { active: boolean; arrived: ReadonlySet; entries: Chat.Entry[]; handle: string; onWithdraw: (id: string) => void; queued: Chat.Waiting[]; working?: Pick; }, ) { let bottom = useRef(null); let pinned = useRef(true); let groups = group(entries, queued, working); useEffect(() => { if (active && pinned.current) bottom.current?.scrollIntoView({ block: "end" }); }, [active, entries, queued]); return (
{ let element = event.currentTarget; let distance = element.scrollHeight - element.scrollTop - element.clientHeight; pinned.current = distance < 40; }} >
{groups.map(item => item.kind === "system" ? : ( ) )}
); }