import type { ConversationPlan } from "@chopin/protocol"; import type { Group, Message } from "./model"; /** `continued` marks the part of a speaker's group that resumes after an offer card. */ export type ResearchTranscriptItem = (Group & { continued?: boolean }) | { kind: "research"; offer: ConversationPlan.ResearchOffer; }; /** Keep offer keys at one React parent while positioning them after their latest source. */ export function researchTranscript( groups: Group[], offers: readonly ConversationPlan.ResearchOffer[], ): ResearchTranscriptItem[] { let byMessage = new Map(); for (let offer of offers) { if (offer.workflow && !offer.workflow.published) continue; let id = offer.workflow?.placementMessageId ?? offer.source.messageId; byMessage.set(id, [...byMessage.get(id) ?? [], offer]); } let items: ResearchTranscriptItem[] = []; for (let group of groups) { if (group.kind !== "messages" || group.queued) { items.push(group); continue; } let messages: Message[] = []; let continued = false; for (let message of group.messages) { messages.push(message); let following = byMessage.get(message.id); if (!following) continue; items.push({ ...group, messages, ...(continued ? { continued } : {}) }); messages = []; continued = true; for (let offer of following) items.push({ kind: "research", offer }); } if (messages.length) items.push({ ...group, messages, ...(continued ? { continued } : {}) }); } return items; }