From 263b3f63892ea8ccbee289ff48bff0e66fd723b3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 2 Apr 2025 09:57:30 -0700 Subject: [PATCH 1/2] added file selector for mistral OCR tool --- .../sub-block/components/file-upload.tsx | 22 +- sim/blocks/blocks/mistral-parse.ts | 206 ++++--- sim/tools/mistral/parser.ts | 510 +++++++++--------- sim/tools/mistral/types.ts | 95 ++++ 4 files changed, 456 insertions(+), 377 deletions(-) create mode 100644 sim/tools/mistral/types.ts diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx index fcb181aa3fe..dc797ca5440 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx @@ -542,17 +542,19 @@ export function FileUpload({ {/* Show upload button if no files and not uploading */} {!hasFiles && !isUploading && ( - - + @@ -560,7 +562,7 @@ export function FileUpload({ {multiple &&

You can select multiple files at once

}
- + )} ) diff --git a/sim/blocks/blocks/mistral-parse.ts b/sim/blocks/blocks/mistral-parse.ts index 996a37c709a..d6ca2fb373e 100644 --- a/sim/blocks/blocks/mistral-parse.ts +++ b/sim/blocks/blocks/mistral-parse.ts @@ -1,24 +1,73 @@ -import { MistralParserOutput } from '@/tools/mistral/parser' -import { BlockConfig } from '../types' import { MistralIcon } from '@/components/icons' +import { MistralParserOutput } from '@/tools/mistral/types' +import { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types' + +const isProduction = process.env.NODE_ENV === 'production' +const isS3Enabled = process.env.USE_S3 === 'true' +const shouldEnableFileUpload = isProduction || isS3Enabled + +// Define the input method selector block when needed +const inputMethodBlock: SubBlockConfig = { + id: 'inputMethod', + title: 'Select Input Method', + type: 'dropdown' as SubBlockType, + layout: 'full' as SubBlockLayout, + options: [ + { id: 'url', label: 'PDF Document URL' }, + { id: 'upload', label: 'Upload PDF Document' }, + ], +} + +// Define the file upload block when needed +const fileUploadBlock: SubBlockConfig = { + id: 'fileUpload', + title: 'Upload PDF', + type: 'file-upload' as SubBlockType, + layout: 'full' as SubBlockLayout, + acceptedTypes: 'application/pdf', + condition: { + field: 'inputMethod', + value: 'upload', + }, +} export const MistralParseBlock: BlockConfig = { type: 'mistral_parse', name: 'Mistral Parser', description: 'Extract text from PDF documents', longDescription: - 'Extract text and structure from PDF documents using Mistral\'s OCR API. Enter a URL to a PDF document (.pdf extension required), configure processing options, and get the content in your preferred format. The URL must be publicly accessible and point to a valid PDF file. Note: Google Drive, Dropbox, and other cloud storage links are not supported; use a direct download URL from a web server instead.', + "Extract text and structure from PDF documents using Mistral's OCR API." + + (shouldEnableFileUpload + ? ' Either enter a URL to a PDF document or upload a PDF file directly.' + : ' Enter a URL to a PDF document (.pdf extension required).') + + ' Configure processing options and get the content in your preferred format. For URLs, they must be publicly accessible and point to a valid PDF file. Note: Google Drive, Dropbox, and other cloud storage links are not supported; use a direct download URL from a web server instead.', category: 'tools', bgColor: '#000000', icon: MistralIcon, subBlocks: [ + // Show input method selection only if file upload is available + ...(shouldEnableFileUpload ? [inputMethodBlock] : []), + + // URL input - always shown, but conditional on inputMethod in production { id: 'filePath', title: 'PDF Document URL', - type: 'short-input', - layout: 'full', + type: 'short-input' as SubBlockType, + layout: 'full' as SubBlockLayout, placeholder: 'Enter full URL to a PDF document (https://example.com/document.pdf)', + ...(shouldEnableFileUpload + ? { + condition: { + field: 'inputMethod', + value: 'url', + }, + } + : {}), }, + + // File upload option - only shown in production environments + ...(shouldEnableFileUpload ? [fileUploadBlock] : []), + { id: 'resultType', title: 'Output Format', @@ -27,7 +76,7 @@ export const MistralParseBlock: BlockConfig = { options: [ { id: 'markdown', label: 'Markdown (Formatted)' }, { id: 'text', label: 'Plain Text' }, - { id: 'json', label: 'JSON (Raw)' } + { id: 'json', label: 'JSON (Raw)' }, ], }, { @@ -65,8 +114,8 @@ export const MistralParseBlock: BlockConfig = { { id: 'apiKey', title: 'API Key', - type: 'short-input', - layout: 'full', + type: 'short-input' as SubBlockType, + layout: 'full' as SubBlockLayout, placeholder: 'Enter your Mistral API key', password: true, }, @@ -78,48 +127,40 @@ export const MistralParseBlock: BlockConfig = { params: (params) => { // Basic validation if (!params || !params.apiKey || params.apiKey.trim() === '') { - throw new Error('Mistral API key is required'); + throw new Error('Mistral API key is required') } - - if (!params || !params.filePath || params.filePath.trim() === '') { - throw new Error('PDF Document URL is required'); + + // Build parameters object - file processing is now handled at the tool level + const parameters: any = { + apiKey: params.apiKey.trim(), + resultType: params.resultType || 'markdown', } - - // Validate URL format - let validatedUrl; - try { - // Try to create a URL object to validate format - validatedUrl = new URL(params.filePath.trim()); - - // Ensure URL is using HTTP or HTTPS protocol - if (!['http:', 'https:'].includes(validatedUrl.protocol)) { - throw new Error(`URL must use HTTP or HTTPS protocol. Found: ${validatedUrl.protocol}`); - } - - // Check for PDF extension and provide specific guidance - const pathname = validatedUrl.pathname.toLowerCase(); - if (!pathname.endsWith('.pdf')) { - if (!pathname.includes('pdf')) { - throw new Error( - 'The URL does not appear to point to a PDF document. ' + - 'Please provide a URL that ends with .pdf extension. ' + - 'If your document is not a PDF, please convert it to PDF format first.' - ); - } else { - // PDF is in the name but not at the end, so give a warning but proceed - console.warn( - 'Warning: URL contains "pdf" but does not end with .pdf extension. ' + - 'This might still work if the server returns a valid PDF document.' - ); + + // Set filePath or fileUpload based on input method (or directly use filePath if no method selector) + if (shouldEnableFileUpload) { + const inputMethod = params.inputMethod || 'url' + if (inputMethod === 'url') { + if (!params.filePath || params.filePath.trim() === '') { + throw new Error('PDF Document URL is required') + } + parameters.filePath = params.filePath.trim() + } else if (inputMethod === 'upload') { + if (!params.fileUpload) { + throw new Error('Please upload a PDF document') } + // Pass the entire fileUpload object to the tool + parameters.fileUpload = params.fileUpload + } + } else { + // In local development, only URL input is available + if (!params.filePath || params.filePath.trim() === '') { + throw new Error('PDF Document URL is required') } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - throw new Error(`Invalid URL format: ${errorMessage}`); + parameters.filePath = params.filePath.trim() } - - // Process pages input (convert from comma-separated string to array of numbers) - let pagesArray: number[] | undefined = undefined; + + // Convert pages input from string to array of numbers if provided + let pagesArray: number[] | undefined = undefined if (params.pages && params.pages.trim() !== '') { try { pagesArray = params.pages @@ -127,77 +168,34 @@ export const MistralParseBlock: BlockConfig = { .map((p: string) => p.trim()) .filter((p: string) => p.length > 0) .map((p: string) => { - const num = parseInt(p, 10); + const num = parseInt(p, 10) if (isNaN(num) || num < 0) { - throw new Error(`Invalid page number: ${p}`); + throw new Error(`Invalid page number: ${p}`) } - return num; - }); - + return num + }) + if (pagesArray && pagesArray.length === 0) { - pagesArray = undefined; + pagesArray = undefined } } catch (error: any) { - throw new Error(`Page number format error: ${error.message}`); - } - } - - // Process numeric inputs - let imageLimit: number | undefined = undefined; - if (params.imageLimit && params.imageLimit.trim() !== '') { - const limit = parseInt(params.imageLimit, 10); - if (!isNaN(limit) && limit > 0) { - imageLimit = limit; - } else { - throw new Error('Image limit must be a positive number'); - } - } - - let imageMinSize: number | undefined = undefined; - if (params.imageMinSize && params.imageMinSize.trim() !== '') { - const size = parseInt(params.imageMinSize, 10); - if (!isNaN(size) && size > 0) { - imageMinSize = size; - } else { - throw new Error('Minimum image size must be a positive number'); + throw new Error(`Page number format error: ${error.message}`) } } - - // Return structured parameters for the tool - const parameters: any = { - filePath: validatedUrl.toString(), - apiKey: params.apiKey.trim(), - resultType: params.resultType || 'markdown', - }; - - // Add optional parameters if they're defined + + // Add optional parameters if (pagesArray && pagesArray.length > 0) { - parameters.pages = pagesArray; + parameters.pages = pagesArray } - - /* - * Image-related parameters - temporarily disabled - * Uncomment if PDF image extraction is needed - * - if (typeof params.includeImageBase64 === 'boolean') { - parameters.includeImageBase64 = params.includeImageBase64; - } - - if (imageLimit !== undefined) { - parameters.imageLimit = imageLimit; - } - - if (imageMinSize !== undefined) { - parameters.imageMinSize = imageMinSize; - } - */ - - return parameters; + + return parameters }, }, }, inputs: { - filePath: { type: 'string', required: true }, + inputMethod: { type: 'string', required: false }, + filePath: { type: 'string', required: !shouldEnableFileUpload }, + fileUpload: { type: 'json', required: false }, apiKey: { type: 'string', required: true }, resultType: { type: 'string', required: false }, pages: { type: 'string', required: false }, @@ -214,4 +212,4 @@ export const MistralParseBlock: BlockConfig = { }, }, }, -} \ No newline at end of file +} diff --git a/sim/tools/mistral/parser.ts b/sim/tools/mistral/parser.ts index a526a3d581a..35d0d2eb21d 100644 --- a/sim/tools/mistral/parser.ts +++ b/sim/tools/mistral/parser.ts @@ -1,95 +1,5 @@ -import { ToolConfig, ToolResponse } from '../types' - -/** - * Input parameters for the Mistral OCR parser tool - */ -export interface MistralParserInput { - /** URL to a PDF document to be processed */ - filePath: string; - - /** Mistral API key for authentication */ - apiKey: string; - - /** Output format for the extracted content (default: 'markdown') */ - resultType?: 'markdown' | 'text' | 'json'; - - /** Whether to include base64-encoded images in the response */ - includeImageBase64?: boolean; - - /** Specific pages to process (zero-indexed) */ - pages?: number[]; - - /** Maximum number of images to extract from the PDF */ - imageLimit?: number; - - /** Minimum height and width (in pixels) for images to extract */ - imageMinSize?: number; -} - -/** - * Usage information returned by the Mistral OCR API - */ -export interface MistralOcrUsageInfo { - /** Number of pages processed in the document */ - pagesProcessed: number; - - /** Size of the document in bytes */ - docSizeBytes: number; -} - -/** - * Metadata about the processed document - */ -export interface MistralParserMetadata { - /** Unique identifier for this OCR job */ - jobId: string; - - /** File type of the document (typically 'pdf') */ - fileType: string; - - /** Filename extracted from the document URL */ - fileName: string; - - /** Source type (always 'url' for now) */ - source: 'url'; - - /** Original URL to the document */ - sourceUrl: string; - - /** Total number of pages in the document */ - pageCount: number; - - /** Usage statistics from the OCR processing */ - usageInfo?: MistralOcrUsageInfo; - - /** The Mistral OCR model used for processing */ - model: string; - - /** The output format that was requested */ - resultType?: 'markdown' | 'text' | 'json'; - - /** ISO timestamp when the document was processed */ - processedAt: string; -} - -/** - * Output data structure from the Mistral OCR parser - */ -export interface MistralParserOutputData { - /** Extracted content in the requested format */ - content: string; - - /** Metadata about the parsed document and processing */ - metadata: MistralParserMetadata; -} - -/** - * Complete response from the Mistral OCR parser tool - */ -export interface MistralParserOutput extends ToolResponse { - /** The output data containing content and metadata */ - output: MistralParserOutputData; -} +import { ToolConfig } from '../types' +import { MistralParserInput, MistralParserOutput } from './types' export const mistralParserTool: ToolConfig = { id: 'mistral_parser', @@ -103,6 +13,11 @@ export const mistralParserTool: ToolConfig { - console.log('Setting up headers with API key:', params.apiKey ? `${params.apiKey.substring(0, 5)}...` : 'Missing'); + console.log( + 'Setting up headers with API key:', + params.apiKey ? `${params.apiKey.substring(0, 5)}...` : 'Missing' + ) return { 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': `Bearer ${params.apiKey}`, - }; + Accept: 'application/json', + Authorization: `Bearer ${params.apiKey}`, + } }, body: (params) => { if (!params || typeof params !== 'object') { - throw new Error('Invalid parameters: Parameters must be provided as an object'); + throw new Error('Invalid parameters: Parameters must be provided as an object') } - + // Validate required parameters if (!params.apiKey || typeof params.apiKey !== 'string' || params.apiKey.trim() === '') { - throw new Error('Missing or invalid API key: A valid Mistral API key is required'); + throw new Error('Missing or invalid API key: A valid Mistral API key is required') } - - if (!params.filePath || typeof params.filePath !== 'string' || params.filePath.trim() === '') { - throw new Error('Missing or invalid file path: Please provide a URL to a PDF document'); + + // Check if we have a file upload instead of direct URL + if ( + params.fileUpload && + (!params.filePath || params.filePath === 'null' || params.filePath === '') + ) { + // Try to extract file path from upload data + if ( + typeof params.fileUpload === 'object' && + params.fileUpload !== null && + params.fileUpload.path + ) { + // Get the full URL to the file + let uploadedFilePath = params.fileUpload.path + + // Make sure the file path is an absolute URL + if (uploadedFilePath.startsWith('/')) { + // If it's a relative path starting with /, convert to absolute URL + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' + uploadedFilePath = `${baseUrl}${uploadedFilePath}` + } + + // Set the filePath parameter + params.filePath = uploadedFilePath + console.log('Using uploaded file:', uploadedFilePath) + } else { + throw new Error('Invalid file upload: Upload data is missing or invalid') + } + } + + if ( + !params.filePath || + typeof params.filePath !== 'string' || + params.filePath.trim() === '' + ) { + throw new Error('Missing or invalid file path: Please provide a URL to a PDF document') } - + // Validate and normalize URL - let url; + let url try { - url = new URL(params.filePath.trim()); - + url = new URL(params.filePath.trim()) + // Validate protocol if (!['http:', 'https:'].includes(url.protocol)) { - throw new Error(`Invalid protocol: ${url.protocol}. URL must use HTTP or HTTPS protocol`); + throw new Error(`Invalid protocol: ${url.protocol}. URL must use HTTP or HTTPS protocol`) } - + // Validate against known unsupported services if (url.hostname.includes('drive.google.com') || url.hostname.includes('docs.google.com')) { throw new Error( 'Google Drive links are not supported by the Mistral OCR API. ' + - 'Please upload your PDF to a public web server or provide a direct download link ' + - 'that ends with .pdf extension.' - ); + 'Please upload your PDF to a public web server or provide a direct download link ' + + 'that ends with .pdf extension.' + ) } - + // Validate file appears to be a PDF (stricter check with informative warning) - const pathname = url.pathname.toLowerCase(); + const pathname = url.pathname.toLowerCase() if (!pathname.endsWith('.pdf')) { // Check if PDF is included in the path at all if (!pathname.includes('pdf')) { console.warn( 'Warning: URL does not appear to point to a PDF document. ' + - 'The Mistral OCR API is designed to work with PDF files. ' + - 'Please ensure your URL points to a valid PDF document (ideally ending with .pdf extension).' - ); + 'The Mistral OCR API is designed to work with PDF files. ' + + 'Please ensure your URL points to a valid PDF document (ideally ending with .pdf extension).' + ) } else { // If "pdf" is in the URL but not at the end, give a different warning console.warn( 'Warning: URL contains "pdf" but does not end with .pdf extension. ' + - 'This might still work if the server returns a valid PDF document despite the missing extension.' - ); + 'This might still work if the server returns a valid PDF document despite the missing extension.' + ) } } } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); + const errorMessage = error instanceof Error ? error.message : String(error) throw new Error( `Invalid URL format: ${errorMessage}. ` + - 'Please provide a valid HTTP or HTTPS URL to a PDF document (e.g., https://example.com/document.pdf)' - ); + 'Please provide a valid HTTP or HTTPS URL to a PDF document (e.g., https://example.com/document.pdf)' + ) } - + // Create the request body with required parameters const requestBody: Record = { - model: "mistral-ocr-latest", + model: 'mistral-ocr-latest', document: { - type: "document_url", - document_url: url.toString() - } - }; + type: 'document_url', + document_url: url.toString(), + }, + } // Add optional parameters with proper validation // Include images (base64) if (params.includeImageBase64 !== undefined) { if (typeof params.includeImageBase64 !== 'boolean') { - console.warn('includeImageBase64 parameter should be a boolean, using default (false)'); + console.warn('includeImageBase64 parameter should be a boolean, using default (false)') } else { - requestBody.include_image_base64 = params.includeImageBase64; + requestBody.include_image_base64 = params.includeImageBase64 } } - // Page selection - if (params.pages !== undefined) { + // Page selection - safely handle null and undefined + if (params.pages !== undefined && params.pages !== null) { if (Array.isArray(params.pages) && params.pages.length > 0) { // Validate all page numbers are non-negative integers const validPages = params.pages.filter( (page) => typeof page === 'number' && Number.isInteger(page) && page >= 0 - ); - + ) + if (validPages.length > 0) { - requestBody.pages = validPages; - + requestBody.pages = validPages + if (validPages.length !== params.pages.length) { console.warn( `Some invalid page numbers were removed. ` + - `Using ${validPages.length} valid pages: ${validPages.join(', ')}` - ); + `Using ${validPages.length} valid pages: ${validPages.join(', ')}` + ) } } else { - console.warn('No valid page numbers provided, processing all pages'); + console.warn('No valid page numbers provided, processing all pages') } - } else if (params.pages.length === 0) { - console.warn('Empty pages array provided, processing all pages'); + } else if (Array.isArray(params.pages) && params.pages.length === 0) { + console.warn('Empty pages array provided, processing all pages') } } - // Image limit - if (params.imageLimit !== undefined) { - const imageLimit = Number(params.imageLimit); + // Image limit - safely handle null and undefined + if (params.imageLimit !== undefined && params.imageLimit !== null) { + const imageLimit = Number(params.imageLimit) if (Number.isInteger(imageLimit) && imageLimit > 0) { - requestBody.image_limit = imageLimit; + requestBody.image_limit = imageLimit } else { - console.warn('imageLimit must be a positive integer, ignoring this parameter'); + console.warn('imageLimit must be a positive integer, ignoring this parameter') } } - // Minimum image size - if (params.imageMinSize !== undefined) { - const imageMinSize = Number(params.imageMinSize); + // Minimum image size - safely handle null and undefined + if (params.imageMinSize !== undefined && params.imageMinSize !== null) { + const imageMinSize = Number(params.imageMinSize) if (Number.isInteger(imageMinSize) && imageMinSize > 0) { - requestBody.image_min_size = imageMinSize; + requestBody.image_min_size = imageMinSize } else { - console.warn('imageMinSize must be a positive integer, ignoring this parameter'); + console.warn('imageMinSize must be a positive integer, ignoring this parameter') } } @@ -282,10 +233,10 @@ export const mistralParserTool: ToolConfig 0) { content = ocrResult.pages - .map((page: any) => (page && typeof page.markdown === 'string') ? page.markdown : '') + .map((page: any) => (page && typeof page.markdown === 'string' ? page.markdown : '')) .filter(Boolean) - .join('\n\n'); + .join('\n\n') } else { - console.warn('No pages found in OCR result, returning raw response'); - content = JSON.stringify(ocrResult, null, 2); + console.warn('No pages found in OCR result, returning raw response') + content = JSON.stringify(ocrResult, null, 2) } - + // Process based on requested result type if (resultType === 'text') { // Strip markdown formatting content = content - .replace(/\#\#*\s/g, '') // Remove markdown headers - .replace(/\*\*/g, '') // Remove bold markers - .replace(/\*/g, '') // Remove italic markers - .replace(/\n{3,}/g, '\n\n'); // Normalize newlines + .replace(/\#\#*\s/g, '') // Remove markdown headers + .replace(/\*\*/g, '') // Remove bold markers + .replace(/\*/g, '') // Remove italic markers + .replace(/\n{3,}/g, '\n\n') // Normalize newlines } else if (resultType === 'json') { // Return the structured data as JSON string - content = JSON.stringify(ocrResult, null, 2); + content = JSON.stringify(ocrResult, null, 2) } - + // Extract file information with proper validation - let fileName = 'document.pdf'; - let fileType = 'pdf'; - + let fileName = 'document.pdf' + let fileType = 'pdf' + if (sourceUrl) { try { - const url = new URL(sourceUrl); - const pathSegments = url.pathname.split('/'); - const lastSegment = pathSegments[pathSegments.length - 1]; - + const url = new URL(sourceUrl) + const pathSegments = url.pathname.split('/') + const lastSegment = pathSegments[pathSegments.length - 1] + if (lastSegment && lastSegment.length > 0) { - fileName = lastSegment; - const fileExtParts = fileName.split('.'); + fileName = lastSegment + const fileExtParts = fileName.split('.') if (fileExtParts.length > 1) { - fileType = fileExtParts[fileExtParts.length - 1].toLowerCase(); + fileType = fileExtParts[fileExtParts.length - 1].toLowerCase() } } } catch (urlError) { - console.warn('Failed to parse document URL:', urlError); + console.warn('Failed to parse document URL:', urlError) } } - + // Generate a tracking ID with timestamp and random component for uniqueness - const timestamp = Date.now(); - const randomId = Math.random().toString(36).substring(2, 10); - const jobId = `mistral-ocr-${timestamp}-${randomId}`; - + const timestamp = Date.now() + const randomId = Math.random().toString(36).substring(2, 10) + const jobId = `mistral-ocr-${timestamp}-${randomId}` + // Map API response fields to our schema with proper type checking - const usageInfo = ocrResult.usage_info && typeof ocrResult.usage_info === 'object' - ? { - pagesProcessed: typeof ocrResult.usage_info.pages_processed === 'number' - ? ocrResult.usage_info.pages_processed - : Number(ocrResult.usage_info.pages_processed), - docSizeBytes: typeof ocrResult.usage_info.doc_size_bytes === 'number' - ? ocrResult.usage_info.doc_size_bytes - : Number(ocrResult.usage_info.doc_size_bytes) - } - : undefined; - + const usageInfo = + ocrResult.usage_info && typeof ocrResult.usage_info === 'object' + ? { + pagesProcessed: + typeof ocrResult.usage_info.pages_processed === 'number' + ? ocrResult.usage_info.pages_processed + : Number(ocrResult.usage_info.pages_processed), + docSizeBytes: + typeof ocrResult.usage_info.doc_size_bytes === 'number' + ? ocrResult.usage_info.doc_size_bytes + : Number(ocrResult.usage_info.doc_size_bytes), + } + : undefined + // Return properly structured response const parserResponse: MistralParserOutput = { success: true, @@ -410,95 +373,116 @@ export const mistralParserTool: ToolConfig { - console.error('Mistral OCR processing error:', error); - + console.error('Mistral OCR processing error:', error) + // Helper function to extract message from various error types const getErrorMessage = (err: any): string => { - if (typeof err === 'string') return err; - if (err instanceof Error) return err.message; + if (typeof err === 'string') return err + if (err instanceof Error) return err.message if (err && typeof err === 'object') { - if (err.message) return String(err.message); - if (err.error) return typeof err.error === 'string' ? err.error : JSON.stringify(err.error); + if (err.message) return String(err.message) + if (err.error) return typeof err.error === 'string' ? err.error : JSON.stringify(err.error) } - return 'Unknown error'; - }; - + return 'Unknown error' + } + // Get base error message - const errorMsg = getErrorMessage(error); - + const errorMsg = getErrorMessage(error) + // Handle null reference errors which often occur with invalid PDF URLs - if (errorMsg.includes('Cannot read properties of null') || - (errorMsg.includes('null') && errorMsg.includes('length'))) { - return 'Mistral OCR Error: Invalid PDF document URL. The URL provided either does not point to a valid PDF file or the PDF cannot be accessed. Please ensure you provide a direct link to a publicly accessible PDF file with .pdf extension.'; + if ( + errorMsg.includes('Cannot read properties of null') || + (errorMsg.includes('null') && errorMsg.includes('length')) + ) { + return 'Mistral OCR Error: Invalid PDF document URL. The URL provided either does not point to a valid PDF file or the PDF cannot be accessed. Please ensure you provide a direct link to a publicly accessible PDF file with .pdf extension.' } - + // Handle common API error status codes if (typeof error === 'object' && error !== null) { - const status = error.status || (error.response && error.response.status); - + const status = error.status || (error.response && error.response.status) + if (status) { switch (status) { case 400: - return 'Mistral OCR Error: The request was invalid. Please check your PDF URL and parameters.'; + return 'Mistral OCR Error: The request was invalid. Please check your PDF URL and parameters.' case 401: - return 'Mistral OCR Error: Invalid API key. Please check your Mistral API key.'; + return 'Mistral OCR Error: Invalid API key. Please check your Mistral API key.' case 403: - return 'Mistral OCR Error: Access forbidden. Your API key may not have permission to use the OCR service.'; + return 'Mistral OCR Error: Access forbidden. Your API key may not have permission to use the OCR service.' case 404: - return 'Mistral OCR Error: The PDF document could not be found. Please check that the URL is accessible.'; + return 'Mistral OCR Error: The PDF document could not be found. Please check that the URL is accessible.' case 413: - return 'Mistral OCR Error: The PDF document is too large for processing.'; + return 'Mistral OCR Error: The PDF document is too large for processing.' case 415: - return 'Mistral OCR Error: Unsupported file format. Please ensure the URL points to a valid PDF document with a .pdf extension.'; + return 'Mistral OCR Error: Unsupported file format. Please ensure the URL points to a valid PDF document with a .pdf extension.' case 429: - return 'Mistral OCR Error: Rate limit exceeded. Please try again later.'; + return 'Mistral OCR Error: Rate limit exceeded. Please try again later.' case 500: case 502: case 503: case 504: - return 'Mistral OCR Error: Service temporarily unavailable. Please try again later.'; + return 'Mistral OCR Error: Service temporarily unavailable. Please try again later.' } } } - + // Handle common network and URL errors if (errorMsg.includes('URL') || errorMsg.includes('protocol') || errorMsg.includes('http')) { - return 'Mistral OCR Error: Invalid PDF URL format. Please provide a complete URL starting with https:// to your PDF document (e.g., https://example.com/document.pdf).'; + return 'Mistral OCR Error: Invalid PDF URL format. Please provide a complete URL starting with https:// to your PDF document (e.g., https://example.com/document.pdf).' } - - if (errorMsg.includes('ETIMEDOUT') || errorMsg.includes('timeout') || errorMsg.includes('ECONNABORTED')) { - return 'Mistral OCR Error: The request timed out. The PDF document may be too large or the server is unresponsive.'; + + if ( + errorMsg.includes('ETIMEDOUT') || + errorMsg.includes('timeout') || + errorMsg.includes('ECONNABORTED') + ) { + return 'Mistral OCR Error: The request timed out. The PDF document may be too large or the server is unresponsive.' } - - if (errorMsg.includes('ENOTFOUND') || errorMsg.includes('ECONNREFUSED') || errorMsg.includes('ECONNRESET')) { - return 'Mistral OCR Error: Could not connect to the document URL. Please verify the document is accessible.'; + + if ( + errorMsg.includes('ENOTFOUND') || + errorMsg.includes('ECONNREFUSED') || + errorMsg.includes('ECONNRESET') + ) { + return 'Mistral OCR Error: Could not connect to the document URL. Please verify the document is accessible.' } - - if (errorMsg.includes('JSON') || errorMsg.includes('Unexpected token') || errorMsg.includes('parse')) { - return 'Mistral OCR Error: Failed to parse the response from the OCR service.'; + + if ( + errorMsg.includes('JSON') || + errorMsg.includes('Unexpected token') || + errorMsg.includes('parse') + ) { + return 'Mistral OCR Error: Failed to parse the response from the OCR service.' } - + // PDF-specific error handling if (errorMsg.toLowerCase().includes('pdf')) { - if (errorMsg.toLowerCase().includes('invalid') || errorMsg.toLowerCase().includes('corrupted')) { - return 'Mistral OCR Error: The document appears to be an invalid or corrupted PDF. Please check that the URL points to a valid, properly formatted PDF document.'; + if ( + errorMsg.toLowerCase().includes('invalid') || + errorMsg.toLowerCase().includes('corrupted') + ) { + return 'Mistral OCR Error: The document appears to be an invalid or corrupted PDF. Please check that the URL points to a valid, properly formatted PDF document.' } - if (errorMsg.toLowerCase().includes('password') || errorMsg.toLowerCase().includes('protected') || errorMsg.toLowerCase().includes('encrypted')) { - return 'Mistral OCR Error: The PDF document appears to be password-protected or encrypted. The OCR service cannot process protected documents.'; + if ( + errorMsg.toLowerCase().includes('password') || + errorMsg.toLowerCase().includes('protected') || + errorMsg.toLowerCase().includes('encrypted') + ) { + return 'Mistral OCR Error: The PDF document appears to be password-protected or encrypted. The OCR service cannot process protected documents.' } } - + // Default error message with the original error for context - return `Mistral OCR Error: Invalid PDF document or URL. Please ensure you provide a direct link to a valid PDF file. Technical details: ${errorMsg}`; + return `Mistral OCR Error: Invalid PDF document or URL. Please ensure you provide a direct link to a valid PDF file. Technical details: ${errorMsg}` }, -} \ No newline at end of file +} diff --git a/sim/tools/mistral/types.ts b/sim/tools/mistral/types.ts new file mode 100644 index 00000000000..084d822db07 --- /dev/null +++ b/sim/tools/mistral/types.ts @@ -0,0 +1,95 @@ +import { ToolResponse } from '../types' + +/** + * Input parameters for the Mistral OCR parser tool + */ +export interface MistralParserInput { + /** URL to a PDF document to be processed */ + filePath: string + + /** File upload data (from file-upload component) */ + fileUpload?: any + + /** Mistral API key for authentication */ + apiKey: string + + /** Output format for the extracted content (default: 'markdown') */ + resultType?: 'markdown' | 'text' | 'json' + + /** Whether to include base64-encoded images in the response */ + includeImageBase64?: boolean + + /** Specific pages to process (zero-indexed) */ + pages?: number[] + + /** Maximum number of images to extract from the PDF */ + imageLimit?: number + + /** Minimum height and width (in pixels) for images to extract */ + imageMinSize?: number +} + +/** + * Usage information returned by the Mistral OCR API + */ +export interface MistralOcrUsageInfo { + /** Number of pages processed in the document */ + pagesProcessed: number + + /** Size of the document in bytes */ + docSizeBytes: number +} + +/** + * Metadata about the processed document + */ +export interface MistralParserMetadata { + /** Unique identifier for this OCR job */ + jobId: string + + /** File type of the document (typically 'pdf') */ + fileType: string + + /** Filename extracted from the document URL */ + fileName: string + + /** Source type (always 'url' for now) */ + source: 'url' + + /** Original URL to the document */ + sourceUrl: string + + /** Total number of pages in the document */ + pageCount: number + + /** Usage statistics from the OCR processing */ + usageInfo?: MistralOcrUsageInfo + + /** The Mistral OCR model used for processing */ + model: string + + /** The output format that was requested */ + resultType?: 'markdown' | 'text' | 'json' + + /** ISO timestamp when the document was processed */ + processedAt: string +} + +/** + * Output data structure from the Mistral OCR parser + */ +export interface MistralParserOutputData { + /** Extracted content in the requested format */ + content: string + + /** Metadata about the parsed document and processing */ + metadata: MistralParserMetadata +} + +/** + * Complete response from the Mistral OCR parser tool + */ +export interface MistralParserOutput extends ToolResponse { + /** The output data containing content and metadata */ + output: MistralParserOutputData +} From 3996c6330962a158dd6058f62448b2f62e3b706b Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 2 Apr 2025 09:57:42 -0700 Subject: [PATCH 2/2] updated twilio icon --- sim/components/icons.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/sim/components/icons.tsx b/sim/components/icons.tsx index 4efc74806e7..485faf2ecb2 100644 --- a/sim/components/icons.tsx +++ b/sim/components/icons.tsx @@ -1736,20 +1736,11 @@ export function ConfluenceIcon(props: SVGProps) { export function TwilioIcon(props: SVGProps) { return ( -