Skip to content

Commit 3893afd

Browse files
committed
Temp
1 parent 89f8842 commit 3893afd

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
@@ -85,11 +85,16 @@ export const ResourceContent = memo(function ResourceContent({
8585
}: ResourceContentProps) {
8686
const streamFileName = streamingFile?.fileName || 'file.md'
8787

88-
const isPatchStream = useMemo(() => {
89-
if (!streamingFile) return false
90-
return /"operation"\s*:\s*"patch"/.test(streamingFile.content)
88+
const streamOperation = useMemo(() => {
89+
if (!streamingFile) return undefined
90+
const m = streamingFile.content.match(/"operation"\s*:\s*"(\w+)"/)
91+
return m?.[1]
9192
}, [streamingFile])
9293

94+
const isWriteStream = streamOperation === 'write'
95+
const isPatchStream = streamOperation === 'patch'
96+
const isUpdateStream = streamOperation === 'update'
97+
9398
const { data: allFiles = [] } = useWorkspaceFiles(workspaceId)
9499
const activeFileRecord = useMemo(() => {
95100
if (!isPatchStream || resource.type !== 'file') return undefined
@@ -112,13 +117,25 @@ export const ResourceContent = memo(function ResourceContent({
112117
if (!streamingFile) return undefined
113118
const raw = streamingFile.content
114119

115-
if (isPatchStream && fetchedFileContent) {
120+
// Do not guess. Until the operation key has streamed in, we don't know
121+
// whether the payload should append, replace, or splice into the file.
122+
// Rendering early here can show content at the end of the file and then
123+
// "snap" to the right place once the operation/mode becomes known.
124+
if (!streamOperation) return undefined
125+
126+
if (isPatchStream) {
127+
if (!fetchedFileContent) return undefined
116128
return extractPatchPreview(raw, fetchedFileContent)
117129
}
118130

119131
const extracted = extractFileContent(raw)
120-
return extracted.length > 0 ? extracted : undefined
121-
}, [streamingFile, isPatchStream, fetchedFileContent])
132+
if (extracted.length === 0) return undefined
133+
134+
if (isUpdateStream) return extracted
135+
if (isWriteStream) return extracted
136+
137+
return undefined
138+
}, [streamingFile, streamOperation, isWriteStream, isPatchStream, isUpdateStream, fetchedFileContent])
122139
const syntheticFile = useMemo(() => {
123140
const ext = getFileExtension(streamFileName)
124141
const SOURCE_MIME_MAP: Record<string, string> = {
@@ -140,6 +157,9 @@ export const ResourceContent = memo(function ResourceContent({
140157
}
141158
}, [workspaceId, streamFileName])
142159

160+
const streamingFileMode: 'append' | 'replace' =
161+
isWriteStream ? 'append' : 'replace'
162+
143163
if (streamingFile && resource.id === 'streaming-file') {
144164
return (
145165
<div className='flex h-full flex-col overflow-hidden'>
@@ -150,6 +170,7 @@ export const ResourceContent = memo(function ResourceContent({
150170
canEdit={false}
151171
previewMode={previewMode ?? 'preview'}
152172
streamingContent={streamingExtractedContent}
173+
streamingMode={streamingFileMode}
153174
/>
154175
) : (
155176
<div className='flex h-full items-center justify-center'>
@@ -172,6 +193,7 @@ export const ResourceContent = memo(function ResourceContent({
172193
fileId={resource.id}
173194
previewMode={previewMode}
174195
streamingContent={streamingExtractedContent}
196+
streamingMode={streamingFileMode}
175197
/>
176198
)
177199

@@ -460,9 +482,10 @@ interface EmbeddedFileProps {
460482
fileId: string
461483
previewMode?: PreviewMode
462484
streamingContent?: string
485+
streamingMode?: 'append' | 'replace'
463486
}
464487

465-
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: EmbeddedFileProps) {
488+
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent, streamingMode }: EmbeddedFileProps) {
466489
const { canEdit } = useUserPermissionsContext()
467490
const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId)
468491
const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId])
@@ -490,6 +513,7 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em
490513
file={file}
491514
workspaceId={workspaceId}
492515
canEdit={canEdit}
516+
streamingMode={streamingMode}
493517
previewMode={previewMode}
494518
streamingContent={streamingContent}
495519
/>

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
@@ -190,7 +190,7 @@ export const SUBAGENT_LABELS: Record<string, string> = {
190190
run: 'Run agent',
191191
agent: 'Agent manager',
192192
job: 'Job agent',
193-
file_write: 'File Write',
193+
file: 'File',
194194
} as const
195195

196196
export interface ToolUIMetadata {

0 commit comments

Comments
 (0)