diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 6a956a45e..4011e9340 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -144,7 +144,7 @@ export const blockTypeToIconMap: Record = { calendly: CalendlyIcon, circleback: CirclebackIcon, clay: ClayIcon, - confluence: ConfluenceIcon, + confluence_v2: ConfluenceIcon, cursor_v2: CursorIcon, datadog: DatadogIcon, discord: DiscordIcon, @@ -246,7 +246,7 @@ export const blockTypeToIconMap: Record = { twilio_sms: TwilioIcon, twilio_voice: TwilioIcon, typeform: TypeformIcon, - video_generator: VideoIcon, + video_generator_v2: VideoIcon, vision: EyeIcon, wealthbox: WealthboxIcon, webflow: WebflowIcon, diff --git a/apps/docs/content/docs/en/tools/confluence.mdx b/apps/docs/content/docs/en/tools/confluence.mdx index bb8453ece..9de626872 100644 --- a/apps/docs/content/docs/en/tools/confluence.mdx +++ b/apps/docs/content/docs/en/tools/confluence.mdx @@ -6,7 +6,7 @@ description: Interact with Confluence import { BlockInfoCard } from "@/components/ui/block-info-card" diff --git a/apps/docs/content/docs/en/tools/mistral_parse.mdx b/apps/docs/content/docs/en/tools/mistral_parse.mdx index ac0b2150c..c45023367 100644 --- a/apps/docs/content/docs/en/tools/mistral_parse.mdx +++ b/apps/docs/content/docs/en/tools/mistral_parse.mdx @@ -54,8 +54,37 @@ Parse PDF documents using Mistral OCR API | Parameter | Type | Description | | --------- | ---- | ----------- | -| `success` | boolean | Whether the PDF was parsed successfully | -| `content` | string | Extracted content in the requested format \(markdown, text, or JSON\) | -| `metadata` | object | Processing metadata including jobId, fileType, pageCount, and usage info | +| `pages` | array | Array of page objects from Mistral OCR | +| ↳ `index` | number | Page index \(zero-based\) | +| ↳ `markdown` | string | Extracted markdown content | +| ↳ `images` | array | Images extracted from this page with bounding boxes | +| ↳ `id` | string | Image identifier \(e.g., img-0.jpeg\) | +| ↳ `top_left_x` | number | Top-left X coordinate in pixels | +| ↳ `top_left_y` | number | Top-left Y coordinate in pixels | +| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels | +| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels | +| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) | +| ↳ `id` | string | Image identifier \(e.g., img-0.jpeg\) | +| ↳ `top_left_x` | number | Top-left X coordinate in pixels | +| ↳ `top_left_y` | number | Top-left Y coordinate in pixels | +| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels | +| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels | +| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) | +| ↳ `dimensions` | object | Page dimensions | +| ↳ `dpi` | number | Dots per inch | +| ↳ `height` | number | Page height in pixels | +| ↳ `width` | number | Page width in pixels | +| ↳ `dpi` | number | Dots per inch | +| ↳ `height` | number | Page height in pixels | +| ↳ `width` | number | Page width in pixels | +| ↳ `tables` | array | Extracted tables as HTML/markdown \(when table_format is set\). Referenced via placeholders like \[tbl-0.html\] | +| ↳ `hyperlinks` | array | Array of URL strings detected in the page \(e.g., \[ | +| ↳ `header` | string | Page header content \(when extract_header=true\) | +| ↳ `footer` | string | Page footer content \(when extract_footer=true\) | +| `model` | string | Mistral OCR model identifier \(e.g., mistral-ocr-latest\) | +| `usage_info` | object | Usage and processing statistics | +| ↳ `pages_processed` | number | Total number of pages processed | +| ↳ `doc_size_bytes` | number | Document file size in bytes | +| `document_annotation` | string | Structured annotation data as JSON string \(when applicable\) | diff --git a/apps/docs/content/docs/en/tools/video_generator.mdx b/apps/docs/content/docs/en/tools/video_generator.mdx index 7930ad7b2..437bb2dd6 100644 --- a/apps/docs/content/docs/en/tools/video_generator.mdx +++ b/apps/docs/content/docs/en/tools/video_generator.mdx @@ -6,7 +6,7 @@ description: Generate videos from text using AI import { BlockInfoCard } from "@/components/ui/block-info-card" 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 e776b3421..2ee395a17 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 @@ -85,14 +85,47 @@ export function FileUpload({ } } + /** + * Checks if a file's MIME type matches the accepted types + * Supports exact matches, wildcard patterns (e.g., 'image/*'), and '*' for all types + */ + const isFileTypeAccepted = (fileType: string, accepted: string): boolean => { + if (accepted === '*') return true + + const acceptedList = accepted.split(',').map((t) => t.trim().toLowerCase()) + const normalizedFileType = fileType.toLowerCase() + + return acceptedList.some((acceptedType) => { + if (acceptedType === normalizedFileType) return true + + if (acceptedType.endsWith('/*')) { + const typePrefix = acceptedType.slice(0, -1) // 'image/' from 'image/*' + return normalizedFileType.startsWith(typePrefix) + } + + if (acceptedType.startsWith('.')) { + const extension = acceptedType.slice(1) // 'pdf' from '.pdf' + return ( + normalizedFileType.endsWith(`/${extension}`) || + normalizedFileType.includes(`${extension}`) + ) + } + + return false + }) + } + const availableWorkspaceFiles = workspaceFiles.filter((workspaceFile) => { const existingFiles = Array.isArray(value) ? value : value ? [value] : [] - return !existingFiles.some( + + const isAlreadySelected = existingFiles.some( (existing) => existing.name === workspaceFile.name || existing.path?.includes(workspaceFile.key) || existing.key === workspaceFile.key ) + + return !isAlreadySelected }) useEffect(() => { @@ -421,23 +454,23 @@ export function FileUpload({ return (
-
+
{truncateMiddle(file.name)} ({formatFileSize(file.size)})
@@ -468,19 +501,30 @@ export function FileUpload({ const comboboxOptions = useMemo( () => [ { label: 'Upload New File', value: '__upload_new__' }, - ...availableWorkspaceFiles.map((file) => ({ - label: file.name, - value: file.id, - })), + ...availableWorkspaceFiles.map((file) => { + const isAccepted = + !acceptedTypes || acceptedTypes === '*' || isFileTypeAccepted(file.type, acceptedTypes) + return { + label: file.name, + value: file.id, + disabled: !isAccepted, + } + }), ], - [availableWorkspaceFiles] + [availableWorkspaceFiles, acceptedTypes] ) const handleComboboxChange = (value: string) => { setInputValue(value) - const isValidOption = - value === '__upload_new__' || availableWorkspaceFiles.some((file) => file.id === value) + const selectedFile = availableWorkspaceFiles.find((file) => file.id === value) + const isAcceptedType = + selectedFile && + (!acceptedTypes || + acceptedTypes === '*' || + isFileTypeAccepted(selectedFile.type, acceptedTypes)) + + const isValidOption = value === '__upload_new__' || isAcceptedType if (!isValidOption) { return diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx index d5fde3119..220be8091 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx @@ -241,11 +241,9 @@ const getOutputTypeForPath = ( const blockState = useWorkflowStore.getState().blocks[blockId] const subBlocks = mergedSubBlocksOverride ?? (blockState?.subBlocks || {}) return getBlockOutputType(block.type, outputPath, subBlocks) - } else { + } else if (blockConfig) { const operationValue = getSubBlockValue(blockId, 'operation') - if (blockConfig && operationValue) { - return getToolOutputType(blockConfig, operationValue, outputPath) - } + return getToolOutputType(blockConfig, operationValue || '', outputPath) } return 'any' } @@ -1213,9 +1211,11 @@ export const TagDropdown: React.FC = ({ } else { const operationValue = mergedSubBlocks?.operation?.value ?? getSubBlockValue(activeSourceBlockId, 'operation') - const toolOutputPaths = operationValue - ? getToolOutputPaths(blockConfig, operationValue, mergedSubBlocks) - : [] + const toolOutputPaths = getToolOutputPaths( + blockConfig, + operationValue || '', + mergedSubBlocks + ) if (toolOutputPaths.length > 0) { blockTags = toolOutputPaths.map((path) => `${normalizedBlockName}.${path}`) @@ -1545,9 +1545,11 @@ export const TagDropdown: React.FC = ({ } else { const operationValue = mergedSubBlocks?.operation?.value ?? getSubBlockValue(accessibleBlockId, 'operation') - const toolOutputPaths = operationValue - ? getToolOutputPaths(blockConfig, operationValue, mergedSubBlocks) - : [] + const toolOutputPaths = getToolOutputPaths( + blockConfig, + operationValue || '', + mergedSubBlocks + ) if (toolOutputPaths.length > 0) { blockTags = toolOutputPaths.map((path) => `${normalizedBlockName}.${path}`) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx index 306ed8e8e..e2db3de27 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx @@ -129,7 +129,6 @@ export function Editor() { blockSubBlockValues, canonicalIndex ) - // When user can edit, respect their toggle; otherwise show if values present const displayAdvancedOptions = userPermissions.canEdit ? advancedMode : advancedMode || advancedValuesPresent diff --git a/apps/sim/blocks/blocks/a2a.ts b/apps/sim/blocks/blocks/a2a.ts index 6996b685a..86c98ac9b 100644 --- a/apps/sim/blocks/blocks/a2a.ts +++ b/apps/sim/blocks/blocks/a2a.ts @@ -107,14 +107,26 @@ export const A2ABlock: BlockConfig = { condition: { field: 'operation', value: 'a2a_send_message' }, }, { - id: 'files', + id: 'fileUpload', title: 'Files', type: 'file-upload', + canonicalParamId: 'files', placeholder: 'Upload files to send', description: 'Files to include with the message (FilePart)', condition: { field: 'operation', value: 'a2a_send_message' }, + mode: 'basic', multiple: true, }, + { + id: 'fileReference', + title: 'Files', + type: 'short-input', + canonicalParamId: 'files', + placeholder: 'Reference files from previous blocks', + description: 'Files to include with the message (FilePart)', + condition: { field: 'operation', value: 'a2a_send_message' }, + mode: 'advanced', + }, { id: 'taskId', title: 'Task ID', @@ -233,6 +245,14 @@ export const A2ABlock: BlockConfig = { type: 'array', description: 'Files to include with the message', }, + fileUpload: { + type: 'array', + description: 'Uploaded files (basic mode)', + }, + fileReference: { + type: 'json', + description: 'File reference from previous blocks (advanced mode)', + }, historyLength: { type: 'number', description: 'Number of history messages to include', diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index 6823bb617..5f9436f5c 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -5,8 +5,9 @@ import type { ConfluenceResponse } from '@/tools/confluence/types' export const ConfluenceBlock: BlockConfig = { type: 'confluence', - name: 'Confluence', + name: 'Confluence (Legacy)', description: 'Interact with Confluence', + hideFromToolbar: true, authMode: AuthMode.OAuth, longDescription: 'Integrate Confluence into the workflow. Can read, create, update, delete pages, manage comments, attachments, labels, and search content.', @@ -357,3 +358,342 @@ export const ConfluenceBlock: BlockConfig = { status: { type: 'string', description: 'Space status' }, }, } + +export const ConfluenceV2Block: BlockConfig = { + ...ConfluenceBlock, + type: 'confluence_v2', + name: 'Confluence', + hideFromToolbar: false, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Read Page', id: 'read' }, + { label: 'Create Page', id: 'create' }, + { label: 'Update Page', id: 'update' }, + { label: 'Delete Page', id: 'delete' }, + { label: 'Search Content', id: 'search' }, + { label: 'Create Comment', id: 'create_comment' }, + { label: 'List Comments', id: 'list_comments' }, + { label: 'Update Comment', id: 'update_comment' }, + { label: 'Delete Comment', id: 'delete_comment' }, + { label: 'Upload Attachment', id: 'upload_attachment' }, + { label: 'List Attachments', id: 'list_attachments' }, + { label: 'Delete Attachment', id: 'delete_attachment' }, + { label: 'List Labels', id: 'list_labels' }, + { label: 'Get Space', id: 'get_space' }, + { label: 'List Spaces', id: 'list_spaces' }, + ], + value: () => 'read', + }, + { + id: 'domain', + title: 'Domain', + type: 'short-input', + placeholder: 'Enter Confluence domain (e.g., simstudio.atlassian.net)', + required: true, + }, + { + id: 'credential', + title: 'Confluence Account', + type: 'oauth-input', + serviceId: 'confluence', + requiredScopes: [ + 'read:confluence-content.all', + 'read:confluence-space.summary', + 'read:space:confluence', + 'read:space-details:confluence', + 'write:confluence-content', + 'write:confluence-space', + 'write:confluence-file', + 'read:content:confluence', + 'read:page:confluence', + 'write:page:confluence', + 'read:comment:confluence', + 'write:comment:confluence', + 'delete:comment:confluence', + 'read:attachment:confluence', + 'write:attachment:confluence', + 'delete:attachment:confluence', + 'delete:page:confluence', + 'read:label:confluence', + 'write:label:confluence', + 'search:confluence', + 'read:me', + 'offline_access', + ], + placeholder: 'Select Confluence account', + required: true, + }, + { + id: 'pageId', + title: 'Select Page', + type: 'file-selector', + canonicalParamId: 'pageId', + serviceId: 'confluence', + placeholder: 'Select Confluence page', + dependsOn: ['credential', 'domain'], + mode: 'basic', + }, + { + id: 'manualPageId', + title: 'Page ID', + type: 'short-input', + canonicalParamId: 'pageId', + placeholder: 'Enter Confluence page ID', + mode: 'advanced', + }, + { + id: 'spaceId', + title: 'Space ID', + type: 'short-input', + placeholder: 'Enter Confluence space ID', + required: true, + condition: { field: 'operation', value: ['create', 'get_space'] }, + }, + { + id: 'title', + title: 'Title', + type: 'short-input', + placeholder: 'Enter title for the page', + condition: { field: 'operation', value: ['create', 'update'] }, + }, + { + id: 'content', + title: 'Content', + type: 'long-input', + placeholder: 'Enter content for the page', + condition: { field: 'operation', value: ['create', 'update'] }, + }, + { + id: 'parentId', + title: 'Parent Page ID', + type: 'short-input', + placeholder: 'Enter parent page ID (optional)', + condition: { field: 'operation', value: 'create' }, + }, + { + id: 'query', + title: 'Search Query', + type: 'short-input', + placeholder: 'Enter search query', + required: true, + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'comment', + title: 'Comment Text', + type: 'long-input', + placeholder: 'Enter comment text', + required: true, + condition: { field: 'operation', value: ['create_comment', 'update_comment'] }, + }, + { + id: 'commentId', + title: 'Comment ID', + type: 'short-input', + placeholder: 'Enter comment ID', + required: true, + condition: { field: 'operation', value: ['update_comment', 'delete_comment'] }, + }, + { + id: 'attachmentId', + title: 'Attachment ID', + type: 'short-input', + placeholder: 'Enter attachment ID', + required: true, + condition: { field: 'operation', value: 'delete_attachment' }, + }, + { + id: 'attachmentFileUpload', + title: 'File', + type: 'file-upload', + canonicalParamId: 'attachmentFile', + placeholder: 'Select file to upload', + condition: { field: 'operation', value: 'upload_attachment' }, + mode: 'basic', + }, + { + id: 'attachmentFileReference', + title: 'File', + type: 'short-input', + canonicalParamId: 'attachmentFile', + placeholder: 'Reference file from previous blocks', + condition: { field: 'operation', value: 'upload_attachment' }, + mode: 'advanced', + }, + { + id: 'attachmentFileName', + title: 'File Name', + type: 'short-input', + placeholder: 'Optional custom file name', + condition: { field: 'operation', value: 'upload_attachment' }, + }, + { + id: 'attachmentComment', + title: 'Comment', + type: 'short-input', + placeholder: 'Optional comment for the attachment', + condition: { field: 'operation', value: 'upload_attachment' }, + }, + { + id: 'labelName', + title: 'Label Name', + type: 'short-input', + placeholder: 'Enter label name', + required: true, + condition: { field: 'operation', value: ['add_label', 'remove_label'] }, + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Enter maximum number of results (default: 25)', + condition: { + field: 'operation', + value: ['search', 'list_comments', 'list_attachments', 'list_spaces'], + }, + }, + ], + tools: { + access: [ + 'confluence_retrieve', + 'confluence_update', + 'confluence_create_page', + 'confluence_delete_page', + 'confluence_search', + 'confluence_create_comment', + 'confluence_list_comments', + 'confluence_update_comment', + 'confluence_delete_comment', + 'confluence_upload_attachment', + 'confluence_list_attachments', + 'confluence_delete_attachment', + 'confluence_list_labels', + 'confluence_get_space', + 'confluence_list_spaces', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'read': + return 'confluence_retrieve' + case 'create': + return 'confluence_create_page' + case 'update': + return 'confluence_update' + case 'delete': + return 'confluence_delete_page' + case 'search': + return 'confluence_search' + case 'create_comment': + return 'confluence_create_comment' + case 'list_comments': + return 'confluence_list_comments' + case 'update_comment': + return 'confluence_update_comment' + case 'delete_comment': + return 'confluence_delete_comment' + case 'upload_attachment': + return 'confluence_upload_attachment' + case 'list_attachments': + return 'confluence_list_attachments' + case 'delete_attachment': + return 'confluence_delete_attachment' + case 'list_labels': + return 'confluence_list_labels' + case 'get_space': + return 'confluence_get_space' + case 'list_spaces': + return 'confluence_list_spaces' + default: + return 'confluence_retrieve' + } + }, + params: (params) => { + const { + credential, + pageId, + manualPageId, + operation, + attachmentFileUpload, + attachmentFileReference, + attachmentFile, + attachmentFileName, + attachmentComment, + ...rest + } = params + + const effectivePageId = (pageId || manualPageId || '').trim() + + const requiresPageId = [ + 'read', + 'update', + 'delete', + 'create_comment', + 'list_comments', + 'list_attachments', + 'list_labels', + 'upload_attachment', + ] + + const requiresSpaceId = ['create', 'get_space'] + + if (requiresPageId.includes(operation) && !effectivePageId) { + throw new Error('Page ID is required. Please select a page or enter a page ID manually.') + } + + if (requiresSpaceId.includes(operation) && !rest.spaceId) { + throw new Error('Space ID is required for this operation.') + } + + if (operation === 'upload_attachment') { + const fileInput = attachmentFileUpload || attachmentFileReference || attachmentFile + if (!fileInput) { + throw new Error('File is required for upload attachment operation.') + } + return { + credential, + pageId: effectivePageId, + operation, + file: fileInput, + fileName: attachmentFileName, + comment: attachmentComment, + ...rest, + } + } + + return { + credential, + pageId: effectivePageId || undefined, + operation, + ...rest, + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + domain: { type: 'string', description: 'Confluence domain' }, + credential: { type: 'string', description: 'Confluence access token' }, + pageId: { type: 'string', description: 'Page identifier' }, + manualPageId: { type: 'string', description: 'Manual page identifier' }, + spaceId: { type: 'string', description: 'Space identifier' }, + title: { type: 'string', description: 'Page title' }, + content: { type: 'string', description: 'Page content' }, + parentId: { type: 'string', description: 'Parent page identifier' }, + query: { type: 'string', description: 'Search query' }, + comment: { type: 'string', description: 'Comment text' }, + commentId: { type: 'string', description: 'Comment identifier' }, + attachmentId: { type: 'string', description: 'Attachment identifier' }, + attachmentFile: { type: 'json', description: 'File to upload as attachment' }, + attachmentFileUpload: { type: 'json', description: 'Uploaded file (basic mode)' }, + attachmentFileReference: { type: 'json', description: 'File reference (advanced mode)' }, + attachmentFileName: { type: 'string', description: 'Custom file name for attachment' }, + attachmentComment: { type: 'string', description: 'Comment for the attachment' }, + labelName: { type: 'string', description: 'Label name' }, + limit: { type: 'number', description: 'Maximum number of results' }, + }, +} diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts index 2cba8700a..bf76cd8ee 100644 --- a/apps/sim/blocks/blocks/mistral_parse.ts +++ b/apps/sim/blocks/blocks/mistral_parse.ts @@ -173,9 +173,9 @@ export const MistralParseV2Block: BlockConfig = { title: 'Output Format', type: 'dropdown', options: [ - { id: 'markdown', label: 'Markdown (Formatted)' }, + { id: 'markdown', label: 'Markdown' }, { id: 'text', label: 'Plain Text' }, - { id: 'json', label: 'JSON (Raw)' }, + { id: 'json', label: 'JSON' }, ], }, { @@ -262,7 +262,9 @@ export const MistralParseV2Block: BlockConfig = { pages: { type: 'string', description: 'Page selection' }, }, outputs: { - content: { type: 'string', description: 'Extracted content' }, - metadata: { type: 'json', description: 'Processing metadata' }, + pages: { type: 'array', description: 'Array of page objects from Mistral OCR' }, + model: { type: 'string', description: 'Mistral OCR model identifier' }, + usage_info: { type: 'json', description: 'Usage statistics from the API' }, + document_annotation: { type: 'string', description: 'Structured annotation data' }, }, } diff --git a/apps/sim/blocks/blocks/video_generator.ts b/apps/sim/blocks/blocks/video_generator.ts index 86e3576c5..88672a17b 100644 --- a/apps/sim/blocks/blocks/video_generator.ts +++ b/apps/sim/blocks/blocks/video_generator.ts @@ -4,8 +4,9 @@ import type { VideoBlockResponse } from '@/tools/video/types' export const VideoGeneratorBlock: BlockConfig = { type: 'video_generator', - name: 'Video Generator', + name: 'Video Generator (Legacy)', description: 'Generate videos from text using AI', + hideFromToolbar: true, authMode: AuthMode.ApiKey, longDescription: 'Generate high-quality videos from text prompts using leading AI providers. Supports multiple models, aspect ratios, resolutions, and provider-specific features like world consistency, camera controls, and audio generation.', @@ -427,3 +428,378 @@ export const VideoGeneratorBlock: BlockConfig = { model: { type: 'string', description: 'Model used' }, }, } + +export const VideoGeneratorV2Block: BlockConfig = { + ...VideoGeneratorBlock, + type: 'video_generator_v2', + name: 'Video Generator', + hideFromToolbar: false, + subBlocks: [ + { + id: 'provider', + title: 'Provider', + type: 'dropdown', + options: [ + { label: 'Runway Gen-4', id: 'runway' }, + { label: 'Google Veo 3', id: 'veo' }, + { label: 'Luma Dream Machine', id: 'luma' }, + { label: 'MiniMax Hailuo', id: 'minimax' }, + { label: 'Fal.ai (Multi-Model)', id: 'falai' }, + ], + value: () => 'runway', + required: true, + }, + { + id: 'model', + title: 'Model', + type: 'dropdown', + condition: { field: 'provider', value: 'veo' }, + options: [ + { label: 'Veo 3', id: 'veo-3' }, + { label: 'Veo 3 Fast', id: 'veo-3-fast' }, + { label: 'Veo 3.1', id: 'veo-3.1' }, + ], + value: () => 'veo-3', + required: false, + }, + { + id: 'model', + title: 'Model', + type: 'dropdown', + condition: { field: 'provider', value: 'luma' }, + options: [{ label: 'Ray 2', id: 'ray-2' }], + value: () => 'ray-2', + required: false, + }, + { + id: 'model', + title: 'Model', + type: 'dropdown', + condition: { field: 'provider', value: 'minimax' }, + options: [{ label: 'Hailuo 2.3', id: 'hailuo-02' }], + value: () => 'hailuo-02', + required: false, + }, + { + id: 'endpoint', + title: 'Quality Endpoint', + type: 'dropdown', + condition: { field: 'provider', value: 'minimax' }, + options: [ + { label: 'Pro', id: 'pro' }, + { label: 'Standard', id: 'standard' }, + ], + value: () => 'standard', + required: false, + }, + { + id: 'model', + title: 'Model', + type: 'dropdown', + condition: { field: 'provider', value: 'falai' }, + options: [ + { label: 'Google Veo 3.1', id: 'veo-3.1' }, + { label: 'OpenAI Sora 2', id: 'sora-2' }, + { label: 'Kling 2.5 Turbo Pro', id: 'kling-2.5-turbo-pro' }, + { label: 'Kling 2.1 Pro', id: 'kling-2.1-pro' }, + { label: 'MiniMax Hailuo 2.3 Pro', id: 'minimax-hailuo-2.3-pro' }, + { label: 'MiniMax Hailuo 2.3 Standard', id: 'minimax-hailuo-2.3-standard' }, + { label: 'WAN 2.1', id: 'wan-2.1' }, + { label: 'LTXV 0.9.8', id: 'ltxv-0.9.8' }, + ], + value: () => 'veo-3.1', + required: true, + }, + { + id: 'prompt', + title: 'Prompt', + type: 'long-input', + placeholder: 'Describe the video you want to generate...', + required: true, + }, + { + id: 'duration', + title: 'Duration (seconds)', + type: 'dropdown', + condition: { field: 'provider', value: 'runway' }, + options: [ + { label: '5', id: '5' }, + { label: '10', id: '10' }, + ], + value: () => '5', + required: false, + }, + { + id: 'duration', + title: 'Duration (seconds)', + type: 'dropdown', + condition: { field: 'provider', value: 'veo' }, + options: [ + { label: '4', id: '4' }, + { label: '6', id: '6' }, + { label: '8', id: '8' }, + ], + value: () => '8', + required: false, + }, + { + id: 'duration', + title: 'Duration (seconds)', + type: 'dropdown', + condition: { field: 'provider', value: 'luma' }, + options: [ + { label: '5', id: '5' }, + { label: '9', id: '9' }, + ], + value: () => '5', + required: false, + }, + { + id: 'duration', + title: 'Duration (seconds)', + type: 'dropdown', + condition: { field: 'provider', value: 'minimax' }, + options: [ + { label: '6', id: '6' }, + { label: '10', id: '10' }, + ], + value: () => '6', + required: false, + }, + { + id: 'duration', + title: 'Duration (seconds)', + type: 'dropdown', + condition: { + field: 'model', + value: [ + 'kling-2.5-turbo-pro', + 'kling-2.1-pro', + 'minimax-hailuo-2.3-pro', + 'minimax-hailuo-2.3-standard', + ], + }, + options: [ + { label: '5', id: '5' }, + { label: '8', id: '8' }, + { label: '10', id: '10' }, + ], + value: () => '5', + required: false, + }, + { + id: 'aspectRatio', + title: 'Aspect Ratio', + type: 'dropdown', + condition: { field: 'provider', value: 'veo' }, + options: [ + { label: '16:9', id: '16:9' }, + { label: '9:16', id: '9:16' }, + ], + value: () => '16:9', + required: false, + }, + { + id: 'aspectRatio', + title: 'Aspect Ratio', + type: 'dropdown', + condition: { field: 'provider', value: 'runway' }, + options: [ + { label: '16:9', id: '16:9' }, + { label: '9:16', id: '9:16' }, + { label: '1:1', id: '1:1' }, + ], + value: () => '16:9', + required: false, + }, + { + id: 'aspectRatio', + title: 'Aspect Ratio', + type: 'dropdown', + condition: { field: 'provider', value: 'luma' }, + options: [ + { label: '16:9', id: '16:9' }, + { label: '9:16', id: '9:16' }, + { label: '1:1', id: '1:1' }, + ], + value: () => '16:9', + required: false, + }, + { + id: 'aspectRatio', + title: 'Aspect Ratio', + type: 'dropdown', + condition: { + field: 'model', + value: [ + 'kling-2.5-turbo-pro', + 'kling-2.1-pro', + 'minimax-hailuo-2.3-pro', + 'minimax-hailuo-2.3-standard', + ], + }, + options: [ + { label: '16:9', id: '16:9' }, + { label: '9:16', id: '9:16' }, + ], + value: () => '16:9', + required: false, + }, + { + id: 'resolution', + title: 'Resolution', + type: 'dropdown', + condition: { field: 'provider', value: 'veo' }, + options: [ + { label: '720p', id: '720p' }, + { label: '1080p', id: '1080p' }, + ], + value: () => '1080p', + required: false, + }, + { + id: 'resolution', + title: 'Resolution', + type: 'dropdown', + condition: { field: 'provider', value: 'luma' }, + options: [ + { label: '540p', id: '540p' }, + { label: '720p', id: '720p' }, + { label: '1080p', id: '1080p' }, + ], + value: () => '1080p', + required: false, + }, + { + id: 'visualReferenceUpload', + title: 'Reference Image', + type: 'file-upload', + canonicalParamId: 'visualReference', + condition: { field: 'provider', value: 'runway' }, + placeholder: 'Upload reference image', + mode: 'basic', + multiple: false, + required: true, + acceptedTypes: '.jpg,.jpeg,.png,.webp', + }, + { + id: 'visualReferenceInput', + title: 'Reference Image', + type: 'short-input', + canonicalParamId: 'visualReference', + condition: { field: 'provider', value: 'runway' }, + placeholder: 'Reference image from previous blocks', + mode: 'advanced', + }, + { + id: 'cameraControl', + title: 'Camera Controls', + type: 'long-input', + condition: { field: 'provider', value: 'luma' }, + placeholder: 'JSON: [{ "key": "pan_right" }, { "key": "zoom_in" }]', + required: false, + }, + { + id: 'promptOptimizer', + title: 'Prompt Optimizer', + type: 'switch', + condition: { field: 'provider', value: 'minimax' }, + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your provider API key', + password: true, + required: true, + }, + ], + tools: { + access: ['video_runway', 'video_veo', 'video_luma', 'video_minimax', 'video_falai'], + config: { + tool: (params) => { + switch (params.provider) { + case 'runway': + return 'video_runway' + case 'veo': + return 'video_veo' + case 'luma': + return 'video_luma' + case 'minimax': + return 'video_minimax' + case 'falai': + return 'video_falai' + default: + return 'video_runway' + } + }, + params: (params) => { + const visualRef = + params.visualReferenceUpload || params.visualReferenceInput || params.visualReference + return { + provider: params.provider, + apiKey: params.apiKey, + model: params.model, + endpoint: params.endpoint, + prompt: params.prompt, + duration: params.duration ? Number(params.duration) : undefined, + aspectRatio: params.aspectRatio, + resolution: params.resolution, + visualReference: visualRef, + consistencyMode: params.consistencyMode, + stylePreset: params.stylePreset, + promptOptimizer: params.promptOptimizer, + cameraControl: params.cameraControl + ? typeof params.cameraControl === 'string' + ? JSON.parse(params.cameraControl) + : params.cameraControl + : undefined, + } + }, + }, + }, + inputs: { + provider: { + type: 'string', + description: 'Video generation provider (runway, veo, luma, minimax)', + }, + apiKey: { type: 'string', description: 'Provider API key' }, + model: { + type: 'string', + description: 'Provider-specific model', + }, + endpoint: { + type: 'string', + description: 'Quality endpoint for MiniMax (pro, standard)', + }, + prompt: { type: 'string', description: 'Text prompt for video generation' }, + duration: { type: 'number', description: 'Video duration in seconds' }, + aspectRatio: { + type: 'string', + description: 'Aspect ratio (16:9, 9:16, 1:1) - not available for MiniMax', + }, + resolution: { + type: 'string', + description: 'Video resolution - not available for MiniMax (fixed per endpoint)', + }, + visualReference: { type: 'json', description: 'Reference image for Runway (UserFile)' }, + visualReferenceUpload: { type: 'json', description: 'Uploaded reference image (basic mode)' }, + visualReferenceInput: { + type: 'json', + description: 'Reference image from previous blocks (advanced mode)', + }, + consistencyMode: { + type: 'string', + description: 'Consistency mode for Runway (character, object, style, location)', + }, + stylePreset: { type: 'string', description: 'Style preset for Runway' }, + promptOptimizer: { + type: 'boolean', + description: 'Enable prompt optimization for MiniMax (default: true)', + }, + cameraControl: { + type: 'json', + description: 'Camera controls for Luma (pan, zoom, tilt, truck, tracking)', + }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 80efc620e..e28ca604e 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -14,7 +14,7 @@ import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger' import { CirclebackBlock } from '@/blocks/blocks/circleback' import { ClayBlock } from '@/blocks/blocks/clay' import { ConditionBlock } from '@/blocks/blocks/condition' -import { ConfluenceBlock } from '@/blocks/blocks/confluence' +import { ConfluenceBlock, ConfluenceV2Block } from '@/blocks/blocks/confluence' import { CursorBlock, CursorV2Block } from '@/blocks/blocks/cursor' import { DatadogBlock } from '@/blocks/blocks/datadog' import { DiscordBlock } from '@/blocks/blocks/discord' @@ -133,7 +133,7 @@ import { TwilioSMSBlock } from '@/blocks/blocks/twilio' import { TwilioVoiceBlock } from '@/blocks/blocks/twilio_voice' import { TypeformBlock } from '@/blocks/blocks/typeform' import { VariablesBlock } from '@/blocks/blocks/variables' -import { VideoGeneratorBlock } from '@/blocks/blocks/video_generator' +import { VideoGeneratorBlock, VideoGeneratorV2Block } from '@/blocks/blocks/video_generator' import { VisionBlock } from '@/blocks/blocks/vision' import { WaitBlock } from '@/blocks/blocks/wait' import { WealthboxBlock } from '@/blocks/blocks/wealthbox' @@ -170,6 +170,7 @@ export const registry: Record = { clay: ClayBlock, condition: ConditionBlock, confluence: ConfluenceBlock, + confluence_v2: ConfluenceV2Block, cursor: CursorBlock, cursor_v2: CursorV2Block, datadog: DatadogBlock, @@ -300,6 +301,7 @@ export const registry: Record = { typeform: TypeformBlock, variables: VariablesBlock, video_generator: VideoGeneratorBlock, + video_generator_v2: VideoGeneratorV2Block, vision: VisionBlock, wait: WaitBlock, wealthbox: WealthboxBlock, diff --git a/apps/sim/tools/mistral/parser.ts b/apps/sim/tools/mistral/parser.ts index a47f22ba4..ee348dd70 100644 --- a/apps/sim/tools/mistral/parser.ts +++ b/apps/sim/tools/mistral/parser.ts @@ -1,6 +1,10 @@ import { createLogger } from '@sim/logger' import { getBaseUrl } from '@/lib/core/utils/urls' -import type { MistralParserInput, MistralParserOutput } from '@/tools/mistral/types' +import type { + MistralParserInput, + MistralParserOutput, + MistralParserV2Output, +} from '@/tools/mistral/types' import type { ToolConfig } from '@/tools/types' const logger = createLogger('MistralParserTool') @@ -416,7 +420,7 @@ export const mistralParserTool: ToolConfig = { +export const mistralParserV2Tool: ToolConfig = { id: 'mistral_parser_v2', name: 'Mistral PDF Parser', description: 'Parse PDF documents using Mistral OCR API', @@ -424,17 +428,129 @@ export const mistralParserV2Tool: ToolConfig { + let ocrResult + try { + ocrResult = await response.json() + } catch (jsonError) { + throw new Error( + `Failed to parse Mistral OCR response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}` + ) + } + + if (!ocrResult || typeof ocrResult !== 'object') { + throw new Error('Invalid response format from Mistral OCR API') + } + + // Extract the actual Mistral data (may be nested in output from our API route) + const mistralData = + ocrResult.output && typeof ocrResult.output === 'object' && !ocrResult.pages + ? ocrResult.output + : ocrResult + + // Return raw Mistral API structure - no transformation + return { + success: true, + output: { + pages: mistralData.pages ?? [], + model: mistralData.model ?? 'mistral-ocr-latest', + usage_info: mistralData.usage_info ?? { pages_processed: 0, doc_size_bytes: null }, + document_annotation: mistralData.document_annotation ?? null, + }, + } + }, outputs: { - success: { type: 'boolean', description: 'Whether the PDF was parsed successfully' }, - content: { - type: 'string', - description: 'Extracted content in the requested format (markdown, text, or JSON)', + pages: { + type: 'array', + description: 'Array of page objects from Mistral OCR', + items: { + type: 'object', + properties: { + index: { type: 'number', description: 'Page index (zero-based)' }, + markdown: { type: 'string', description: 'Extracted markdown content' }, + images: { + type: 'array', + description: 'Images extracted from this page with bounding boxes', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Image identifier (e.g., img-0.jpeg)' }, + top_left_x: { type: 'number', description: 'Top-left X coordinate in pixels' }, + top_left_y: { type: 'number', description: 'Top-left Y coordinate in pixels' }, + bottom_right_x: { + type: 'number', + description: 'Bottom-right X coordinate in pixels', + }, + bottom_right_y: { + type: 'number', + description: 'Bottom-right Y coordinate in pixels', + }, + image_base64: { + type: 'string', + description: 'Base64-encoded image data (when include_image_base64=true)', + optional: true, + }, + }, + }, + }, + dimensions: { + type: 'object', + description: 'Page dimensions', + properties: { + dpi: { type: 'number', description: 'Dots per inch' }, + height: { type: 'number', description: 'Page height in pixels' }, + width: { type: 'number', description: 'Page width in pixels' }, + }, + }, + tables: { + type: 'array', + description: + 'Extracted tables as HTML/markdown (when table_format is set). Referenced via placeholders like [tbl-0.html]', + }, + hyperlinks: { + type: 'array', + description: + 'Array of URL strings detected in the page (e.g., ["https://...", "mailto:..."])', + items: { + type: 'string', + description: 'URL or mailto link', + }, + }, + header: { + type: 'string', + description: 'Page header content (when extract_header=true)', + optional: true, + }, + footer: { + type: 'string', + description: 'Page footer content (when extract_footer=true)', + optional: true, + }, + }, + }, }, - metadata: { + model: { + type: 'string', + description: 'Mistral OCR model identifier (e.g., mistral-ocr-latest)', + }, + usage_info: { type: 'object', - description: 'Processing metadata including jobId, fileType, pageCount, and usage info', + description: 'Usage and processing statistics', + properties: { + pages_processed: { type: 'number', description: 'Total number of pages processed' }, + doc_size_bytes: { + type: 'number', + description: 'Document file size in bytes', + optional: true, + }, + }, + }, + document_annotation: { + type: 'string', + description: 'Structured annotation data as JSON string (when applicable)', + optional: true, }, }, } diff --git a/apps/sim/tools/mistral/types.ts b/apps/sim/tools/mistral/types.ts index 8fe0f20bf..f8f9e6207 100644 --- a/apps/sim/tools/mistral/types.ts +++ b/apps/sim/tools/mistral/types.ts @@ -96,3 +96,81 @@ export interface MistralParserOutput extends ToolResponse { /** The output data containing content and metadata */ output: MistralParserOutputData } + +/** + * Image bounding box and data from Mistral OCR API + */ +export interface MistralOcrImage { + /** Image identifier */ + id: string + /** Top-left X coordinate */ + top_left_x: number + /** Top-left Y coordinate */ + top_left_y: number + /** Bottom-right X coordinate */ + bottom_right_x: number + /** Bottom-right Y coordinate */ + bottom_right_y: number + /** Base64-encoded image data (if includeImageBase64 was true) */ + image_base64?: string +} + +/** + * Page dimensions from Mistral OCR API + */ +export interface MistralOcrDimensions { + /** DPI of the page */ + dpi: number + /** Page height in pixels */ + height: number + /** Page width in pixels */ + width: number +} + +/** + * Page data from Mistral OCR API + */ +export interface MistralOcrPage { + /** Page index (zero-based) */ + index: number + /** Markdown content extracted from this page */ + markdown: string + /** Images extracted from this page */ + images: MistralOcrImage[] + /** Page dimensions */ + dimensions: MistralOcrDimensions + /** Tables extracted from this page */ + tables: unknown[] + /** Hyperlinks found on this page */ + hyperlinks: unknown[] + /** Header content if detected */ + header: string | null + /** Footer content if detected */ + footer: string | null +} + +/** + * Raw usage info from Mistral OCR API + */ +export interface MistralOcrUsageInfoRaw { + /** Number of pages processed */ + pages_processed: number + /** Document size in bytes */ + doc_size_bytes: number | null +} + +/** + * V2 Output - Returns raw Mistral API response structure + */ +export interface MistralParserV2Output extends ToolResponse { + output: { + /** Array of page objects with full OCR data */ + pages: MistralOcrPage[] + /** Model used for OCR processing */ + model: string + /** Usage statistics from the API */ + usage_info: MistralOcrUsageInfoRaw + /** Structured annotation data as JSON string (when applicable) */ + document_annotation: string | null + } +}