/** The shared conversation, grouped for reading rather than event delivery. */
import { useEffect, useRef, useState } from "react";
import { SignInIcon } from "@phosphor-icons/react";
import { AgentFace, Face } from "@chopin/editor";
import { MessageMarkdown } from "./markdown";
import { capitalize, displayText, duration, group, summarize, toolCopy } from "./model";
import toolChevronDown from "../assets/icons/tool-chevron-down.svg";
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 summary = summarize(tools);
if (summary.state === "running") {
return (
{summary.name}
{summary.completed} done
);
}
return (
{open && (
{tools.map(tool => (
-
{toolCopy(tool.name)}
{tool.status === "failed" && (
Failed
)}
{tool.took === undefined ? "—" : duration(tool.took)}
))}
)}
);
}
function SystemEntry(
{ arrived, item }: { arrived: boolean; item: Extract },
) {
return (
);
}
function MessageBody(
{ handle, message, onWithdraw }: {
handle: string;
message: Message;
onWithdraw: (id: string) => void;
},
) {
let text = displayText(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
>
{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"
?
: (
)
)}
);
}