diff options
Diffstat (limited to 'src/core/chunks/selectors.ts')
| -rw-r--r-- | src/core/chunks/selectors.ts | 88 |
1 files changed, 53 insertions, 35 deletions
diff --git a/src/core/chunks/selectors.ts b/src/core/chunks/selectors.ts index 8fb832f..e46d5f5 100644 --- a/src/core/chunks/selectors.ts +++ b/src/core/chunks/selectors.ts @@ -1,4 +1,4 @@ -import type { ChatMessage, Chunk } from "@dispatch/wire"; +import type { ChatMessage, Chunk, TurnProviderRetryEvent } from "@dispatch/wire"; import type { RenderedChunk, TranscriptState } from "./types"; /** @@ -6,46 +6,64 @@ import type { RenderedChunk, TranscriptState } from "./types"; * then provisional (seq: null). */ export function selectChunks(state: TranscriptState): readonly RenderedChunk[] { - const result: RenderedChunk[] = []; - for (const c of state.committed) { - result.push({ seq: c.seq, role: c.role, chunk: c.chunk, provisional: false }); - } - for (const p of state.provisional) { - result.push({ seq: null, role: p.role, chunk: p.chunk, provisional: true }); - } - if (state.accumulating !== null) { - const chunk: Chunk = - state.accumulating.kind === "text" - ? { type: "text", text: state.accumulating.text } - : { type: "thinking", text: state.accumulating.text }; - result.push({ seq: null, role: "assistant", chunk, provisional: true }); - } - return result; + const result: RenderedChunk[] = []; + for (const c of state.committed) { + result.push({ seq: c.seq, role: c.role, chunk: c.chunk, provisional: false }); + } + for (const p of state.provisional) { + result.push({ seq: null, role: p.role, chunk: p.chunk, provisional: true }); + } + if (state.accumulating !== null) { + const chunk: Chunk = + state.accumulating.kind === "text" + ? { type: "text", text: state.accumulating.text } + : { type: "thinking", text: state.accumulating.text }; + result.push({ seq: null, role: "assistant", chunk, provisional: true, streaming: true }); + } + return result; +} + +/** + * Whether a turn is currently generating (for a "generating…" indicator). True + * for ANY client watching the conversation — the sender, a second device, or a + * reconnected client whose in-flight turn was replayed. + */ +export function selectGenerating(state: TranscriptState): boolean { + return state.generating; +} + +/** + * The latest `provider-retry` event for the current turn, or `null` when no retry + * is pending. Drives the transient yellow "retrying…" warning banner (rendered + * by ChatView). Never persisted — see `TranscriptState.providerRetry`. + */ +export function selectProviderRetry(state: TranscriptState): TurnProviderRetryEvent | null { + return state.providerRetry; } /** * Group consecutive same-role rendered chunks into ChatMessages. */ export function selectMessages(state: TranscriptState): readonly ChatMessage[] { - const rendered = selectChunks(state); - const first = rendered[0]; - if (first === undefined) return []; + const rendered = selectChunks(state); + const first = rendered[0]; + if (first === undefined) return []; - const messages: ChatMessage[] = []; - let role = first.role; - let chunks: Chunk[] = [first.chunk]; + const messages: ChatMessage[] = []; + let role = first.role; + let chunks: Chunk[] = [first.chunk]; - for (let i = 1; i < rendered.length; i++) { - const rc = rendered[i]; - if (rc === undefined) continue; - if (rc.role === role) { - chunks.push(rc.chunk); - } else { - messages.push({ role, chunks }); - role = rc.role; - chunks = [rc.chunk]; - } - } - messages.push({ role, chunks }); - return messages; + for (let i = 1; i < rendered.length; i++) { + const rc = rendered[i]; + if (rc === undefined) continue; + if (rc.role === role) { + chunks.push(rc.chunk); + } else { + messages.push({ role, chunks }); + role = rc.role; + chunks = [rc.chunk]; + } + } + messages.push({ role, chunks }); + return messages; } |
