61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import type { ChatTurn, ThreadMessage } from "./types";
|
|
|
|
/**
|
|
* Flattens stored turns into the flat message list the transcript renders.
|
|
*
|
|
* Storage keeps a question and its answer together as a turn, because that is
|
|
* what gets replayed to the model. The transcript needs a flat sequence, newest
|
|
* message first. This is the one place that translation happens.
|
|
*/
|
|
|
|
export interface LiveState {
|
|
isStreaming: boolean;
|
|
/** Partial answer for the turn currently in flight. */
|
|
response: string;
|
|
thinking: string;
|
|
/** Activity line, e.g. "Searching the web". Only meaningful mid-turn. */
|
|
status?: string;
|
|
}
|
|
|
|
export function buildThread(turns: ChatTurn[], live: LiveState): ThreadMessage[] {
|
|
const messages: ThreadMessage[] = [];
|
|
|
|
// Strictly newest message first. The search bar sits at the top of the window,
|
|
// so the most recent thing — an answer, once one has been written — should be
|
|
// the first line under it. A consequence is that within an exchange the answer
|
|
// comes before its question, which is the intended reading order here.
|
|
for (let index = turns.length - 1; index >= 0; index--) {
|
|
const turn = turns[index];
|
|
|
|
// Only the newest turn can be mid-flight.
|
|
const isLive = index === turns.length - 1 && live.isStreaming;
|
|
const text = isLive ? live.response : turn.response;
|
|
|
|
messages.push({
|
|
id: `${turn.id}:assistant`,
|
|
role: "assistant",
|
|
text,
|
|
createdAt: turn.createdAt,
|
|
isStreaming: isLive,
|
|
thinking: isLive ? live.thinking : turn.thinking,
|
|
toolCalls: turn.toolCalls,
|
|
error: isLive ? undefined : turn.error,
|
|
// Only while there is nothing to read yet. Once tokens land, the
|
|
// placeholder it feeds is gone anyway.
|
|
status: isLive && text.length === 0 ? live.status : undefined,
|
|
});
|
|
|
|
messages.push({
|
|
id: `${turn.id}:user`,
|
|
role: "user",
|
|
text: turn.prompt,
|
|
createdAt: turn.createdAt,
|
|
isStreaming: false,
|
|
thinking: "",
|
|
toolCalls: [],
|
|
});
|
|
}
|
|
|
|
return messages;
|
|
}
|