| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- /**
- * @node ocr-upload
- * @name OCR Upload
- * @category ocr
- * @version 1.0.1
- * @description Upload a file for OCR processing via pdftoimageapi
- * @icon file-scan
- */
- const configSchema = {
- type: 'object',
- properties: {
- baseUrl: {
- type: 'string',
- title: 'API Base URL',
- default: 'http://localhost:3000',
- description: 'Base URL of the OCR API'
- },
- credentialId: {
- type: 'string',
- title: 'Credential',
- description: 'Bearer or API Key credential for authentication',
- dynamicOptions: {
- source: 'credentials',
- filter: { type: 'api_key' }
- }
- },
- processingMode: {
- type: 'string',
- title: 'Processing Mode',
- enum: ['auto', 'single', 'batch', 'smart'],
- default: 'auto',
- description: 'How to process the uploaded file'
- },
- batchSize: {
- type: 'number',
- title: 'Batch Size',
- default: 5,
- description: 'Number of pages per batch (for batch mode)'
- },
- targetWidth: {
- type: 'number',
- title: 'Target Width',
- description: 'Target width in pixels for image conversion'
- },
- groupId: {
- type: 'string',
- title: 'Group ID',
- description: 'Optional group ID to associate jobs'
- }
- },
- required: ['baseUrl']
- };
- const inputSchema = {
- type: 'object',
- properties: {
- file: {
- type: 'object',
- description: 'Binary file object with type, data (base64), mimeType, filename, size'
- },
- storage: {
- type: 'object',
- description: 'Storage reference to retrieve file from database',
- properties: {
- collection: { type: 'string' },
- id: { type: 'string' }
- }
- }
- }
- };
- const outputSchema = {
- type: 'object',
- properties: {
- job: {
- type: 'object',
- properties: {
- id: { type: 'string' },
- status: { type: 'string' },
- filename: { type: 'string' },
- created_at: { type: 'string' }
- }
- }
- }
- };
- const outputs = [
- { name: 'main', displayName: 'Job Created', type: 'object', color: '#10b981' }
- ];
- function isBinaryInput(input) {
- return input && input.type === 'binary' && typeof input.data === 'string';
- }
- function generateBoundary() {
- return '----SmartBoticBoundary' + smartbotic.utils.uuid().replace(/-/g, '');
- }
- function buildMultipartBody(boundary, file, options) {
- const crlf = '\r\n';
- let body = '';
- body += `--${boundary}${crlf}`;
- body += `Content-Disposition: form-data; name="file"; filename="${file.filename}"${crlf}`;
- body += `Content-Type: ${file.mimeType}${crlf}`;
- body += `Content-Transfer-Encoding: base64${crlf}`;
- body += crlf;
- body += file.data;
- body += crlf;
- if (options.processingMode && options.processingMode !== 'auto') {
- body += `--${boundary}${crlf}`;
- body += `Content-Disposition: form-data; name="processing_mode"${crlf}`;
- body += crlf;
- body += options.processingMode;
- body += crlf;
- }
- if (options.batchSize) {
- body += `--${boundary}${crlf}`;
- body += `Content-Disposition: form-data; name="batch_size"${crlf}`;
- body += crlf;
- body += String(options.batchSize);
- body += crlf;
- }
- if (options.targetWidth) {
- body += `--${boundary}${crlf}`;
- body += `Content-Disposition: form-data; name="target_width"${crlf}`;
- body += crlf;
- body += String(options.targetWidth);
- body += crlf;
- }
- if (options.groupId) {
- body += `--${boundary}${crlf}`;
- body += `Content-Disposition: form-data; name="group_id"${crlf}`;
- body += crlf;
- body += options.groupId;
- body += crlf;
- }
- body += `--${boundary}--${crlf}`;
- return body;
- }
- module.exports = {
- configSchema,
- inputSchema,
- outputSchema,
- outputs,
- async execute(config, input, context) {
- const baseUrl = (config.baseUrl || 'http://localhost:3000').replace(/\/$/, '');
- let file = null;
- if (isBinaryInput(input.file)) {
- file = input.file;
- } else if (isBinaryInput(input)) {
- file = input;
- } else if (input.storage && input.storage.collection && input.storage.id) {
- const storageResult = smartbotic.storage.get(input.storage.collection, input.storage.id);
- if (!storageResult.found) {
- throw new Error(`File not found in storage: ${input.storage.collection}/${input.storage.id}`);
- }
- const doc = storageResult.document;
- file = {
- type: 'binary',
- data: doc.data,
- mimeType: doc.mimeType,
- filename: doc.filename,
- size: doc.size
- };
- }
- if (!file) {
- throw new Error('No file provided. Provide either a binary file object or a storage reference.');
- }
- const headers = {};
- if (config.credentialId) {
- const auth = smartbotic.credentials.get(config.credentialId);
- if (!auth.success) {
- throw new Error(`Failed to load credential: ${auth.error}`);
- }
- headers[auth.headerName] = auth.headerValue;
- }
- const boundary = generateBoundary();
- headers['Content-Type'] = `multipart/form-data; boundary=${boundary}`;
- const body = buildMultipartBody(boundary, file, {
- processingMode: config.processingMode,
- batchSize: config.batchSize,
- targetWidth: config.targetWidth,
- groupId: config.groupId
- });
- smartbotic.log.info(`OCR Upload: ${file.filename} (${file.size} bytes)`);
- const response = smartbotic.http.request({
- method: 'POST',
- url: `${baseUrl}/api/ocr/upload`,
- headers: headers,
- body: body,
- timeout: 120000
- });
- if (response.status < 200 || response.status >= 300) {
- const errorMsg = typeof response.data === 'string' ? response.data : JSON.stringify(response.data);
- if (response.status === 401 && !config.credentialId) { throw new Error('Authentication required - please select a credential in node settings'); } throw new Error(`OCR API error (${response.status}): ${errorMsg}`);
- }
- const data = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
- return {
- job: data.job || data
- };
- }
- };
|