Files
orca/src/shared/native-chat-streaming.ts
b916248294 Polish desktop native chat view (#6641)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-06-28 15:43:07 -07:00

64 lines
2.2 KiB
TypeScript

// Shared derivation for the in-flight "streaming" assistant bubble. While an
// agent works, its hook preview (lastAssistantMessage) is shown as a synthetic
// assistant message so the user sees the reply build in real time, before the
// completed turn is flushed to the transcript. Desktop and mobile both use this
// so the show/hide rule can't drift between platforms.
import type { NativeChatMessage } from './native-chat-types'
/** The synthetic streaming bubble's stable id (kept stable so the list keys it
* consistently across ticks and the real turn can replace it cleanly). */
export const NATIVE_CHAT_STREAMING_ID = 'streaming'
/** Concatenated text of an assistant message's text blocks, trimmed. */
function assistantText(message: NativeChatMessage | undefined): string {
if (!message || message.role !== 'assistant') {
return ''
}
return message.blocks
.filter((b) => b.type === 'text')
.map((b) => (b.type === 'text' ? b.text : ''))
.join('')
.trim()
}
/**
* Decide the streaming text to show, or null to show nothing. Returns the
* preview only while it leads the transcript — i.e. it's longer than (and not
* already contained in) the last assistant turn. Once the real turn lands with
* the same (or more) text, the preview is suppressed so the bubble doesn't
* duplicate or flicker as the transcript catches up.
*
* `working` gates it: a stale preview from a finished turn never shows.
*/
export function deriveNativeChatStreamingText(args: {
messages: readonly NativeChatMessage[]
previewText: string | null | undefined
working: boolean
}): string | null {
const { messages, previewText, working } = args
if (!working) {
return null
}
const text = previewText?.trim()
if (!text) {
return null
}
const lastText = assistantText(messages.at(-1))
if (lastText.includes(text) || text.length <= lastText.length) {
return null
}
return text
}
/** Build the synthetic streaming assistant message for the given text. */
export function nativeChatStreamingMessage(text: string): NativeChatMessage {
return {
id: NATIVE_CHAT_STREAMING_ID,
role: 'assistant',
blocks: [{ type: 'text', text }],
timestamp: null,
source: 'hook'
}
}