mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
Fix-convert-V2 (#5147)
Custom processors can now return consume all inputs flag. This allows to have many inputs to single output consumption Fixed multi call conversion logic
This commit is contained in:
parent
5d827df08c
commit
f2bffe2dc6
@ -75,8 +75,7 @@ public class ReactRoutingController {
|
||||
|
||||
@GetMapping(
|
||||
"/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}")
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request) throws IOException {
|
||||
return serveIndexHtml(request);
|
||||
}
|
||||
|
||||
|
||||
@ -331,7 +331,9 @@ public class SecurityConfiguration {
|
||||
formLogin ->
|
||||
formLogin
|
||||
.loginPage("/login") // Redirect here when unauthenticated
|
||||
.loginProcessingUrl("/perform_login") // Process form posts here (not /login)
|
||||
.loginProcessingUrl(
|
||||
"/perform_login") // Process form posts here (not
|
||||
// /login)
|
||||
.successHandler(
|
||||
new CustomAuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
|
||||
57
frontend/package-lock.json
generated
57
frontend/package-lock.json
generated
@ -456,6 +456,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@ -499,6 +500,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@ -579,6 +581,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "1.5.0",
|
||||
"@embedpdf/models": "1.5.0"
|
||||
@ -678,6 +681,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
|
||||
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -694,6 +698,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
|
||||
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -711,6 +716,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
|
||||
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -764,6 +770,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
|
||||
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -798,6 +805,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
|
||||
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -834,6 +842,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
|
||||
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -909,6 +918,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
|
||||
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@ -1064,6 +1074,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@ -1107,6 +1118,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@ -2137,6 +2149,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz",
|
||||
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"clsx": "^2.1.1",
|
||||
@ -2187,6 +2200,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz",
|
||||
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "^18.x || ^19.x"
|
||||
}
|
||||
@ -2254,6 +2268,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
|
||||
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.5",
|
||||
@ -3186,6 +3201,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
|
||||
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.16"
|
||||
}
|
||||
@ -3304,7 +3320,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
|
||||
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.9.0"
|
||||
}
|
||||
@ -4081,6 +4096,7 @@
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@ -4409,6 +4425,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@ -4419,6 +4436,7 @@
|
||||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@ -4488,6 +4506,7 @@
|
||||
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.3",
|
||||
"@typescript-eslint/types": "8.46.3",
|
||||
@ -5201,7 +5220,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
|
||||
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.24"
|
||||
}
|
||||
@ -5211,7 +5229,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
|
||||
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.24",
|
||||
"@vue/shared": "3.5.24"
|
||||
@ -5222,7 +5239,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
|
||||
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.24",
|
||||
"@vue/runtime-core": "3.5.24",
|
||||
@ -5235,7 +5251,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
|
||||
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.24",
|
||||
"@vue/shared": "3.5.24"
|
||||
@ -5262,6 +5277,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -5669,7 +5685,6 @@
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -5946,6 +5961,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.19",
|
||||
"caniuse-lite": "^1.0.30001751",
|
||||
@ -6993,7 +7009,8 @@
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz",
|
||||
"integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
@ -7388,6 +7405,7 @@
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@ -7558,6 +7576,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@ -7724,8 +7743,7 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
@ -7790,7 +7808,6 @@
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
|
||||
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
@ -8881,6 +8898,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@ -9357,7 +9375,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
@ -9678,6 +9695,7 @@
|
||||
"integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.19",
|
||||
"@asamuzakjp/dom-selector": "^6.7.3",
|
||||
@ -10264,8 +10282,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
@ -11411,6 +11428,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@ -11690,6 +11708,7 @@
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
|
||||
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
@ -12072,6 +12091,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -12081,6 +12101,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@ -13592,7 +13613,6 @@
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -13801,6 +13821,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -14102,6 +14123,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -14183,6 +14205,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.3.0"
|
||||
},
|
||||
@ -14387,6 +14410,7 @@
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@ -14538,6 +14562,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -14551,6 +14576,7 @@
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@ -15162,8 +15188,7 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { ToolType, useToolOperation, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { AdjustContrastParameters, defaultParameters } from '@app/hooks/tools/adjustContrast/useAdjustContrastParameters';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { applyAdjustmentsToCanvas } from '@app/components/tools/adjustContrast/utils';
|
||||
@ -46,7 +46,7 @@ async function buildAdjustedPdfForFile(file: File, params: AdjustContrastParamet
|
||||
return out;
|
||||
}
|
||||
|
||||
async function processPdfClientSide(params: AdjustContrastParameters, files: File[]): Promise<File[]> {
|
||||
async function processPdfClientSide(params: AdjustContrastParameters, files: File[]): Promise<CustomProcessorResult> {
|
||||
// Limit concurrency to avoid exhausting memory/CPU while still getting speedups
|
||||
// Heuristic: use up to 4 workers on capable machines, otherwise 2-3
|
||||
let CONCURRENCY_LIMIT = 2;
|
||||
@ -72,7 +72,12 @@ async function processPdfClientSide(params: AdjustContrastParameters, files: Fil
|
||||
return results;
|
||||
};
|
||||
|
||||
return mapWithConcurrency(files, CONCURRENCY_LIMIT, (file) => buildAdjustedPdfForFile(file, params));
|
||||
const processedFiles = await mapWithConcurrency(files, CONCURRENCY_LIMIT, (file) => buildAdjustedPdfForFile(file, params));
|
||||
|
||||
return {
|
||||
files: processedFiles,
|
||||
consumedAllInputs: false,
|
||||
};
|
||||
}
|
||||
|
||||
export const adjustContrastOperationConfig = {
|
||||
|
||||
@ -36,7 +36,10 @@ export function useAutomateOperation() {
|
||||
);
|
||||
|
||||
console.log(`✅ Automation completed, returning ${finalResults.length} files`);
|
||||
return finalResults;
|
||||
return {
|
||||
files: finalResults,
|
||||
consumedAllInputs: false,
|
||||
};
|
||||
}, [toolRegistry]);
|
||||
|
||||
return useToolOperation<AutomateParameters>({
|
||||
|
||||
@ -3,8 +3,8 @@ import apiClient from '@app/services/apiClient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ConvertParameters, defaultParameters } from '@app/hooks/tools/convert/useConvertParameters';
|
||||
import { createFileFromApiResponse } from '@app/utils/fileResponseUtils';
|
||||
import { useToolOperation, ToolType } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { getEndpointUrl, isImageFormat, isWebFormat } from '@app/utils/convertUtils';
|
||||
import { useToolOperation, ToolType, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { getEndpointUrl, isImageFormat, isWebFormat, isOfficeFormat } from '@app/utils/convertUtils';
|
||||
|
||||
// Static function that can be used by both the hook and automation executor
|
||||
export const shouldProcessFilesSeparately = (
|
||||
@ -21,6 +21,10 @@ export const shouldProcessFilesSeparately = (
|
||||
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
|
||||
// PDF to text-like formats should be one output per input
|
||||
(parameters.fromExtension === 'pdf' && ['txt', 'rtf', 'csv'].includes(parameters.toExtension)) ||
|
||||
// PDF to office format conversions (each PDF should generate its own office file)
|
||||
(parameters.fromExtension === 'pdf' && isOfficeFormat(parameters.toExtension)) ||
|
||||
// Office files to PDF conversions (each file should be processed separately via LibreOffice)
|
||||
(isOfficeFormat(parameters.fromExtension) && parameters.toExtension === 'pdf') ||
|
||||
// Web files to PDF conversions (each web file should generate its own PDF)
|
||||
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
|
||||
parameters.toExtension === 'pdf') ||
|
||||
@ -98,7 +102,7 @@ export const createFileFromResponse = (
|
||||
export const convertProcessor = async (
|
||||
parameters: ConvertParameters,
|
||||
selectedFiles: File[]
|
||||
): Promise<File[]> => {
|
||||
): Promise<CustomProcessorResult> => {
|
||||
const processedFiles: File[] = [];
|
||||
const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension);
|
||||
|
||||
@ -107,7 +111,9 @@ export const convertProcessor = async (
|
||||
}
|
||||
|
||||
// Convert-specific routing logic: decide batch vs individual processing
|
||||
if (shouldProcessFilesSeparately(selectedFiles, parameters)) {
|
||||
const isSeparateProcessing = shouldProcessFilesSeparately(selectedFiles, parameters);
|
||||
|
||||
if (isSeparateProcessing) {
|
||||
// Individual processing for complex cases (PDF→image, smart detection, etc.)
|
||||
for (const file of selectedFiles) {
|
||||
try {
|
||||
@ -134,7 +140,14 @@ export const convertProcessor = async (
|
||||
processedFiles.push(convertedFile);
|
||||
}
|
||||
|
||||
return processedFiles;
|
||||
// When batch processing multiple files into one output (e.g., 3 images → 1 PDF),
|
||||
// mark all inputs as consumed even though there's only 1 output file
|
||||
const isCombiningMultiple = !isSeparateProcessing && selectedFiles.length > 1;
|
||||
|
||||
return {
|
||||
files: processedFiles,
|
||||
consumedAllInputs: isCombiningMultiple,
|
||||
};
|
||||
};
|
||||
|
||||
// Static configuration object
|
||||
@ -151,7 +164,7 @@ export const useConvertOperation = () => {
|
||||
const customConvertProcessor = useCallback(async (
|
||||
parameters: ConvertParameters,
|
||||
selectedFiles: File[]
|
||||
): Promise<File[]> => {
|
||||
): Promise<CustomProcessorResult> => {
|
||||
return convertProcessor(parameters, selectedFiles);
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { ToolType, useToolOperation, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
|
||||
import { ExtractPagesParameters, defaultParameters } from '@app/hooks/tools/extractPages/useExtractPagesParameters';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
@ -23,7 +23,7 @@ async function resolveSelectionToCsv(expression: string, file: File): Promise<st
|
||||
export const extractPagesOperationConfig = {
|
||||
toolType: ToolType.custom,
|
||||
operationType: 'extractPages',
|
||||
customProcessor: async (parameters: ExtractPagesParameters, files: File[]): Promise<File[]> => {
|
||||
customProcessor: async (parameters: ExtractPagesParameters, files: File[]): Promise<CustomProcessorResult> => {
|
||||
const outputs: File[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
@ -43,7 +43,10 @@ export const extractPagesOperationConfig = {
|
||||
outputs.push(outFile);
|
||||
}
|
||||
|
||||
return outputs;
|
||||
return {
|
||||
files: outputs,
|
||||
consumedAllInputs: false,
|
||||
};
|
||||
},
|
||||
defaultParameters,
|
||||
} as const;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation, ToolType } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { useToolOperation, ToolType, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
|
||||
import { RemoveAnnotationsParameters, defaultParameters } from '@app/hooks/tools/removeAnnotations/useRemoveAnnotationsParameters';
|
||||
import { PDFDocument, PDFName, PDFRef, PDFDict } from 'pdf-lib';
|
||||
// Client-side PDF processing using PDF-lib
|
||||
const removeAnnotationsProcessor = async (_parameters: RemoveAnnotationsParameters, files: File[]): Promise<File[]> => {
|
||||
const removeAnnotationsProcessor = async (_parameters: RemoveAnnotationsParameters, files: File[]): Promise<CustomProcessorResult> => {
|
||||
const processedFiles: File[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
@ -75,7 +75,10 @@ const removeAnnotationsProcessor = async (_parameters: RemoveAnnotationsParamete
|
||||
}
|
||||
}
|
||||
|
||||
return processedFiles;
|
||||
return {
|
||||
files: processedFiles,
|
||||
consumedAllInputs: false,
|
||||
};
|
||||
};
|
||||
|
||||
// Static configuration object
|
||||
|
||||
@ -4,6 +4,7 @@ import apiClient from '@app/services/apiClient'; // Our configured instance
|
||||
import { processResponse, ResponseHandler } from '@app/utils/toolResponseProcessor';
|
||||
import { isEmptyOutput } from '@app/services/errorUtils';
|
||||
import type { ProcessingProgress } from '@app/hooks/tools/shared/useToolState';
|
||||
import type { StirlingFile, FileId } from '@app/types/fileContext';
|
||||
|
||||
export interface ApiCallsConfig<TParams = void> {
|
||||
endpoint: string | ((params: TParams) => string);
|
||||
@ -18,14 +19,14 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
|
||||
const processFiles = useCallback(async (
|
||||
params: TParams,
|
||||
validFiles: File[],
|
||||
validFiles: StirlingFile[],
|
||||
config: ApiCallsConfig<TParams>,
|
||||
onProgress: (progress: ProcessingProgress) => void,
|
||||
onStatus: (status: string) => void,
|
||||
markFileError?: (fileId: string) => void,
|
||||
): Promise<{ outputFiles: File[]; successSourceIds: string[] }> => {
|
||||
markFileError?: (fileId: FileId) => void,
|
||||
): Promise<{ outputFiles: File[]; successSourceIds: FileId[] }> => {
|
||||
const processedFiles: File[] = [];
|
||||
const successSourceIds: string[] = [];
|
||||
const successSourceIds: FileId[] = [];
|
||||
const failedFiles: string[] = [];
|
||||
const total = validFiles.length;
|
||||
|
||||
@ -35,7 +36,7 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
for (let i = 0; i < validFiles.length; i++) {
|
||||
const file = validFiles[i];
|
||||
|
||||
console.debug('[processFiles] Start', { index: i, total, name: file.name, fileId: (file as any).fileId });
|
||||
console.debug('[processFiles] Start', { index: i, total, name: file.name, fileId: file.fileId });
|
||||
onProgress({ current: i + 1, total, currentFileName: file.name });
|
||||
onStatus(`Processing ${file.name} (${i + 1}/${total})`);
|
||||
|
||||
@ -47,7 +48,7 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
responseType: 'blob',
|
||||
cancelToken: cancelTokenRef.current?.token,
|
||||
});
|
||||
console.debug('[processFiles] Response OK', { name: file.name, status: (response as any)?.status });
|
||||
console.debug('[processFiles] Response OK', { name: file.name, status: response.status });
|
||||
|
||||
// Forward to shared response processor (uses tool-specific responseHandler if provided)
|
||||
const responseFiles = await processResponse(
|
||||
@ -63,7 +64,7 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
console.warn('[processFiles] Empty output treated as failure', { name: file.name });
|
||||
failedFiles.push(file.name);
|
||||
try {
|
||||
(markFileError as any)?.((file as any).fileId);
|
||||
markFileError?.(file.fileId);
|
||||
} catch (e) {
|
||||
console.debug('markFileError', e);
|
||||
}
|
||||
@ -71,7 +72,7 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
}
|
||||
processedFiles.push(...responseFiles);
|
||||
// record source id as successful
|
||||
successSourceIds.push((file as any).fileId);
|
||||
successSourceIds.push(file.fileId);
|
||||
console.debug('[processFiles] Success', { name: file.name, produced: responseFiles.length });
|
||||
|
||||
} catch (error) {
|
||||
@ -82,7 +83,7 @@ export const useToolApiCalls = <TParams = void>() => {
|
||||
failedFiles.push(file.name);
|
||||
// mark errored file so UI can highlight
|
||||
try {
|
||||
(markFileError as any)?.((file as any).fileId);
|
||||
markFileError?.(file.fileId);
|
||||
} catch (e) {
|
||||
console.debug('markFileError', e);
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { useToolResources } from '@app/hooks/tools/shared/useToolResources';
|
||||
import { extractErrorMessage } from '@app/utils/toolErrorHandler';
|
||||
import { StirlingFile, extractFiles, FileId, StirlingFileStub, createStirlingFile } from '@app/types/fileContext';
|
||||
import { FILE_EVENTS } from '@app/services/errorUtils';
|
||||
import { getFilenameWithoutExtension } from '@app/utils/fileUtils';
|
||||
import { ResponseHandler } from '@app/utils/toolResponseProcessor';
|
||||
import { createChildStub, generateProcessedFileMetadata } from '@app/contexts/file/fileActions';
|
||||
import { ToolOperation } from '@app/types/file';
|
||||
@ -23,6 +24,20 @@ export enum ToolType {
|
||||
custom,
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from custom processor with optional metadata about input consumption.
|
||||
*/
|
||||
export interface CustomProcessorResult {
|
||||
/** Processed output files */
|
||||
files: File[];
|
||||
/**
|
||||
* When true, marks all input files as successfully consumed regardless of output count.
|
||||
* Use when operation combines N inputs into fewer outputs (e.g., 3 images → 1 PDF).
|
||||
* When false/undefined, uses filename-based mapping to determine which inputs succeeded.
|
||||
*/
|
||||
consumedAllInputs?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for tool operations defining processing behavior and API integration.
|
||||
*
|
||||
@ -98,8 +113,12 @@ export interface CustomToolOperationConfig<TParams> extends BaseToolOperationCon
|
||||
* Custom processing logic that completely bypasses standard file processing.
|
||||
* This tool handles all API calls, response processing, and file creation.
|
||||
* Use for tools with complex routing logic or non-standard processing requirements.
|
||||
*
|
||||
* Returns CustomProcessorResult with:
|
||||
* - files: Processed output files
|
||||
* - consumedAllInputs: true if operation combines N inputs → fewer outputs
|
||||
*/
|
||||
customProcessor: (params: TParams, files: File[]) => Promise<File[]>;
|
||||
customProcessor: (params: TParams, files: File[]) => Promise<CustomProcessorResult>;
|
||||
}
|
||||
|
||||
export type ToolOperationConfig<TParams = void> = SingleFileToolOperationConfig<TParams> | MultiFileToolOperationConfig<TParams> | CustomToolOperationConfig<TParams>;
|
||||
@ -172,17 +191,17 @@ export const useToolOperation = <TParams>(
|
||||
}
|
||||
|
||||
// Handle zero-byte inputs explicitly: mark as error and continue with others
|
||||
const zeroByteFiles = selectedFiles.filter(file => (file as any)?.size === 0);
|
||||
const zeroByteFiles = selectedFiles.filter(file => file.size === 0);
|
||||
if (zeroByteFiles.length > 0) {
|
||||
try {
|
||||
for (const f of zeroByteFiles) {
|
||||
(fileActions.markFileError as any)((f as any).fileId);
|
||||
fileActions.markFileError(f.fileId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('markFileError', e);
|
||||
}
|
||||
}
|
||||
const validFiles = selectedFiles.filter(file => (file as any)?.size > 0);
|
||||
const validFiles: StirlingFile[] = selectedFiles.filter(file => file.size > 0);
|
||||
if (validFiles.length === 0) {
|
||||
actions.setError(t('noValidFiles', 'No valid files to process'));
|
||||
return;
|
||||
@ -215,7 +234,7 @@ export const useToolOperation = <TParams>(
|
||||
|
||||
try {
|
||||
let processedFiles: File[];
|
||||
let successSourceIds: string[] = [];
|
||||
let successSourceIds: FileId[] = [];
|
||||
|
||||
// Use original files directly (no PDF metadata injection - history stored in IndexedDB)
|
||||
const filesForAPI = extractFiles(validFiles);
|
||||
@ -233,14 +252,14 @@ export const useToolOperation = <TParams>(
|
||||
console.debug('[useToolOperation] Multi-file start', { count: filesForAPI.length });
|
||||
const result = await processFiles(
|
||||
params,
|
||||
filesForAPI,
|
||||
validFiles,
|
||||
apiCallsConfig,
|
||||
actions.setProgress,
|
||||
actions.setStatus,
|
||||
fileActions.markFileError as any
|
||||
fileActions.markFileError
|
||||
);
|
||||
processedFiles = result.outputFiles;
|
||||
successSourceIds = result.successSourceIds as any;
|
||||
successSourceIds = result.successSourceIds;
|
||||
console.debug('[useToolOperation] Multi-file results', { outputFiles: processedFiles.length, successSources: result.successSourceIds.length });
|
||||
break;
|
||||
}
|
||||
@ -268,30 +287,40 @@ export const useToolOperation = <TParams>(
|
||||
processedFiles = await extractZipFiles(response.data);
|
||||
}
|
||||
// Assume all inputs succeeded together unless server provided an error earlier
|
||||
successSourceIds = validFiles.map(f => (f as any).fileId) as any;
|
||||
successSourceIds = validFiles.map(f => f.fileId);
|
||||
break;
|
||||
}
|
||||
|
||||
case ToolType.custom: {
|
||||
actions.setStatus('Processing files...');
|
||||
processedFiles = await config.customProcessor(params, filesForAPI);
|
||||
// Try to map outputs back to inputs by filename (before extension)
|
||||
const inputBaseNames = new Map<string, string>();
|
||||
for (const f of validFiles) {
|
||||
const base = (f.name || '').replace(/\.[^.]+$/, '').toLowerCase();
|
||||
inputBaseNames.set(base, (f as any).fileId);
|
||||
}
|
||||
const mappedSuccess: string[] = [];
|
||||
for (const out of processedFiles) {
|
||||
const base = (out.name || '').replace(/\.[^.]+$/, '').toLowerCase();
|
||||
const id = inputBaseNames.get(base);
|
||||
if (id) mappedSuccess.push(id);
|
||||
}
|
||||
// Fallback to naive alignment if names don't match
|
||||
if (mappedSuccess.length === 0) {
|
||||
successSourceIds = validFiles.slice(0, processedFiles.length).map(f => (f as any).fileId) as any;
|
||||
const result = await config.customProcessor(params, filesForAPI);
|
||||
|
||||
processedFiles = result.files;
|
||||
const consumedAllInputs = result.consumedAllInputs || false;
|
||||
|
||||
// If consumedAllInputs flag is set, mark all inputs as successful
|
||||
// (used for operations that combine N inputs into fewer outputs)
|
||||
if (consumedAllInputs) {
|
||||
successSourceIds = validFiles.map(f => f.fileId);
|
||||
} else {
|
||||
successSourceIds = mappedSuccess as any;
|
||||
// Try to map outputs back to inputs by filename (before extension)
|
||||
const inputBaseNames = new Map<string, FileId>();
|
||||
for (const f of validFiles) {
|
||||
const base = getFilenameWithoutExtension(f.name || '');
|
||||
inputBaseNames.set(base, f.fileId);
|
||||
}
|
||||
const mappedSuccess: FileId[] = [];
|
||||
for (const out of processedFiles) {
|
||||
const base = getFilenameWithoutExtension(out.name || '');
|
||||
const id = inputBaseNames.get(base);
|
||||
if (id) mappedSuccess.push(id);
|
||||
}
|
||||
// Fallback to naive alignment if names don't match
|
||||
if (mappedSuccess.length === 0) {
|
||||
successSourceIds = validFiles.slice(0, processedFiles.length).map(f => f.fileId);
|
||||
} else {
|
||||
successSourceIds = mappedSuccess;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -299,16 +328,16 @@ export const useToolOperation = <TParams>(
|
||||
|
||||
// Normalize error flags across tool types: mark failures, clear successes
|
||||
try {
|
||||
const allInputIds = validFiles.map(f => (f as any).fileId) as unknown as string[];
|
||||
const okSet = new Set((successSourceIds as unknown as string[]) || []);
|
||||
const allInputIds = validFiles.map(f => f.fileId);
|
||||
const okSet = new Set(successSourceIds);
|
||||
// Clear errors on successes
|
||||
for (const okId of okSet) {
|
||||
try { (fileActions.clearFileError as any)(okId); } catch (_e) { void _e; }
|
||||
try { fileActions.clearFileError(okId); } catch (_e) { void _e; }
|
||||
}
|
||||
// Mark errors on inputs that didn't succeed
|
||||
for (const id of allInputIds) {
|
||||
if (!okSet.has(id)) {
|
||||
try { (fileActions.markFileError as any)(id); } catch (_e) { void _e; }
|
||||
try { fileActions.markFileError(id); } catch (_e) { void _e; }
|
||||
}
|
||||
}
|
||||
} catch (_e) { void _e; }
|
||||
@ -316,12 +345,12 @@ export const useToolOperation = <TParams>(
|
||||
if (externalErrorFileIds.length > 0) {
|
||||
// If backend told us which sources failed, prefer that mapping
|
||||
successSourceIds = validFiles
|
||||
.map(f => (f as any).fileId)
|
||||
.filter(id => !externalErrorFileIds.includes(id)) as any;
|
||||
.map(f => f.fileId)
|
||||
.filter(id => !externalErrorFileIds.includes(id));
|
||||
// Also mark failed IDs immediately
|
||||
try {
|
||||
for (const badId of externalErrorFileIds) {
|
||||
(fileActions.markFileError as any)(badId);
|
||||
fileActions.markFileError(badId as FileId);
|
||||
}
|
||||
} catch (_e) { void _e; }
|
||||
}
|
||||
@ -370,7 +399,7 @@ export const useToolOperation = <TParams>(
|
||||
);
|
||||
// Always create child stubs linking back to the successful source inputs
|
||||
const successInputStubs = successSourceIds
|
||||
.map((id) => selectors.getStirlingFileStub(id as any))
|
||||
.map((id) => selectors.getStirlingFileStub(id))
|
||||
.filter(Boolean) as StirlingFileStub[];
|
||||
|
||||
if (successInputStubs.length !== processedFiles.length) {
|
||||
@ -396,7 +425,7 @@ export const useToolOperation = <TParams>(
|
||||
return createStirlingFile(file, childStub.id);
|
||||
});
|
||||
// Build consumption arrays aligned to the successful source IDs
|
||||
const toConsumeInputIds = successSourceIds.filter((id: string) => inputFileIds.includes(id as any)) as unknown as FileId[];
|
||||
const toConsumeInputIds = successSourceIds.filter((id) => inputFileIds.includes(id));
|
||||
// Outputs and stubs are already ordered by success sequence
|
||||
console.debug('[useToolOperation] Consuming files', { inputCount: inputFileIds.length, toConsume: toConsumeInputIds.length });
|
||||
const outputFileIds = await consumeFiles(toConsumeInputIds, outputStirlingFiles, outputStirlingFileStubs);
|
||||
@ -413,25 +442,27 @@ export const useToolOperation = <TParams>(
|
||||
} catch (error: any) {
|
||||
// Centralized 422 handler: mark provided IDs in errorFileIds
|
||||
try {
|
||||
const status = (error?.response?.status as number | undefined);
|
||||
if (status === 422) {
|
||||
const status = error?.response?.status;
|
||||
if (typeof status === 'number' && status === 422) {
|
||||
const payload = error?.response?.data;
|
||||
let parsed: any = payload;
|
||||
let parsed: unknown = payload;
|
||||
if (typeof payload === 'string') {
|
||||
try { parsed = JSON.parse(payload); } catch { parsed = payload; }
|
||||
} else if (payload && typeof (payload as any).text === 'function') {
|
||||
} else if (payload && typeof (payload as Blob).text === 'function') {
|
||||
// Blob or Response-like object from axios when responseType='blob'
|
||||
const text = await (payload as Blob).text();
|
||||
try { parsed = JSON.parse(text); } catch { parsed = text; }
|
||||
}
|
||||
let ids: string[] | undefined = Array.isArray(parsed?.errorFileIds) ? parsed.errorFileIds : undefined;
|
||||
let ids: string[] | undefined = Array.isArray((parsed as { errorFileIds?: unknown })?.errorFileIds)
|
||||
? (parsed as { errorFileIds: string[] }).errorFileIds
|
||||
: undefined;
|
||||
if (!ids && typeof parsed === 'string') {
|
||||
const match = parsed.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g);
|
||||
if (match && match.length > 0) ids = Array.from(new Set(match));
|
||||
}
|
||||
if (ids && ids.length > 0) {
|
||||
for (const badId of ids) {
|
||||
try { (fileActions.markFileError as any)(badId); } catch (_e) { void _e; }
|
||||
try { fileActions.markFileError(badId as FileId); } catch (_e) { void _e; }
|
||||
}
|
||||
actions.setStatus('Process failed due to invalid/corrupted file(s)');
|
||||
// Avoid duplicating toast messaging here
|
||||
|
||||
@ -158,8 +158,8 @@ export const executeToolOperationWithPrefix = async (
|
||||
try {
|
||||
// Check if tool uses custom processor (like Convert tool)
|
||||
if (config.customProcessor) {
|
||||
const resultFiles = await config.customProcessor(parameters, files);
|
||||
return resultFiles;
|
||||
const result = await config.customProcessor(parameters, files);
|
||||
return result.files;
|
||||
}
|
||||
|
||||
// Execute based on tool type
|
||||
|
||||
@ -60,6 +60,18 @@ export const isWebFormat = (extension: string): boolean => {
|
||||
return ['html', 'zip'].includes(extension.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given extension is an office format (Word, Excel, PowerPoint, OpenOffice)
|
||||
* These formats use LibreOffice for conversion and require individual file processing
|
||||
*/
|
||||
export const isOfficeFormat = (extension: string): boolean => {
|
||||
return [
|
||||
'docx', 'doc', 'odt', // Word processors
|
||||
'xlsx', 'xls', 'ods', // Spreadsheets
|
||||
'pptx', 'ppt', 'odp' // Presentations
|
||||
].includes(extension.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets available target extensions for a given source extension
|
||||
* Extracted from useConvertParameters to be reusable in automation settings
|
||||
|
||||
@ -52,6 +52,29 @@ export function detectFileExtension(filename: string): string {
|
||||
return extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file extension from a filename
|
||||
* @param filename - The filename to process
|
||||
* @param options - Options for processing
|
||||
* @param options.preserveCase - If true, preserves original case. If false (default), converts to lowercase
|
||||
* @returns Filename without extension
|
||||
* @example
|
||||
* getFilenameWithoutExtension('document.pdf') // 'document'
|
||||
* getFilenameWithoutExtension('my.file.name.txt') // 'my.file.name'
|
||||
* getFilenameWithoutExtension('REPORT.PDF', { preserveCase: true }) // 'REPORT'
|
||||
*/
|
||||
export function getFilenameWithoutExtension(
|
||||
filename: string,
|
||||
options: { preserveCase?: boolean } = {}
|
||||
): string {
|
||||
if (!filename || typeof filename !== 'string') return '';
|
||||
|
||||
const { preserveCase = false } = options;
|
||||
const withoutExtension = filename.replace(/\.[^.]+$/, '');
|
||||
|
||||
return preserveCase ? withoutExtension : withoutExtension.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file is a PDF based on extension and MIME type
|
||||
* @param file - File or file-like object with name and type properties
|
||||
|
||||
Loading…
Reference in New Issue
Block a user