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,