import {splitStreamingMarkdown} from "./streaming-markdown"; export type IncrementalMarkdownBlock = T & { source: string; type: string; index: number; stable: boolean; }; export type IncrementalMarkdownResult = { blocks: Array>; parsed: number; reused: number; }; type IncrementalMarkdownState = { stableSource: string; stableBlocks: Array>; nextIndex: number; }; export class IncrementalMarkdownCache { private readonly states = new Map>(); render( messageId: string, value: string, renderBlock: (source: string, type: string, index: number, stable: boolean) => T, onSplit?: (durationMs: number) => void, complete = false ): IncrementalMarkdownResult { let state = this.states.get(messageId); if (!state || !value.startsWith(state.stableSource)) { state = {stableSource: "", stableBlocks: [], nextIndex: 0}; this.states.set(messageId, state); } if (value === state.stableSource) return {blocks: state.stableBlocks, parsed: 0, reused: state.stableBlocks.length}; const tail = value.slice(state.stableSource.length); const startedAt = performance.now(); const split = splitStreamingMarkdown(tail, complete); onSplit?.(performance.now() - startedAt); const renderedTail = split.blocks.map((block, offset) => ({ ...renderBlock(block.source, block.type, state!.nextIndex + offset, block.stable), source: block.source, type: block.type, index: state!.nextIndex + offset, stable: block.stable })); const newlyStable = renderedTail.filter((_block, index) => split.blocks[index].end <= split.stableOffset); const previouslyStableCount = state.stableBlocks.length; if (split.stableOffset > 0) { state.stableSource += tail.slice(0, split.stableOffset); state.stableBlocks.push(...newlyStable); state.nextIndex += newlyStable.length; } return { blocks: [...state.stableBlocks.slice(0, state.stableBlocks.length - newlyStable.length), ...renderedTail], parsed: renderedTail.length, reused: previouslyStableCount }; } delete(messageId: string) { this.states.delete(messageId); } move(fromMessageId: string, toMessageId: string) { if (fromMessageId === toMessageId) return; const state = this.states.get(fromMessageId); if (!state) return; this.states.set(toMessageId, state); this.states.delete(fromMessageId); } has(messageId: string) { return this.states.has(messageId); } }