diff --git a/apps/docs/content/docs/en/tools/file.mdx b/apps/docs/content/docs/en/tools/file.mdx index ddc31bd6050..2a13095afdd 100644 --- a/apps/docs/content/docs/en/tools/file.mdx +++ b/apps/docs/content/docs/en/tools/file.mdx @@ -1,6 +1,6 @@ --- title: File -description: Read and parse multiple files +description: Read and write workspace files --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -27,7 +27,7 @@ The File Parser tool is particularly useful for scenarios where your agents need ## Usage Instructions -Upload files directly or import from external URLs to get UserFile objects for use in other blocks. +Read and parse files from uploads or URLs, write new workspace files, or append content to existing files. @@ -52,4 +52,45 @@ Parse one or more uploaded files or files from URLs (text, PDF, CSV, images, etc | `files` | file[] | Parsed files as UserFile objects | | `combinedContent` | string | Combined content of all parsed files | +### `file_write` + +Create a new workspace file. If a file with the same name already exists, a numeric suffix is added (e.g., + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileName` | string | Yes | File name \(e.g., "data.csv"\). If a file with this name exists, a numeric suffix is added automatically. | +| `content` | string | Yes | The text content to write to the file. | +| `contentType` | string | No | MIME type for new files \(e.g., "text/plain"\). Auto-detected from file extension if omitted. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | File ID | +| `name` | string | File name | +| `size` | number | File size in bytes | +| `url` | string | URL to access the file | + +### `file_append` + +Append content to an existing workspace file. The file must already exist. Content is added to the end of the file. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileName` | string | Yes | Name of an existing workspace file to append to. | +| `content` | string | Yes | The text content to append to the file. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | File ID | +| `name` | string | File name | +| `size` | number | File size in bytes | +| `url` | string | URL to access the file | + diff --git a/apps/docs/content/docs/en/tools/profound.mdx b/apps/docs/content/docs/en/tools/profound.mdx index 8f2cb0e83cd..23ff1444cc5 100644 --- a/apps/docs/content/docs/en/tools/profound.mdx +++ b/apps/docs/content/docs/en/tools/profound.mdx @@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" {/* MANUAL-CONTENT-START:intro */} diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 2816d7c4ee0..789e5ef4a3e 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -2993,13 +2993,26 @@ "type": "file_v3", "slug": "file", "name": "File", - "description": "Read and parse multiple files", - "longDescription": "Upload files directly or import from external URLs to get UserFile objects for use in other blocks.", + "description": "Read and write workspace files", + "longDescription": "Read and parse files from uploads or URLs, write new workspace files, or append content to existing files.", "bgColor": "#40916C", "iconName": "DocumentIcon", "docsUrl": "https://docs.sim.ai/tools/file", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Read", + "description": "Parse one or more uploaded files or files from URLs (text, PDF, CSV, images, etc.)" + }, + { + "name": "Write", + "description": "Create a new workspace file. If a file with the same name already exists, a numeric suffix is added (e.g., " + }, + { + "name": "Append", + "description": "Append content to an existing workspace file. The file must already exist. Content is added to the end of the file." + } + ], + "operationCount": 3, "triggers": [], "triggerCount": 0, "authType": "none", @@ -8617,7 +8630,7 @@ "name": "Profound", "description": "AI visibility and analytics with Profound", "longDescription": "Track how your brand appears across AI platforms. Monitor visibility scores, sentiment, citations, bot traffic, referrals, content optimization, and prompt volumes with Profound.", - "bgColor": "#1A1A2E", + "bgColor": "#000000", "iconName": "ProfoundIcon", "docsUrl": "https://docs.sim.ai/tools/profound", "operations": [ diff --git a/apps/sim/app/api/emails/preview/route.ts b/apps/sim/app/api/emails/preview/route.ts index 0658a73db7a..6022f2ef653 100644 --- a/apps/sim/app/api/emails/preview/route.ts +++ b/apps/sim/app/api/emails/preview/route.ts @@ -100,7 +100,7 @@ const emailTemplates = { trigger: 'api', duration: '2.3s', cost: '$0.0042', - logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123', + logUrl: 'https://sim.ai/workspace/ws_123/logs?executionId=exec_abc123', }), 'workflow-notification-error': () => renderWorkflowNotificationEmail({ @@ -109,7 +109,7 @@ const emailTemplates = { trigger: 'webhook', duration: '1.1s', cost: '$0.0021', - logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123', + logUrl: 'https://sim.ai/workspace/ws_123/logs?executionId=exec_abc123', }), 'workflow-notification-alert': () => renderWorkflowNotificationEmail({ @@ -118,7 +118,7 @@ const emailTemplates = { trigger: 'schedule', duration: '45.2s', cost: '$0.0156', - logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123', + logUrl: 'https://sim.ai/workspace/ws_123/logs?executionId=exec_abc123', alertReason: '3 consecutive failures detected', }), 'workflow-notification-full': () => @@ -128,7 +128,7 @@ const emailTemplates = { trigger: 'api', duration: '12.5s', cost: '$0.0234', - logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123', + logUrl: 'https://sim.ai/workspace/ws_123/logs?executionId=exec_abc123', finalOutput: { processed: 150, skipped: 3, status: 'completed' }, rateLimits: { sync: { requestsPerMinute: 60, remaining: 45 }, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx index 4bf89d74395..f3147311489 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx @@ -7,9 +7,9 @@ import { useParams } from 'next/navigation' import { Button, Combobox } from '@/components/emcn/components' import { Progress } from '@/components/ui/progress' import { cn } from '@/lib/core/utils/cn' -import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace' import { getExtensionFromMimeType } from '@/lib/uploads/utils/file-utils' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' +import { useWorkspaceFiles } from '@/hooks/queries/workspace-files' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' @@ -150,8 +150,6 @@ export function FileUpload({ const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) const [uploadingFiles, setUploadingFiles] = useState([]) const [uploadProgress, setUploadProgress] = useState(0) - const [workspaceFiles, setWorkspaceFiles] = useState([]) - const [loadingWorkspaceFiles, setLoadingWorkspaceFiles] = useState(false) const [uploadError, setUploadError] = useState(null) const [inputValue, setInputValue] = useState('') @@ -163,25 +161,13 @@ export function FileUpload({ const params = useParams() const workspaceId = params?.workspaceId as string - const value = isPreview ? previewValue : storeValue - - const loadWorkspaceFiles = async () => { - if (!workspaceId || isPreview) return - - try { - setLoadingWorkspaceFiles(true) - const response = await fetch(`/api/workspaces/${workspaceId}/files`) - const data = await response.json() + const { + data: workspaceFiles = [], + isLoading: loadingWorkspaceFiles, + refetch: refetchWorkspaceFiles, + } = useWorkspaceFiles(isPreview ? '' : workspaceId) - if (data.success) { - setWorkspaceFiles(data.files || []) - } - } catch (error) { - logger.error('Error loading workspace files:', error) - } finally { - setLoadingWorkspaceFiles(false) - } - } + const value = isPreview ? previewValue : storeValue /** * Checks if a file's MIME type matches the accepted types @@ -226,10 +212,6 @@ export function FileUpload({ return !isAlreadySelected }) - useEffect(() => { - void loadWorkspaceFiles() - }, [workspaceId]) - /** * Opens file dialog */ @@ -394,7 +376,7 @@ export function FileUpload({ setUploadError(null) if (workspaceId) { - void loadWorkspaceFiles() + void refetchWorkspaceFiles() } if (uploadedFiles.length === 1) { @@ -726,7 +708,7 @@ export function FileUpload({ value={inputValue} onChange={handleComboboxChange} onOpenChange={(open) => { - if (open) void loadWorkspaceFiles() + if (open) void refetchWorkspaceFiles() }} placeholder={loadingWorkspaceFiles ? 'Loading files...' : '+ Add More'} disabled={disabled || loadingWorkspaceFiles} @@ -746,7 +728,7 @@ export function FileUpload({ onInputChange={handleComboboxChange} onClear={(e) => handleRemoveFile(filesArray[0], e)} onOpenChange={(open) => { - if (open) void loadWorkspaceFiles() + if (open) void refetchWorkspaceFiles() }} disabled={disabled} isLoading={loadingWorkspaceFiles} @@ -763,7 +745,7 @@ export function FileUpload({ value={inputValue} onChange={handleComboboxChange} onOpenChange={(open) => { - if (open) void loadWorkspaceFiles() + if (open) void refetchWorkspaceFiles() }} placeholder={loadingWorkspaceFiles ? 'Loading files...' : 'Select or upload file'} disabled={disabled || loadingWorkspaceFiles} diff --git a/apps/sim/background/workspace-notification-delivery.ts b/apps/sim/background/workspace-notification-delivery.ts index 41d08f80cbd..9e0d7b632a7 100644 --- a/apps/sim/background/workspace-notification-delivery.ts +++ b/apps/sim/background/workspace-notification-delivery.ts @@ -247,7 +247,7 @@ function formatCost(cost?: Record): string { } function buildLogUrl(workspaceId: string, executionId: string): string { - return `${getBaseUrl()}/workspace/${workspaceId}/logs?search=${encodeURIComponent(executionId)}` + return `${getBaseUrl()}/workspace/${workspaceId}/logs?executionId=${encodeURIComponent(executionId)}` } function formatAlertReason(alertConfig: AlertConfig): string { diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index bf377179645..4be2f20bbd6 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -318,23 +318,26 @@ export const FileV3Block: BlockConfig = { condition: { field: 'operation', value: 'file_write' }, mode: 'advanced', }, + { + id: 'appendFile', + title: 'File', + type: 'file-upload' as SubBlockType, + canonicalParamId: 'appendFileInput', + acceptedTypes: '.txt,.md,.json,.csv,.xml,.html,.htm,.yaml,.yml,.log,.rtf', + placeholder: 'Select or upload a workspace file', + mode: 'basic', + condition: { field: 'operation', value: 'file_append' }, + required: { field: 'operation', value: 'file_append' }, + }, { id: 'appendFileName', title: 'File', - type: 'dropdown' as SubBlockType, - placeholder: 'Select a workspace file...', + type: 'short-input' as SubBlockType, + canonicalParamId: 'appendFileInput', + placeholder: 'File name (e.g., notes.md)', + mode: 'advanced', condition: { field: 'operation', value: 'file_append' }, required: { field: 'operation', value: 'file_append' }, - options: [], - fetchOptions: async () => { - const { useWorkflowRegistry } = await import('@/stores/workflows/registry/store') - const workspaceId = useWorkflowRegistry.getState().hydration.workspaceId - if (!workspaceId) return [] - const response = await fetch(`/api/workspaces/${workspaceId}/files`) - const data = await response.json() - if (!data.success || !data.files) return [] - return data.files.map((f: { name: string }) => ({ label: f.name, id: f.name })) - }, }, { id: 'appendContent', @@ -362,8 +365,26 @@ export const FileV3Block: BlockConfig = { } if (operation === 'file_append') { + const appendInput = params.appendFileInput + if (!appendInput) { + throw new Error('File is required for append') + } + + let fileName: string + if (typeof appendInput === 'string') { + fileName = appendInput.trim() + } else { + const normalized = normalizeFileInput(appendInput, { single: true }) + const file = normalized as Record | null + fileName = (file?.name as string) ?? '' + } + + if (!fileName) { + throw new Error('Could not determine file name') + } + return { - fileName: params.appendFileName, + fileName, content: params.appendContent, workspaceId: params._context?.workspaceId, } @@ -408,12 +429,12 @@ export const FileV3Block: BlockConfig = { }, inputs: { operation: { type: 'string', description: 'Operation to perform (read, write, or append)' }, - fileInput: { type: 'json', description: 'File input for read (canonical param)' }, + fileInput: { type: 'json', description: 'File input for read' }, fileType: { type: 'string', description: 'File type for read' }, fileName: { type: 'string', description: 'Name for a new file (write)' }, content: { type: 'string', description: 'File content to write' }, contentType: { type: 'string', description: 'MIME content type for write' }, - appendFileName: { type: 'string', description: 'Name of existing file to append to' }, + appendFileInput: { type: 'json', description: 'File to append to' }, appendContent: { type: 'string', description: 'Content to append to file' }, }, outputs: { diff --git a/apps/sim/blocks/blocks/profound.ts b/apps/sim/blocks/blocks/profound.ts index 47bc3079440..93d8ef02e06 100644 --- a/apps/sim/blocks/blocks/profound.ts +++ b/apps/sim/blocks/blocks/profound.ts @@ -68,7 +68,7 @@ export const ProfoundBlock: BlockConfig = { category: 'tools', integrationType: IntegrationType.Analytics, tags: ['seo', 'data-analytics'], - bgColor: '#1A1A2E', + bgColor: '#000000', icon: ProfoundIcon, authMode: AuthMode.ApiKey,