11'use client'
22
3- import { lazy , memo , Suspense , useCallback , useEffect , useMemo } from 'react'
3+ import { lazy , memo , Suspense , useCallback , useEffect , useMemo , useState } from 'react'
44import { createLogger } from '@sim/logger'
5+ import { useQueryClient } from '@tanstack/react-query'
56import { Square } from 'lucide-react'
67import { useRouter } from 'next/navigation'
78import { Button , PlayOutline , Skeleton , Tooltip } from '@/components/emcn'
@@ -46,7 +47,11 @@ import { useUsageLimits } from '@/app/workspace/[workspaceId]/w/[workflowId]/com
4647import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
4748import { useFolders } from '@/hooks/queries/folders'
4849import { useWorkflows } from '@/hooks/queries/workflows'
49- import { useWorkspaceFileContent , useWorkspaceFiles } from '@/hooks/queries/workspace-files'
50+ import {
51+ useWorkspaceFileContent ,
52+ useWorkspaceFiles ,
53+ workspaceFilesKeys ,
54+ } from '@/hooks/queries/workspace-files'
5055import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
5156import { useExecutionStore } from '@/stores/execution/store'
5257import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -91,6 +96,7 @@ export const ResourceContent = memo(function ResourceContent({
9196 streamingFile,
9297 genericResourceData,
9398} : ResourceContentProps ) {
99+ const queryClient = useQueryClient ( )
94100 const streamFileName = streamingFile ?. fileName || 'file.md'
95101
96102 const streamOperation = useMemo ( ( ) => {
@@ -114,6 +120,7 @@ export const ResourceContent = memo(function ResourceContent({
114120 previewFileRecord ?. type === 'text/x-pptxgenjs' ||
115121 previewFileRecord ?. type === 'text/x-docxjs' ||
116122 previewFileRecord ?. type === 'text/x-pdflibjs'
123+ const previewContentMode = isSourceMime ? 'raw' : 'text'
117124
118125 const { data : fetchedFileContent } = useWorkspaceFileContent (
119126 workspaceId ,
@@ -122,11 +129,57 @@ export const ResourceContent = memo(function ResourceContent({
122129 isSourceMime
123130 )
124131
132+ const frozenPreviewBaseKey =
133+ streamingFile ?. toolCallId &&
134+ previewFileRecord ?. id &&
135+ streamingFile . targetKind === 'file_id' &&
136+ ( streamOperation === 'append' || streamOperation === 'patch' )
137+ ? `${ streamingFile . toolCallId } :${ streamOperation } :${ previewFileRecord . id } `
138+ : null
139+ const cachedFrozenPreviewBase =
140+ frozenPreviewBaseKey && previewFileRecord ?. id
141+ ? queryClient . getQueryData < string > (
142+ workspaceFilesKeys . content ( workspaceId , previewFileRecord . id , previewContentMode )
143+ )
144+ : undefined
145+ const [ frozenPreviewBaseState , setFrozenPreviewBaseState ] = useState < {
146+ key : string | null
147+ content : string | null
148+ } > ( { key : null , content : null } )
149+
150+ useEffect ( ( ) => {
151+ if ( ! frozenPreviewBaseKey || ! previewFileRecord ?. id ) {
152+ setFrozenPreviewBaseState ( ( prev ) => ( prev . key === null ? prev : { key : null , content : null } ) )
153+ return
154+ }
155+
156+ setFrozenPreviewBaseState ( ( prev ) => {
157+ if ( prev . key === frozenPreviewBaseKey ) {
158+ return prev
159+ }
160+ return typeof cachedFrozenPreviewBase === 'string'
161+ ? { key : frozenPreviewBaseKey , content : cachedFrozenPreviewBase }
162+ : { key : frozenPreviewBaseKey , content : null }
163+ } )
164+ } , [ cachedFrozenPreviewBase , frozenPreviewBaseKey , previewFileRecord ?. id ] )
165+
166+ const frozenExistingFilePreviewBase =
167+ frozenPreviewBaseState . key === frozenPreviewBaseKey
168+ ? ( frozenPreviewBaseState . content ?? undefined )
169+ : typeof cachedFrozenPreviewBase === 'string'
170+ ? cachedFrozenPreviewBase
171+ : undefined
172+
125173 const streamingExtractedContent = useMemo ( ( ) => {
126174 if ( ! streamingFile ) return undefined
127175 if ( ! streamOperation ) return undefined
128176
129177 if ( isPatchStream ) {
178+ if ( streamingFile . targetKind === 'file_id' ) {
179+ if ( frozenExistingFilePreviewBase === undefined ) return undefined
180+ if ( ! shouldApplyPatchPreview ( streamingFile ) ) return undefined
181+ return extractPatchPreview ( streamingFile , frozenExistingFilePreviewBase )
182+ }
130183 if ( fetchedFileContent === undefined ) return undefined
131184 if ( ! shouldApplyPatchPreview ( streamingFile ) ) return undefined
132185 return extractPatchPreview ( streamingFile , fetchedFileContent )
@@ -138,8 +191,8 @@ export const ResourceContent = memo(function ResourceContent({
138191
139192 if ( streamOperation === 'append' ) {
140193 if ( streamingFile . targetKind === 'file_id' ) {
141- if ( fetchedFileContent === undefined ) return undefined
142- return buildAppendPreview ( fetchedFileContent , extracted )
194+ if ( frozenExistingFilePreviewBase === undefined ) return undefined
195+ return buildAppendPreview ( frozenExistingFilePreviewBase , extracted )
143196 }
144197 return extracted . length > 0 ? extracted : undefined
145198 }
@@ -158,6 +211,7 @@ export const ResourceContent = memo(function ResourceContent({
158211 isPatchStream ,
159212 isUpdateStream ,
160213 fetchedFileContent ,
214+ frozenExistingFilePreviewBase ,
161215 ] )
162216 const syntheticFile = useMemo ( ( ) => {
163217 const ext = getFileExtension ( streamFileName )
@@ -732,13 +786,5 @@ function shouldApplyPatchPreview(streamingFile: {
732786function buildAppendPreview ( existingContent : string , incomingContent : string ) : string {
733787 if ( incomingContent . length === 0 ) return existingContent
734788 if ( existingContent . length === 0 ) return incomingContent
735- // The fetched file can advance to the just-written content before the
736- // streaming preview clears. Keep append previews idempotent in that window.
737- if (
738- existingContent . endsWith ( incomingContent ) ||
739- existingContent . endsWith ( `\n${ incomingContent } ` )
740- ) {
741- return existingContent
742- }
743789 return `${ existingContent } \n${ incomingContent } `
744790}
0 commit comments