Skip to content

Commit fd4fa1c

Browse files
committed
File writes
1 parent ce1f00c commit fd4fa1c

File tree

13 files changed

+4557
-2842
lines changed

13 files changed

+4557
-2842
lines changed

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,26 @@ function TextEditor({
255255
? fetchedContent
256256
: `${fetchedContent}\n${streamingContent}`
257257
// #region agent log
258-
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'6f10b0'},body:JSON.stringify({sessionId:'6f10b0',location:'file-viewer.tsx:TextEditor-merge',message:'streaming merge',data:{streamingMode,fetchedContentLen:fetchedContent?.length,streamingContentLen:streamingContent.length,nextContentLen:nextContent.length,fetchedUndefined:fetchedContent===undefined,usedReplace:streamingMode==='replace'||fetchedContent===undefined,nextPreview:nextContent.slice(0,200)},timestamp:Date.now(),hypothesisId:'H2-H3'})}).catch(()=>{});
258+
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06', {
259+
method: 'POST',
260+
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '6f10b0' },
261+
body: JSON.stringify({
262+
sessionId: '6f10b0',
263+
location: 'file-viewer.tsx:TextEditor-merge',
264+
message: 'streaming merge',
265+
data: {
266+
streamingMode,
267+
fetchedContentLen: fetchedContent?.length,
268+
streamingContentLen: streamingContent.length,
269+
nextContentLen: nextContent.length,
270+
fetchedUndefined: fetchedContent === undefined,
271+
usedReplace: streamingMode === 'replace' || fetchedContent === undefined,
272+
nextPreview: nextContent.slice(0, 200),
273+
},
274+
timestamp: Date.now(),
275+
hypothesisId: 'H2-H3',
276+
}),
277+
}).catch(() => {})
259278
// #endregion
260279
setContent(nextContent)
261280
contentRef.current = nextContent
@@ -264,8 +283,8 @@ function TextEditor({
264283
}
265284

266285
if (wasStreamingRef.current) {
267-
wasStreamingRef.current = false
268-
if (fetchedContent !== undefined) {
286+
if (fetchedContent !== undefined && fetchedContent !== savedContentRef.current) {
287+
wasStreamingRef.current = false
269288
setContent(fetchedContent)
270289
setSavedContent(fetchedContent)
271290
savedContentRef.current = fetchedContent
@@ -377,7 +396,8 @@ function TextEditor({
377396
)
378397

379398
const isStreaming = streamingContent !== undefined
380-
const revealedContent = useStreamingText(content, isStreaming)
399+
const shouldAnimateStreaming = isStreaming && streamingMode === 'append'
400+
const revealedContent = useStreamingText(content, shouldAnimateStreaming)
381401

382402
const textareaStuckRef = useRef(true)
383403

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,30 @@ export const ResourceContent = memo(function ResourceContent({
122122

123123
// #region agent log
124124
if (streamingFile) {
125-
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'6f10b0'},body:JSON.stringify({sessionId:'6f10b0',location:'resource-content.tsx:streaming-context',message:'streaming state',data:{resourceId:resource.id,resourceType:resource.type,streamOp:streamOperation,isPatch:isPatchStream,isWrite:isWriteStream,isUpdate:isUpdateStream,hasActiveFileRecord:!!activeFileRecord,hasFetchedContent:!!fetchedFileContent,fetchedContentLen:fetchedFileContent?.length,streamingFileContentLen:streamingFile.content.length,streamingFileName:streamingFile.fileName,streamingFileMode:isWriteStream?'append':'replace'},timestamp:Date.now()})}).catch(()=>{});
125+
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06', {
126+
method: 'POST',
127+
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '6f10b0' },
128+
body: JSON.stringify({
129+
sessionId: '6f10b0',
130+
location: 'resource-content.tsx:streaming-context',
131+
message: 'streaming state',
132+
data: {
133+
resourceId: resource.id,
134+
resourceType: resource.type,
135+
streamOp: streamOperation,
136+
isPatch: isPatchStream,
137+
isWrite: isWriteStream,
138+
isUpdate: isUpdateStream,
139+
hasActiveFileRecord: !!activeFileRecord,
140+
hasFetchedContent: !!fetchedFileContent,
141+
fetchedContentLen: fetchedFileContent?.length,
142+
streamingFileContentLen: streamingFile.content.length,
143+
streamingFileName: streamingFile.fileName,
144+
streamingFileMode: isWriteStream ? 'append' : 'replace',
145+
},
146+
timestamp: Date.now(),
147+
}),
148+
}).catch(() => {})
126149
}
127150
// #endregion
128151
const streamingExtractedContent = useMemo(() => {
@@ -132,13 +155,41 @@ export const ResourceContent = memo(function ResourceContent({
132155
if (isPatchStream) {
133156
if (!fetchedFileContent) {
134157
// #region agent log
135-
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'6f10b0'},body:JSON.stringify({sessionId:'6f10b0',location:'resource-content.tsx:patch-no-fetched',message:'patch but no fetchedFileContent',data:{resourceId:resource.id,activeFileRecordId:activeFileRecord?.id},timestamp:Date.now(),hypothesisId:'H1'})}).catch(()=>{});
158+
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06', {
159+
method: 'POST',
160+
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '6f10b0' },
161+
body: JSON.stringify({
162+
sessionId: '6f10b0',
163+
location: 'resource-content.tsx:patch-no-fetched',
164+
message: 'patch but no fetchedFileContent',
165+
data: { resourceId: resource.id, activeFileRecordId: activeFileRecord?.id },
166+
timestamp: Date.now(),
167+
hypothesisId: 'H1',
168+
}),
169+
}).catch(() => {})
136170
// #endregion
137171
return undefined
138172
}
139173
const patchResult = extractPatchPreview(streamingFile, fetchedFileContent)
140174
// #region agent log
141-
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'6f10b0'},body:JSON.stringify({sessionId:'6f10b0',location:'resource-content.tsx:patch-result',message:'extractPatchPreview result',data:{hasPatchResult:!!patchResult,patchResultLen:patchResult?.length,fetchedLen:fetchedFileContent.length,contentPreview:streamingFile.content.slice(0,200),edit:streamingFile.edit},timestamp:Date.now(),hypothesisId:'H4'})}).catch(()=>{});
175+
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06', {
176+
method: 'POST',
177+
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '6f10b0' },
178+
body: JSON.stringify({
179+
sessionId: '6f10b0',
180+
location: 'resource-content.tsx:patch-result',
181+
message: 'extractPatchPreview result',
182+
data: {
183+
hasPatchResult: !!patchResult,
184+
patchResultLen: patchResult?.length,
185+
fetchedLen: fetchedFileContent.length,
186+
contentPreview: streamingFile.content.slice(0, 200),
187+
edit: streamingFile.edit,
188+
},
189+
timestamp: Date.now(),
190+
hypothesisId: 'H4',
191+
}),
192+
}).catch(() => {})
142193
// #endregion
143194
return patchResult
144195
}
@@ -147,14 +198,36 @@ export const ResourceContent = memo(function ResourceContent({
147198
if (extracted.length === 0) return undefined
148199

149200
// #region agent log
150-
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'6f10b0'},body:JSON.stringify({sessionId:'6f10b0',location:'resource-content.tsx:write-update-content',message:'extracted content for write/update',data:{streamOp:streamOperation,extractedLen:extracted.length,extractedPreview:extracted.slice(0,150)},timestamp:Date.now(),hypothesisId:'H2'})}).catch(()=>{});
201+
fetch('http://127.0.0.1:7774/ingest/b056eec6-a1ee-457f-8556-85f94314ca06', {
202+
method: 'POST',
203+
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '6f10b0' },
204+
body: JSON.stringify({
205+
sessionId: '6f10b0',
206+
location: 'resource-content.tsx:write-update-content',
207+
message: 'extracted content for write/update',
208+
data: {
209+
streamOp: streamOperation,
210+
extractedLen: extracted.length,
211+
extractedPreview: extracted.slice(0, 150),
212+
},
213+
timestamp: Date.now(),
214+
hypothesisId: 'H2',
215+
}),
216+
}).catch(() => {})
151217
// #endregion
152218

