Files
Termix/plugins/ai/src/frontend/transcript.ts
T

83 lines
2.3 KiB
TypeScript

import type { AiProposal } from "./ai-api";
import type { StreamState, ToolActivity } from "./use-ai-stream";
/** A finished part of the conversation, kept after the stream resets. */
export type HistoryEntry =
| { kind: "message"; role: "user" | "assistant"; content: string }
| { kind: "tool"; tool: ToolActivity };
/** One entry in the rendered conversation, in the order it happened. */
export type TimelineItem =
| {
kind: "message";
key: string;
role: "user" | "assistant";
content: string;
}
/** live is false for a past run's step, which can never still be running. */
| { kind: "tool"; key: string; tool: ToolActivity; live: boolean }
| { kind: "proposal"; key: string; proposal: AiProposal };
export function userEntry(content: string): HistoryEntry {
return { kind: "message", role: "user", content };
}
/**
* What a finished run leaves in the transcript: its tool steps, then its
* reply. Tool ids are only unique within one run, so they get a run prefix.
*/
export function finishedRunEntries(
runId: number,
tools: ToolActivity[],
reply: string,
): HistoryEntry[] {
const entries: HistoryEntry[] = tools.map((tool) => ({
kind: "tool",
tool: { ...tool, id: `run-${runId}-${tool.id}` },
}));
if (reply)
entries.push({ kind: "message", role: "assistant", content: reply });
return entries;
}
/** Past runs, then the run in progress, then its proposals. */
export function buildTimeline(
history: HistoryEntry[],
state: Pick<StreamState, "tools" | "assistantText">,
proposals: AiProposal[],
): TimelineItem[] {
const timeline: TimelineItem[] = history.map((entry, index) =>
entry.kind === "tool"
? { kind: "tool", key: entry.tool.id, tool: entry.tool, live: false }
: {
kind: "message",
key: `history-${index}`,
role: entry.role,
content: entry.content,
},
);
for (const tool of state.tools) {
timeline.push({ kind: "tool", key: tool.id, tool, live: true });
}
if (state.assistantText) {
timeline.push({
kind: "message",
key: "streaming",
role: "assistant",
content: state.assistantText,
});
}
for (const proposal of proposals) {
timeline.push({
kind: "proposal",
key: `proposal-${proposal.id}`,
proposal,
});
}
return timeline;
}