mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
Temp
This commit is contained in:
@@ -130,6 +130,7 @@ interface FileViewerProps {
|
||||
onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void
|
||||
saveRef?: React.MutableRefObject<(() => Promise<void>) | null>
|
||||
streamingContent?: string
|
||||
streamingMode?: 'append' | 'replace'
|
||||
}
|
||||
|
||||
export function FileViewer({
|
||||
@@ -143,6 +144,7 @@ export function FileViewer({
|
||||
onSaveStatusChange,
|
||||
saveRef,
|
||||
streamingContent,
|
||||
streamingMode,
|
||||
}: FileViewerProps) {
|
||||
const category = resolveFileCategory(file.type, file.name)
|
||||
|
||||
@@ -158,6 +160,7 @@ export function FileViewer({
|
||||
onSaveStatusChange={onSaveStatusChange}
|
||||
saveRef={saveRef}
|
||||
streamingContent={streamingContent}
|
||||
streamingMode={streamingMode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -195,6 +198,7 @@ interface TextEditorProps {
|
||||
onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void
|
||||
saveRef?: React.MutableRefObject<(() => Promise<void>) | null>
|
||||
streamingContent?: string
|
||||
streamingMode?: 'append' | 'replace'
|
||||
}
|
||||
|
||||
function TextEditor({
|
||||
@@ -207,6 +211,7 @@ function TextEditor({
|
||||
onSaveStatusChange,
|
||||
saveRef,
|
||||
streamingContent,
|
||||
streamingMode = 'append',
|
||||
}: TextEditorProps) {
|
||||
const initializedRef = useRef(false)
|
||||
const contentRef = useRef('')
|
||||
@@ -237,15 +242,13 @@ function TextEditor({
|
||||
const [content, setContent] = useState('')
|
||||
const [savedContent, setSavedContent] = useState('')
|
||||
const savedContentRef = useRef('')
|
||||
const wasStreamingRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (streamingContent !== undefined) {
|
||||
const isSplicedFull =
|
||||
fetchedContent !== undefined &&
|
||||
streamingContent.length > fetchedContent.length * 0.5 &&
|
||||
streamingContent.startsWith(fetchedContent.slice(0, Math.min(100, fetchedContent.length)))
|
||||
wasStreamingRef.current = true
|
||||
const nextContent =
|
||||
fetchedContent === undefined || isSplicedFull
|
||||
streamingMode === 'replace' || fetchedContent === undefined
|
||||
? streamingContent
|
||||
: fetchedContent.endsWith(streamingContent) ||
|
||||
fetchedContent.endsWith(`\n${streamingContent}`)
|
||||
@@ -257,6 +260,17 @@ function TextEditor({
|
||||
return
|
||||
}
|
||||
|
||||
if (wasStreamingRef.current) {
|
||||
wasStreamingRef.current = false
|
||||
if (fetchedContent !== undefined) {
|
||||
setContent(fetchedContent)
|
||||
setSavedContent(fetchedContent)
|
||||
savedContentRef.current = fetchedContent
|
||||
contentRef.current = fetchedContent
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchedContent === undefined) return
|
||||
|
||||
if (!initializedRef.current) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
FileWrite,
|
||||
File as FileTool,
|
||||
Read as ReadTool,
|
||||
ToolSearchToolRegex,
|
||||
WorkspaceFile,
|
||||
@@ -43,12 +43,12 @@ const SUBAGENT_KEYS = new Set(Object.keys(SUBAGENT_LABELS))
|
||||
|
||||
/**
|
||||
* Maps subagent names to the Mothership tool that dispatches them when the
|
||||
* tool name differs from the subagent name (e.g. `workspace_file` → `file_write`).
|
||||
* tool name differs from the subagent name (e.g. `workspace_file` → `file`).
|
||||
* When a `subagent` block arrives, any trailing dispatch tool in the previous
|
||||
* group is absorbed so it doesn't render as a separate Mothership entry.
|
||||
*/
|
||||
const SUBAGENT_DISPATCH_TOOLS: Record<string, string> = {
|
||||
[FileWrite.id]: WorkspaceFile.id,
|
||||
[FileTool.id]: WorkspaceFile.id,
|
||||
}
|
||||
|
||||
function isToolResultRead(params?: Record<string, unknown>): boolean {
|
||||
|
||||
@@ -57,7 +57,7 @@ const TOOL_ICONS: Record<string, IconComponent> = {
|
||||
debug: Bug,
|
||||
context_compaction: Asterisk,
|
||||
open_resource: Eye,
|
||||
file_write: File,
|
||||
file: File,
|
||||
}
|
||||
|
||||
export function getAgentIcon(name: string): IconComponent {
|
||||
|
||||
@@ -85,11 +85,16 @@ export const ResourceContent = memo(function ResourceContent({
|
||||
}: ResourceContentProps) {
|
||||
const streamFileName = streamingFile?.fileName || 'file.md'
|
||||
|
||||
const isPatchStream = useMemo(() => {
|
||||
if (!streamingFile) return false
|
||||
return /"operation"\s*:\s*"patch"/.test(streamingFile.content)
|
||||
const streamOperation = useMemo(() => {
|
||||
if (!streamingFile) return undefined
|
||||
const m = streamingFile.content.match(/"operation"\s*:\s*"(\w+)"/)
|
||||
return m?.[1]
|
||||
}, [streamingFile])
|
||||
|
||||
const isWriteStream = streamOperation === 'write'
|
||||
const isPatchStream = streamOperation === 'patch'
|
||||
const isUpdateStream = streamOperation === 'update'
|
||||
|
||||
const { data: allFiles = [] } = useWorkspaceFiles(workspaceId)
|
||||
const activeFileRecord = useMemo(() => {
|
||||
if (!isPatchStream || resource.type !== 'file') return undefined
|
||||
@@ -112,13 +117,25 @@ export const ResourceContent = memo(function ResourceContent({
|
||||
if (!streamingFile) return undefined
|
||||
const raw = streamingFile.content
|
||||
|
||||
if (isPatchStream && fetchedFileContent) {
|
||||
// Do not guess. Until the operation key has streamed in, we don't know
|
||||
// whether the payload should append, replace, or splice into the file.
|
||||
// Rendering early here can show content at the end of the file and then
|
||||
// "snap" to the right place once the operation/mode becomes known.
|
||||
if (!streamOperation) return undefined
|
||||
|
||||
if (isPatchStream) {
|
||||
if (!fetchedFileContent) return undefined
|
||||
return extractPatchPreview(raw, fetchedFileContent)
|
||||
}
|
||||
|
||||
const extracted = extractFileContent(raw)
|
||||
return extracted.length > 0 ? extracted : undefined
|
||||
}, [streamingFile, isPatchStream, fetchedFileContent])
|
||||
if (extracted.length === 0) return undefined
|
||||
|
||||
if (isUpdateStream) return extracted
|
||||
if (isWriteStream) return extracted
|
||||
|
||||
return undefined
|
||||
}, [streamingFile, streamOperation, isWriteStream, isPatchStream, isUpdateStream, fetchedFileContent])
|
||||
const syntheticFile = useMemo(() => {
|
||||
const ext = getFileExtension(streamFileName)
|
||||
const SOURCE_MIME_MAP: Record<string, string> = {
|
||||
@@ -140,6 +157,9 @@ export const ResourceContent = memo(function ResourceContent({
|
||||
}
|
||||
}, [workspaceId, streamFileName])
|
||||
|
||||
const streamingFileMode: 'append' | 'replace' =
|
||||
isWriteStream ? 'append' : 'replace'
|
||||
|
||||
if (streamingFile && resource.id === 'streaming-file') {
|
||||
return (
|
||||
<div className='flex h-full flex-col overflow-hidden'>
|
||||
@@ -150,6 +170,7 @@ export const ResourceContent = memo(function ResourceContent({
|
||||
canEdit={false}
|
||||
previewMode={previewMode ?? 'preview'}
|
||||
streamingContent={streamingExtractedContent}
|
||||
streamingMode={streamingFileMode}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-full items-center justify-center'>
|
||||
@@ -172,6 +193,7 @@ export const ResourceContent = memo(function ResourceContent({
|
||||
fileId={resource.id}
|
||||
previewMode={previewMode}
|
||||
streamingContent={streamingExtractedContent}
|
||||
streamingMode={streamingFileMode}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -460,9 +482,10 @@ interface EmbeddedFileProps {
|
||||
fileId: string
|
||||
previewMode?: PreviewMode
|
||||
streamingContent?: string
|
||||
streamingMode?: 'append' | 'replace'
|
||||
}
|
||||
|
||||
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: EmbeddedFileProps) {
|
||||
function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent, streamingMode }: EmbeddedFileProps) {
|
||||
const { canEdit } = useUserPermissionsContext()
|
||||
const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId)
|
||||
const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId])
|
||||
@@ -490,6 +513,7 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em
|
||||
file={file}
|
||||
workspaceId={workspaceId}
|
||||
canEdit={canEdit}
|
||||
streamingMode={streamingMode}
|
||||
previewMode={previewMode}
|
||||
streamingContent={streamingContent}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ function fileTitlesEquivalent(streamFileName: string, resourceTitle: string): bo
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the active resource should show the in-progress file_write stream.
|
||||
* Whether the active resource should show the in-progress file stream.
|
||||
* The synthetic `streaming-file` tab always shows it; a real file tab shows it when
|
||||
* the streamed `fileName` matches that resource (so users who stay on the open file see live text).
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
DeployApi,
|
||||
DeployChat,
|
||||
DeployMcp,
|
||||
FileWrite,
|
||||
File as FileTool,
|
||||
MoveFolder,
|
||||
MoveWorkflow,
|
||||
Read as ReadTool,
|
||||
@@ -914,9 +914,16 @@ export function useChat(
|
||||
)
|
||||
|
||||
if (existingFileMatch) {
|
||||
setActiveResourceId(matchedResourceId)
|
||||
setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file'))
|
||||
} else if (fileName || fileIdMatch || activeSubagent === 'file_write') {
|
||||
const hadStreamingResource = resourcesRef.current.some(
|
||||
(resource) => resource.id === 'streaming-file'
|
||||
)
|
||||
if (hadStreamingResource) {
|
||||
setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file'))
|
||||
setActiveResourceId(matchedResourceId)
|
||||
} else if (activeResourceIdRef.current === null) {
|
||||
setActiveResourceId(matchedResourceId)
|
||||
}
|
||||
} else if (fileName || fileIdMatch || activeSubagent === FileTool.id) {
|
||||
const hasStreamingResource = resourcesRef.current.some(
|
||||
(resource) => resource.id === 'streaming-file'
|
||||
)
|
||||
@@ -927,8 +934,6 @@ export function useChat(
|
||||
title: fileName || 'Writing file...',
|
||||
})
|
||||
setActiveResourceId('streaming-file')
|
||||
} else if (activeResourceIdRef.current !== 'streaming-file') {
|
||||
setActiveResourceId('streaming-file')
|
||||
}
|
||||
}
|
||||
const next = { fileName, fileId: matchedResourceId, content: raw }
|
||||
@@ -1294,7 +1299,7 @@ export function useChat(
|
||||
if (!isSameActiveSubagent) {
|
||||
blocks.push({ type: 'subagent', content: name })
|
||||
}
|
||||
if (name === FileWrite.id) {
|
||||
if (name === FileTool.id) {
|
||||
const emptyFile = { fileName: '', content: '' }
|
||||
streamingFileRef.current = emptyFile
|
||||
setStreamingFile(emptyFile)
|
||||
|
||||
@@ -190,7 +190,7 @@ export const SUBAGENT_LABELS: Record<string, string> = {
|
||||
run: 'Run agent',
|
||||
agent: 'Agent manager',
|
||||
job: 'Job agent',
|
||||
file_write: 'File Write',
|
||||
file: 'File',
|
||||
} as const
|
||||
|
||||
export interface ToolUIMetadata {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ import type {
|
||||
StreamingContext,
|
||||
ToolCallState,
|
||||
} from '@/lib/copilot/request/types'
|
||||
import { isSimExecuted } from '@/lib/copilot/tool-executor'
|
||||
import { isSimExecuted, getToolEntry } from '@/lib/copilot/tool-executor'
|
||||
import { isWorkflowToolName } from '@/lib/copilot/tools/workflow-tools'
|
||||
import type { ToolScope } from './types'
|
||||
import {
|
||||
@@ -191,8 +191,10 @@ async function handleCallPhase(
|
||||
if (isGoHandledInternalRead) return
|
||||
|
||||
const { clientExecutable, simExecutable, internal } = getEventUI(event)
|
||||
const catalogEntry = getToolEntry(toolName)
|
||||
const isInternal = internal || catalogEntry?.internal === true
|
||||
const staticSimExecuted = isSimExecuted(toolName)
|
||||
const willDispatch = !internal && (staticSimExecuted || simExecutable || clientExecutable)
|
||||
const willDispatch = !isInternal && (staticSimExecuted || simExecutable || clientExecutable)
|
||||
logger.info('Tool call routing decision', {
|
||||
toolCallId,
|
||||
toolName,
|
||||
@@ -203,12 +205,12 @@ async function handleCallPhase(
|
||||
clientExecutable,
|
||||
simExecutable,
|
||||
staticSimExecuted,
|
||||
internal,
|
||||
internal: isInternal,
|
||||
hasPendingPromise: context.pendingToolPromises.has(toolCallId),
|
||||
existingStatus: existing?.status,
|
||||
willDispatch,
|
||||
})
|
||||
if (internal) return
|
||||
if (isInternal) return
|
||||
if (!willDispatch) return
|
||||
|
||||
await dispatchToolExecution(
|
||||
|
||||
@@ -8,6 +8,7 @@ export {
|
||||
} from './executor'
|
||||
export { ensureHandlersRegistered } from './register-handlers'
|
||||
export {
|
||||
getToolEntry,
|
||||
isGoExecuted,
|
||||
isKnownTool,
|
||||
isSimExecuted,
|
||||
|
||||
Reference in New Issue
Block a user