153219
if (isUpdateStream) return extracted
154220
if (isWriteStream) return extracted
155221

156222
return undefined
157-
}, [streamingFile, streamOperation, isWriteStream, isPatchStream, isUpdateStream, fetchedFileContent])
223+
}, [
224+
streamingFile,
225+
streamOperation,
226+
isWriteStream,
227+
isPatchStream,
228+
isUpdateStream,
229+
fetchedFileContent,
230+
])
158231
const syntheticFile = useMemo(() => {
159232
const ext = getFileExtension(streamFileName)
160233
const SOURCE_MIME_MAP: Record<string, string> = {
@@ -176,17 +249,14 @@ export const ResourceContent = memo(function ResourceContent({
176249
}
177250
}, [workspaceId, streamFileName])
178251

179-
const streamingFileMode: 'append' | 'replace' =
180-
isWriteStream ? 'append' : 'replace'
252+
const streamingFileMode: 'append' | 'replace' = isWriteStream ? 'append' : 'replace'
181253

182254
// For existing file resources (not streaming-file), only pass streaming
183255
// content for patch operations where the preview splices new content into
184256
// the displayed file. Update operations re-stream the entire file from
185257
// scratch which causes visual duplication of already-visible content.
186258
const embeddedStreamingContent =
187-
resource.id !== 'streaming-file' && isUpdateStream
188-
? undefined
189-
: streamingExtractedContent
259+
resource.id !== 'streaming-file' && isUpdateStream ? undefined : streamingExtractedContent
190260

191261
if (streamingFile && resource.id === 'streaming-file') {
192262
return (
@@ -513,7 +583,13 @@ interface EmbeddedFileProps {
513583
streamingMode?: 'append' | 'replace'
514584
}
515585

516-
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent, streamingMode }: EmbeddedFileProps) {
586+
function EmbeddedFile({
587+
workspaceId,
588+
fileId,
589+
previewMode,
590+
streamingContent,
591+
streamingMode,
592+
}: EmbeddedFileProps) {
517593
const { canEdit } = useUserPermissionsContext()
518594
const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId)
519595
const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId])
@@ -638,9 +714,7 @@ function extractPatchPreview(
638714
const strategy = typeof edit.strategy === 'string' ? edit.strategy : undefined
639715
const lines = existingContent.split('\n')
640716
const occurrence =
641-
typeof edit.occurrence === 'number' && Number.isFinite(edit.occurrence)
642-
? edit.occurrence
643-
: 1
717+
typeof edit.occurrence === 'number' && Number.isFinite(edit.occurrence) ? edit.occurrence : 1
644718

645719
if (strategy === 'search_replace') {
646720
const search = typeof edit.search === 'string' ? edit.search : ''
@@ -651,7 +725,9 @@ function extractPatchPreview(
651725
}
652726
const firstIdx = existingContent.indexOf(search)
653727
if (firstIdx === -1) return undefined
654-
return existingContent.slice(0, firstIdx) + replace + existingContent.slice(firstIdx + search.length)
728+
return (
729+
existingContent.slice(0, firstIdx) + replace + existingContent.slice(firstIdx + search.length)
730+
)
655731
}
656732

657733
const mode = typeof edit.mode === 'string' ? edit.mode : undefined

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from 'react'
1010
import { Button, Tooltip } from '@/components/emcn'
1111
import { Columns3, Eye, PanelLeft, Pencil } from '@/components/emcn/icons'
12-
import { isEphemeralResource } from '@/lib/copilot/resources/types'
1312
import { SIM_RESOURCE_DRAG_TYPE } from '@/lib/copilot/resource-types'
13+
import { isEphemeralResource } from '@/lib/copilot/resources/types'
1414
import { cn } from '@/lib/core/utils/cn'
1515
import type { PreviewMode } from '@/app/workspace/[workspaceId]/files/components/file-viewer'
1616
import { AddResourceDropdown } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown'

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,15 @@ interface MothershipViewProps {
5858
onCollapse: () => void
5959
isCollapsed: boolean
6060
className?: string
61-
streamingFile?:
62-
| {
63-
toolCallId?: string
64-
fileName: string
65-
fileId?: string
66-
targetKind?: 'new_file' | 'file_id'
67-
operation?: string
68-
edit?: Record<string, unknown>
69-
content: string
70-
}
71-
| null
61+
streamingFile?: {
62+
toolCallId?: string
63+
fileName: string
64+
fileId?: string
65+
targetKind?: 'new_file' | 'file_id'
66+
operation?: string
67+
edit?: Record<string, unknown>
68+
content: string
69+
} | null
7270
genericResourceData?: GenericResourceData
7371
}
7472

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -904,13 +904,15 @@ export function useChat(
904904
const target = asPayloadRecord(payload.target)
905905
const nextSession: StreamingFilePreview = {
906906
...prevSession,
907-
operation: typeof payload.operation === 'string' ? payload.operation : prevSession.operation,
907+
operation:
908+
typeof payload.operation === 'string'
909+
? payload.operation
910+
: prevSession.operation,
908911
targetKind:
909912
target?.kind === 'new_file' || target?.kind === 'file_id'
910913
? (target.kind as 'new_file' | 'file_id')
911914
: prevSession.targetKind,
912-
fileId:
913-
typeof target?.fileId === 'string' ? target.fileId : prevSession.fileId,
915+
fileId: typeof target?.fileId === 'string' ? target.fileId : prevSession.fileId,
914916
fileName:
915917
typeof target?.fileName === 'string' ? target.fileName : prevSession.fileName,
916918
}
@@ -954,13 +956,11 @@ export function useChat(
954956
break
955957
}
956958

957-
if (previewPhase === 'file_preview_content_delta') {
958-
const delta =
959-
typeof payload.delta === 'string' ? payload.delta : ''
960-
if (!delta) break
959+
if (previewPhase === 'file_preview_content') {
960+
const content = typeof payload.content === 'string' ? payload.content : ''
961961
const nextSession: StreamingFilePreview = {
962962
...prevSession,
963-
content: (prevSession.content ?? '') + delta,
963+
content,
964964
}
965965
sessions.set(id, nextSession)
966966
activeFilePreviewToolCallIdRef.current = id

0 commit comments

Comments
 (0)