Skip to content

Commit bec2e58

Browse files
committed
Temp
1 parent 35aae77 commit bec2e58

10 files changed

Lines changed: 545 additions & 677 deletions

File tree

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ interface FileViewerProps {
130130
onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void
131131
saveRef?: React.MutableRefObject<(() => Promise<void>) | null>
132132
streamingContent?: string
133+
streamingMode?: 'append' | 'replace'
133134
}
134135

135136
export function FileViewer({
@@ -143,6 +144,7 @@ export function FileViewer({
143144
onSaveStatusChange,
144145
saveRef,
145146
streamingContent,
147+
streamingMode,
146148
}: FileViewerProps) {
147149
const category = resolveFileCategory(file.type, file.name)
148150

@@ -158,6 +160,7 @@ export function FileViewer({
158160
onSaveStatusChange={onSaveStatusChange}
159161
saveRef={saveRef}
160162
streamingContent={streamingContent}
163+
streamingMode={streamingMode}
161164
/>
162165
)
163166
}
@@ -195,6 +198,7 @@ interface TextEditorProps {
195198
onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void
196199
saveRef?: React.MutableRefObject<(() => Promise<void>) | null>
197200
streamingContent?: string
201+
streamingMode?: 'append' | 'replace'
198202
}
199203

200204
function TextEditor({
@@ -207,6 +211,7 @@ function TextEditor({
207211
onSaveStatusChange,
208212
saveRef,
209213
streamingContent,
214+
streamingMode = 'append',
210215
}: TextEditorProps) {
211216
const initializedRef = useRef(false)
212217
const contentRef = useRef('')
@@ -237,15 +242,13 @@ function TextEditor({
237242
const [content, setContent] = useState('')
238243
const [savedContent, setSavedContent] = useState('')
239244
const savedContentRef = useRef('')
245+
const wasStreamingRef = useRef(false)
240246

241247
useEffect(() => {
242248
if (streamingContent !== undefined) {
243-
const isSplicedFull =
244-
fetchedContent !== undefined &&
245-
streamingContent.length > fetchedContent.length * 0.5 &&
246-
streamingContent.startsWith(fetchedContent.slice(0, Math.min(100, fetchedContent.length)))
249+
wasStreamingRef.current = true
247250
const nextContent =
248-
fetchedContent === undefined || isSplicedFull
251+
streamingMode === 'replace' || fetchedContent === undefined
249252
? streamingContent
250253
: fetchedContent.endsWith(streamingContent) ||
251254
fetchedContent.endsWith(`\n${streamingContent}`)
@@ -257,6 +260,17 @@ function TextEditor({
257260
return
258261
}
259262

263+
if (wasStreamingRef.current) {
264+
wasStreamingRef.current = false
265+
if (fetchedContent !== undefined) {
266+
setContent(fetchedContent)
267+
setSavedContent(fetchedContent)
268+
savedContentRef.current = fetchedContent
269+
contentRef.current = fetchedContent
270+
return
271+
}
272+
}
273+
260274
if (fetchedContent === undefined) return
261275

262276
if (!initializedRef.current) {

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import {
4-
FileWrite,
4+
File as FileTool,
55
Read as ReadTool,
66
ToolSearchToolRegex,
77
WorkspaceFile,
@@ -43,12 +43,12 @@ const SUBAGENT_KEYS = new Set(Object.keys(SUBAGENT_LABELS))
4343

4444
/**
4545
* Maps subagent names to the Mothership tool that dispatches them when the
46-
* tool name differs from the subagent name (e.g. `workspace_file` → `file_write`).
46+
* tool name differs from the subagent name (e.g. `workspace_file` → `file`).
4747
* When a `subagent` block arrives, any trailing dispatch tool in the previous
4848
* group is absorbed so it doesn't render as a separate Mothership entry.
4949
*/
5050
const SUBAGENT_DISPATCH_TOOLS: Record<string, string> = {
51-
[FileWrite.id]: WorkspaceFile.id,
51+
[FileTool.id]: WorkspaceFile.id,
5252
}
5353

5454
function isToolResultRead(params?: Record<string, unknown>): boolean {

apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const TOOL_ICONS: Record<string, IconComponent> = {
5757
debug: Bug,
5858
context_compaction: Asterisk,
5959
open_resource: Eye,
60-
file_write: File,
60+
file: File,
6161
}
6262

6363
export function getAgentIcon(name: string): IconComponent {

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,16 @@ export const ResourceContent = memo(function ResourceContent({
7777
}: ResourceContentProps) {
7878
const streamFileName = streamingFile?.fileName || 'file.md'
7979

80-
const isPatchStream = useMemo(() => {
81-
if (!streamingFile) return false
82-
return /"operation"\s*:\s*"patch"/.test(streamingFile.content)
80+
const streamOperation = useMemo(() => {
81+
if (!streamingFile) return undefined
82+
const m = streamingFile.content.match(/"operation"\s*:\s*"(\w+)"/)
83+
return m?.[1]
8384
}, [streamingFile])
8485

86+
const isWriteStream = streamOperation === 'write'
87+
const isPatchStream = streamOperation === 'patch'
88+
const isUpdateStream = streamOperation === 'update'
89+
8590
const { data: allFiles = [] } = useWorkspaceFiles(workspaceId)
8691
const activeFileRecord = useMemo(() => {
8792
if (!isPatchStream || resource.type !== 'file') return undefined
@@ -104,13 +109,25 @@ export const ResourceContent = memo(function ResourceContent({
104109
if (!streamingFile) return undefined
105110
const raw = streamingFile.content
106111

107-
if (isPatchStream && fetchedFileContent) {
112+
// Do not guess. Until the operation key has streamed in, we don't know
113+
// whether the payload should append, replace, or splice into the file.
114+
// Rendering early here can show content at the end of the file and then
115+
// "snap" to the right place once the operation/mode becomes known.
116+
if (!streamOperation) return undefined
117+
118+
if (isPatchStream) {
119+
if (!fetchedFileContent) return undefined
108120
return extractPatchPreview(raw, fetchedFileContent)
109121
}
110122

111123
const extracted = extractFileContent(raw)
112-
return extracted.length > 0 ? extracted : undefined
113-
}, [streamingFile, isPatchStream, fetchedFileContent])
124+
if (extracted.length === 0) return undefined
125+
126+
if (isUpdateStream) return extracted
127+
if (isWriteStream) return extracted
128+
129+
return undefined
130+
}, [streamingFile, streamOperation, isWriteStream, isPatchStream, isUpdateStream, fetchedFileContent])
114131
const syntheticFile = useMemo(() => {
115132
const ext = getFileExtension(streamFileName)
116133
const SOURCE_MIME_MAP: Record<string, string> = {
@@ -132,6 +149,9 @@ export const ResourceContent = memo(function ResourceContent({
132149
}
133150
}, [workspaceId, streamFileName])
134151

152+
const streamingFileMode: 'append' | 'replace' =
153+
isWriteStream ? 'append' : 'replace'
154+
135155
if (streamingFile && resource.id === 'streaming-file') {
136156
return (
137157
<div className='flex h-full flex-col overflow-hidden'>
@@ -142,6 +162,7 @@ export const ResourceContent = memo(function ResourceContent({
142162
canEdit={false}
143163
previewMode={previewMode ?? 'preview'}
144164
streamingContent={streamingExtractedContent}
165+
streamingMode={streamingFileMode}
145166
/>
146167
) : (
147168
<div className='flex h-full items-center justify-center'>
@@ -164,6 +185,7 @@ export const ResourceContent = memo(function ResourceContent({
164185
fileId={resource.id}
165186
previewMode={previewMode}
166187
streamingContent={streamingExtractedContent}
188+
streamingMode={streamingFileMode}
167189
/>
168190
)
169191

@@ -448,9 +470,10 @@ interface EmbeddedFileProps {
448470
fileId: string
449471
previewMode?: PreviewMode
450472
streamingContent?: string
473+
streamingMode?: 'append' | 'replace'
451474
}
452475

453-
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: EmbeddedFileProps) {
476+
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent, streamingMode }: EmbeddedFileProps) {
454477
const { canEdit } = useUserPermissionsContext()
455478
const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId)
456479
const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId])
@@ -478,6 +501,7 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em
478501
file={file}
479502
workspaceId={workspaceId}
480503
canEdit={canEdit}
504+
streamingMode={streamingMode}
481505
previewMode={previewMode}
482506
streamingContent={streamingContent}
483507
/>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function fileTitlesEquivalent(streamFileName: string, resourceTitle: string): bo
3030
}
3131

3232
/**
33-
* Whether the active resource should show the in-progress file_write stream.
33+
* Whether the active resource should show the in-progress file stream.
3434
* The synthetic `streaming-file` tab always shows it; a real file tab shows it when
3535
* the streamed `fileName` matches that resource (so users who stay on the open file see live text).
3636
*/

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
DeployApi,
2828
DeployChat,
2929
DeployMcp,
30-
FileWrite,
30+
File as FileTool,
3131
MoveFolder,
3232
MoveWorkflow,
3333
Read as ReadTool,
@@ -914,9 +914,16 @@ export function useChat(
914914
)
915915

916916
if (existingFileMatch) {
917-
setActiveResourceId(matchedResourceId)
918-
setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file'))
919-
} else if (fileName || fileIdMatch || activeSubagent === 'file_write') {
917+
const hadStreamingResource = resourcesRef.current.some(
918+
(resource) => resource.id === 'streaming-file'
919+
)
920+
if (hadStreamingResource) {
921+
setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file'))
922+
setActiveResourceId(matchedResourceId)
923+
} else if (activeResourceIdRef.current === null) {
924+
setActiveResourceId(matchedResourceId)
925+
}
926+
} else if (fileName || fileIdMatch || activeSubagent === FileTool.id) {
920927
const hasStreamingResource = resourcesRef.current.some(
921928
(resource) => resource.id === 'streaming-file'
922929
)
@@ -927,8 +934,6 @@ export function useChat(
927934
title: fileName || 'Writing file...',
928935
})
929936
setActiveResourceId('streaming-file')
930-
} else if (activeResourceIdRef.current !== 'streaming-file') {
931-
setActiveResourceId('streaming-file')
932937
}
933938
}
934939
const next = { fileName, fileId: matchedResourceId, content: raw }
@@ -1294,7 +1299,7 @@ export function useChat(
12941299
if (!isSameActiveSubagent) {
12951300
blocks.push({ type: 'subagent', content: name })
12961301
}
1297-
if (name === FileWrite.id) {
1302+
if (name === FileTool.id) {
12981303
const emptyFile = { fileName: '', content: '' }
12991304
streamingFileRef.current = emptyFile
13001305
setStreamingFile(emptyFile)

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export const SUBAGENT_LABELS: Record<string, string> = {
185185
run: 'Run agent',
186186
agent: 'Agent manager',
187187
job: 'Job agent',
188-
file_write: 'File Write',
188+
file: 'File',
189189
} as const
190190

191191
export interface ToolUIMetadata {

0 commit comments

Comments
 (0)