mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-12 15:34:58 -05:00
Compare commits
1 Commits
main
...
feat/strea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031866e07c |
@@ -108,7 +108,7 @@ const SmoothThinkingText = memo(
|
||||
return (
|
||||
<div
|
||||
ref={textRef}
|
||||
className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.4] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-6 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 font-season text-[12px] text-[var(--text-muted)]'
|
||||
className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.4] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-8 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 font-season text-[12px] text-[var(--text-muted)]'
|
||||
>
|
||||
<CopilotMarkdownRenderer content={displayedContent} />
|
||||
</div>
|
||||
@@ -355,7 +355,7 @@ export function ThinkingBlock({
|
||||
isExpanded ? 'mt-1.5 max-h-[150px] opacity-100' : 'max-h-0 opacity-0'
|
||||
)}
|
||||
>
|
||||
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.4] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-6 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 font-season text-[12px] text-[var(--text-muted)]'>
|
||||
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.4] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-8 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 font-season text-[12px] text-[var(--text-muted)]'>
|
||||
<CopilotMarkdownRenderer content={cleanContent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { serializeMessagesForDB } from './serialization'
|
||||
|
||||
const logger = createLogger('CopilotMessagePersistence')
|
||||
|
||||
export async function persistMessages(params: {
|
||||
interface PersistParams {
|
||||
chatId: string
|
||||
messages: CopilotMessage[]
|
||||
sensitiveCredentialIds?: Set<string>
|
||||
@@ -13,24 +13,29 @@ export async function persistMessages(params: {
|
||||
mode?: string
|
||||
model?: string
|
||||
conversationId?: string
|
||||
}): Promise<boolean> {
|
||||
}
|
||||
|
||||
/** Builds the JSON body used by both fetch and sendBeacon persistence paths. */
|
||||
function buildPersistBody(params: PersistParams): string {
|
||||
const dbMessages = serializeMessagesForDB(
|
||||
params.messages,
|
||||
params.sensitiveCredentialIds ?? new Set<string>()
|
||||
)
|
||||
return JSON.stringify({
|
||||
chatId: params.chatId,
|
||||
messages: dbMessages,
|
||||
...(params.planArtifact !== undefined ? { planArtifact: params.planArtifact } : {}),
|
||||
...(params.mode || params.model ? { config: { mode: params.mode, model: params.model } } : {}),
|
||||
...(params.conversationId ? { conversationId: params.conversationId } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function persistMessages(params: PersistParams): Promise<boolean> {
|
||||
try {
|
||||
const dbMessages = serializeMessagesForDB(
|
||||
params.messages,
|
||||
params.sensitiveCredentialIds ?? new Set<string>()
|
||||
)
|
||||
const response = await fetch(COPILOT_UPDATE_MESSAGES_API_PATH, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
chatId: params.chatId,
|
||||
messages: dbMessages,
|
||||
...(params.planArtifact !== undefined ? { planArtifact: params.planArtifact } : {}),
|
||||
...(params.mode || params.model
|
||||
? { config: { mode: params.mode, model: params.model } }
|
||||
: {}),
|
||||
...(params.conversationId ? { conversationId: params.conversationId } : {}),
|
||||
}),
|
||||
body: buildPersistBody(params),
|
||||
})
|
||||
return response.ok
|
||||
} catch (error) {
|
||||
@@ -41,3 +46,27 @@ export async function persistMessages(params: {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists messages using navigator.sendBeacon, which is reliable during page unload.
|
||||
* Unlike fetch, sendBeacon is guaranteed to be queued even when the page is being torn down.
|
||||
*/
|
||||
export function persistMessagesBeacon(params: PersistParams): boolean {
|
||||
try {
|
||||
const body = buildPersistBody(params)
|
||||
const blob = new Blob([body], { type: 'application/json' })
|
||||
const sent = navigator.sendBeacon(COPILOT_UPDATE_MESSAGES_API_PATH, blob)
|
||||
if (!sent) {
|
||||
logger.warn('sendBeacon returned false — browser may have rejected the request', {
|
||||
chatId: params.chatId,
|
||||
})
|
||||
}
|
||||
return sent
|
||||
} catch (error) {
|
||||
logger.warn('Failed to persist messages via sendBeacon', {
|
||||
chatId: params.chatId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
buildToolCallsById,
|
||||
normalizeMessagesForUI,
|
||||
persistMessages,
|
||||
persistMessagesBeacon,
|
||||
saveMessageCheckpoint,
|
||||
} from '@/lib/copilot/messages'
|
||||
import type { CopilotTransportMode } from '@/lib/copilot/models'
|
||||
@@ -78,6 +79,28 @@ let _isPageUnloading = false
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
_isPageUnloading = true
|
||||
|
||||
// Emergency persistence: flush any pending streaming updates to the store and
|
||||
// persist via sendBeacon (which is guaranteed to be queued during page teardown).
|
||||
// Without this, thinking blocks and in-progress content are lost on refresh.
|
||||
try {
|
||||
const state = useCopilotStore.getState()
|
||||
if (state.isSendingMessage && state.currentChat) {
|
||||
// Flush batched streaming updates into the store messages
|
||||
flushStreamingUpdates(useCopilotStore.setState.bind(useCopilotStore))
|
||||
const flushedState = useCopilotStore.getState()
|
||||
persistMessagesBeacon({
|
||||
chatId: flushedState.currentChat!.id,
|
||||
messages: flushedState.messages,
|
||||
sensitiveCredentialIds: flushedState.sensitiveCredentialIds,
|
||||
planArtifact: flushedState.streamingPlanContent || null,
|
||||
mode: flushedState.mode,
|
||||
model: flushedState.selectedModel,
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Best-effort — don't let errors prevent page unload
|
||||
}
|
||||
})
|
||||
}
|
||||
function isPageUnloading(): boolean {
|
||||
@@ -1461,19 +1484,26 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
// Immediately put all in-progress tools into aborted state
|
||||
abortAllInProgressTools(set, get)
|
||||
|
||||
// Persist whatever contentBlocks/text we have to keep ordering for reloads
|
||||
// Persist whatever contentBlocks/text we have to keep ordering for reloads.
|
||||
// During page unload, use sendBeacon which is guaranteed to be queued even
|
||||
// as the page tears down. Regular async fetch won't complete in time.
|
||||
const { currentChat, streamingPlanContent, mode, selectedModel } = get()
|
||||
if (currentChat) {
|
||||
try {
|
||||
const currentMessages = get().messages
|
||||
void persistMessages({
|
||||
const persistParams = {
|
||||
chatId: currentChat.id,
|
||||
messages: currentMessages,
|
||||
sensitiveCredentialIds: get().sensitiveCredentialIds,
|
||||
planArtifact: streamingPlanContent || null,
|
||||
mode,
|
||||
model: selectedModel,
|
||||
})
|
||||
}
|
||||
if (isPageUnloading()) {
|
||||
persistMessagesBeacon(persistParams)
|
||||
} else {
|
||||
void persistMessages(persistParams)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('[Copilot] Failed to queue abort snapshot persistence', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
|
||||
Reference in New Issue
Block a user