1import type { MessagesTimelineRow } from "./MessagesTimeline.logic";
2
3export interface TimelineMinimapItem {
4 readonly id: string;
5 readonly rowIndex: number;
6 readonly userText: string | null;
7 readonly assistantText: string | null;
8}
9
10/** Keep full source text untouched until a minimap preview is opened. */
11export function deriveTimelineMinimapItems(
12 rows: ReadonlyArray<MessagesTimelineRow>,
13): TimelineMinimapItem[] {
14 const items: TimelineMinimapItem[] = [];
15 for (let index = 0; index < rows.length; index += 1) {
16 const row = rows[index];
17 if (row?.kind !== "message" || row.message.role !== "user") {
18 continue;
19 }
20
21 items.push({
22 id: row.id,
23 rowIndex: index,
24 userText: row.message.text,
25 assistantText: resolveFinalAssistantTextForTurn(rows, index),
26 });
27 }
28 return items;
29}
30
31function resolveFinalAssistantTextForTurn(
32 rows: ReadonlyArray<MessagesTimelineRow>,
33 userRowIndex: number,
34) {
35 let finalAssistantText: string | null = null;
36 for (let index = userRowIndex + 1; index < rows.length; index += 1) {
37 const row = rows[index];
38 if (row?.kind !== "message") {
39 continue;
40 }
41 if (row.message.role === "user") {
42 break;
43 }
44 if (row.message.role === "assistant") {
45 finalAssistantText = row.message.text ?? null;
46 }
47 }
48 return finalAssistantText;
49}
50
51function compactMinimapPreview(text: string | null | undefined) {
52 const compact = text?.replace(/\s+/g, " ").trim() ?? "";
53 return compact.length > 0 ? compact : null;
54}
55
56export function resolveTimelineMinimapPreview(
57 item: TimelineMinimapItem | null,
58): TimelineMinimapItem | null {
59 return item === null
60 ? null
61 : {
62 ...item,
63 userText: compactMinimapPreview(item.userText),
64 assistantText: compactMinimapPreview(item.assistantText),
65 };
66}