mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(open-resource): open resource tool to open existing files (#3670)
* fix(open-resource): open resource tool to open existing files * fix loading state * address comment * remove title
This commit is contained in:
+2
-2
@@ -344,10 +344,10 @@ interface EmbeddedFileProps {
|
||||
}
|
||||
|
||||
function EmbeddedFile({ workspaceId, fileId, previewMode }: EmbeddedFileProps) {
|
||||
const { data: files = [], isLoading } = useWorkspaceFiles(workspaceId)
|
||||
const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId)
|
||||
const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId])
|
||||
|
||||
if (isLoading) return LOADING_SKELETON
|
||||
if (isLoading || (isFetching && !file)) return LOADING_SKELETON
|
||||
|
||||
if (!file) {
|
||||
return (
|
||||
|
||||
@@ -68,6 +68,8 @@ import type {
|
||||
ListWorkspaceMcpServersParams,
|
||||
MoveFolderParams,
|
||||
MoveWorkflowParams,
|
||||
OpenResourceParams,
|
||||
OpenResourceType,
|
||||
RenameFolderParams,
|
||||
RenameWorkflowParams,
|
||||
RunBlockParams,
|
||||
@@ -77,6 +79,7 @@ import type {
|
||||
SetGlobalWorkflowVariablesParams,
|
||||
UpdateWorkflowParams,
|
||||
UpdateWorkspaceMcpServerParams,
|
||||
ValidOpenResourceParams,
|
||||
} from './param-types'
|
||||
import { PLATFORM_ACTIONS_CONTENT } from './platform-actions'
|
||||
import { executeVfsGlob, executeVfsGrep, executeVfsList, executeVfsRead } from './vfs-tools'
|
||||
@@ -105,6 +108,36 @@ import {
|
||||
} from './workflow-tools'
|
||||
|
||||
const logger = createLogger('CopilotToolExecutor')
|
||||
const VALID_OPEN_RESOURCE_TYPES = new Set<OpenResourceType>([
|
||||
'workflow',
|
||||
'table',
|
||||
'knowledgebase',
|
||||
'file',
|
||||
])
|
||||
|
||||
function validateOpenResourceParams(
|
||||
params: OpenResourceParams
|
||||
): { success: true; params: ValidOpenResourceParams } | { success: false; error: string } {
|
||||
if (!params.type) {
|
||||
return { success: false, error: 'type is required' }
|
||||
}
|
||||
|
||||
if (!VALID_OPEN_RESOURCE_TYPES.has(params.type)) {
|
||||
return { success: false, error: `Invalid resource type: ${params.type}` }
|
||||
}
|
||||
|
||||
if (!params.id) {
|
||||
return { success: false, error: `${params.type} resources require \`id\`` }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
params: {
|
||||
type: params.type,
|
||||
id: params.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ManageCustomToolOperation = 'add' | 'edit' | 'delete' | 'list'
|
||||
|
||||
@@ -996,16 +1029,16 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
|
||||
list: (p, c) => executeVfsList(p, c),
|
||||
|
||||
// Resource visibility
|
||||
open_resource: async (p) => {
|
||||
const resourceType = p.type as string | undefined
|
||||
const resourceId = p.id as string | undefined
|
||||
if (!resourceType || !resourceId) {
|
||||
return { success: false, error: 'type and id are required' }
|
||||
}
|
||||
const validTypes = new Set(['workflow', 'table', 'knowledgebase', 'file'])
|
||||
if (!validTypes.has(resourceType)) {
|
||||
return { success: false, error: `Invalid resource type: ${resourceType}` }
|
||||
open_resource: async (p: OpenResourceParams) => {
|
||||
const validated = validateOpenResourceParams(p)
|
||||
if (!validated.success) {
|
||||
return { success: false, error: validated.error }
|
||||
}
|
||||
|
||||
const params = validated.params
|
||||
const resourceType = params.type
|
||||
const resourceId = params.id
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { message: `Opened ${resourceType} ${resourceId} for the user` },
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
|
||||
import { getTableById, queryRows } from '@/lib/table/service'
|
||||
import {
|
||||
downloadWorkspaceFile,
|
||||
findWorkspaceFileRecord,
|
||||
listWorkspaceFiles,
|
||||
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { getWorkflowById } from '@/lib/workflows/utils'
|
||||
@@ -178,9 +179,7 @@ export async function executeIntegrationToolDirect(
|
||||
logger.warn('Skipping non-text sandbox input file', { fileName, ext })
|
||||
continue
|
||||
}
|
||||
const record = allFiles.find(
|
||||
(f) => f.name === fileName || f.name.normalize('NFC') === fileName.normalize('NFC')
|
||||
)
|
||||
const record = findWorkspaceFileRecord(allFiles, filePath)
|
||||
if (!record) {
|
||||
logger.warn('Sandbox input file not found', { fileName })
|
||||
continue
|
||||
|
||||
@@ -202,3 +202,15 @@ export interface UpdateWorkspaceMcpServerParams {
|
||||
export interface DeleteWorkspaceMcpServerParams {
|
||||
serverId: string
|
||||
}
|
||||
|
||||
export type OpenResourceType = 'workflow' | 'table' | 'knowledgebase' | 'file'
|
||||
|
||||
export interface OpenResourceParams {
|
||||
type?: OpenResourceType
|
||||
id?: string
|
||||
}
|
||||
|
||||
export interface ValidOpenResourceParams {
|
||||
type: OpenResourceType
|
||||
id: string
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
updateTagDefinition,
|
||||
} from '@/lib/knowledge/tags/service'
|
||||
import { StorageService } from '@/lib/uploads'
|
||||
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { resolveWorkspaceFileReference } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { getQueryStrategy, handleVectorOnlySearch } from '@/app/api/knowledge/search/utils'
|
||||
|
||||
const logger = createLogger('KnowledgeBaseServerTool')
|
||||
@@ -235,13 +235,8 @@ export const knowledgeBaseServerTool: BaseServerTool<KnowledgeBaseArgs, Knowledg
|
||||
}
|
||||
}
|
||||
|
||||
const match = args.filePath.match(/^files\/(.+)$/)
|
||||
const fileName = match ? match[1] : args.filePath
|
||||
const kbWorkspaceId: string = targetKb.workspaceId
|
||||
const files = await listWorkspaceFiles(kbWorkspaceId)
|
||||
const fileRecord = files.find(
|
||||
(f) => f.name === fileName || f.name.normalize('NFC') === fileName.normalize('NFC')
|
||||
)
|
||||
const fileRecord = await resolveWorkspaceFileReference(kbWorkspaceId, args.filePath)
|
||||
|
||||
if (!fileRecord) {
|
||||
return {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import type { ColumnDefinition, RowData, TableDefinition } from '@/lib/table/types'
|
||||
import {
|
||||
downloadWorkspaceFile,
|
||||
listWorkspaceFiles,
|
||||
resolveWorkspaceFileReference,
|
||||
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
|
||||
const logger = createLogger('UserTableServerTool')
|
||||
@@ -40,15 +40,10 @@ async function resolveWorkspaceFile(
|
||||
filePath: string,
|
||||
workspaceId: string
|
||||
): Promise<{ buffer: Buffer; name: string; type: string }> {
|
||||
const match = filePath.match(/^files\/(.+)$/)
|
||||
const fileName = match ? match[1] : filePath
|
||||
const files = await listWorkspaceFiles(workspaceId)
|
||||
const record = files.find(
|
||||
(f) => f.name === fileName || f.name.normalize('NFC') === fileName.normalize('NFC')
|
||||
)
|
||||
const record = await resolveWorkspaceFileReference(workspaceId, filePath)
|
||||
if (!record) {
|
||||
throw new Error(
|
||||
`File not found: "${fileName}". Use glob("files/*/meta.json") to list available files.`
|
||||
`File not found: "${filePath}". Use glob("files/*/meta.json") to list available files.`
|
||||
)
|
||||
}
|
||||
const buffer = await downloadWorkspaceFile(record)
|
||||
|
||||
@@ -56,7 +56,10 @@ import {
|
||||
import { getPersonalAndWorkspaceEnv } from '@/lib/environment/utils'
|
||||
import { getKnowledgeBases } from '@/lib/knowledge/service'
|
||||
import { listTables } from '@/lib/table/service'
|
||||
import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import {
|
||||
findWorkspaceFileRecord,
|
||||
listWorkspaceFiles,
|
||||
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { hasWorkflowChanged } from '@/lib/workflows/comparison'
|
||||
import { listCustomTools } from '@/lib/workflows/custom-tools/operations'
|
||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
||||
@@ -397,9 +400,7 @@ export class WorkspaceVFS {
|
||||
|
||||
try {
|
||||
const files = await listWorkspaceFiles(this._workspaceId)
|
||||
const record = files.find(
|
||||
(f) => f.name === fileName || f.name.normalize('NFC') === fileName.normalize('NFC')
|
||||
)
|
||||
const record = findWorkspaceFileRecord(files, fileName)
|
||||
if (!record) return null
|
||||
return readFileRecord(record)
|
||||
} catch (err) {
|
||||
|
||||
@@ -328,6 +328,61 @@ export async function listWorkspaceFiles(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a workspace file reference to its display name.
|
||||
* Supports raw names and VFS-style paths like `files/name`, `files/name/content`,
|
||||
* and `files/name/meta.json`.
|
||||
*/
|
||||
export function normalizeWorkspaceFileReference(fileReference: string): string {
|
||||
const trimmed = fileReference.trim().replace(/^\/+/, '')
|
||||
|
||||
if (trimmed.startsWith('files/')) {
|
||||
const withoutPrefix = trimmed.slice('files/'.length)
|
||||
if (withoutPrefix.endsWith('/meta.json')) {
|
||||
return withoutPrefix.slice(0, -'/meta.json'.length)
|
||||
}
|
||||
if (withoutPrefix.endsWith('/content')) {
|
||||
return withoutPrefix.slice(0, -'/content'.length)
|
||||
}
|
||||
return withoutPrefix
|
||||
}
|
||||
|
||||
return trimmed
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a workspace file record in an existing list from either its id or a VFS/name reference.
|
||||
*/
|
||||
export function findWorkspaceFileRecord(
|
||||
files: WorkspaceFileRecord[],
|
||||
fileReference: string
|
||||
): WorkspaceFileRecord | null {
|
||||
const exactIdMatch = files.find((file) => file.id === fileReference)
|
||||
if (exactIdMatch) {
|
||||
return exactIdMatch
|
||||
}
|
||||
|
||||
const normalizedReference = normalizeWorkspaceFileReference(fileReference)
|
||||
return (
|
||||
files.find(
|
||||
(file) =>
|
||||
file.name === normalizedReference ||
|
||||
file.name.normalize('NFC') === normalizedReference.normalize('NFC')
|
||||
) ?? null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a workspace file record from either its id or a VFS/name reference.
|
||||
*/
|
||||
export async function resolveWorkspaceFileReference(
|
||||
workspaceId: string,
|
||||
fileReference: string
|
||||
): Promise<WorkspaceFileRecord | null> {
|
||||
const files = await listWorkspaceFiles(workspaceId)
|
||||
return findWorkspaceFileRecord(files, fileReference)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific workspace file
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user