This commit is contained in:
seb
2026-09-26 14:42:29 +02:00
parent f0176e0fe0
commit cbb2fdeb6d
4 changed files with 15 additions and 5 deletions

View File

@@ -4609,7 +4609,7 @@
"Delivery" "Delivery"
], ],
"summary": "Parse document (SSE)", "summary": "Parse document (SSE)",
"description": "Accepts an invoice or delivery note as PDF or text file\n(`multipart/form-data`, field `file`) and streams extraction results as\nServer-Sent Events until parsing is complete.\n\nPDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.\nPages are separated by the form feed character (`\\f`). If no form feed\nis present, the input is treated as a single page.\n\nThe extracted pages are fed to an LLM (OpenRouter) which fills a structured\nmanifest of header data and line items. The OpenRouter API key is read from\nthe authenticated user's company setting `keys.openrouter`; the endpoint\nreturns `400` if the key is not configured.\n\nThe stream ends after the final `result` event (or an `error` event on failure).\n", "description": "Accepts an invoice or delivery note as PDF or text file\n(`multipart/form-data`, field `file`) and streams extraction results as\nServer-Sent Events until parsing is complete.\n\nPDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.\nPages are separated by the form feed character (`\\f`). If no form feed\nis present, the input is treated as a single page.\n\nThe extracted pages are fed to an LLM (OpenRouter) which fills a structured\nmanifest of header data and line items. The OpenRouter API key is read from\nthe authenticated user's company setting `keys.openrouter` (or the server's\n`OPENROUTER_FREE_KEY` if not configured); the endpoint\nreturns `400` if the key is not configured.\n\nThe stream ends after the final `result` event (or an `error` event on failure).\n",
"security": [ "security": [
{ {
"cookieAuth": [] "cookieAuth": []

View File

@@ -14,7 +14,8 @@ is present, the input is treated as a single page.
The extracted pages are fed to an LLM (OpenRouter) which fills a structured The extracted pages are fed to an LLM (OpenRouter) which fills a structured
manifest of header data and line items. The OpenRouter API key is read from manifest of header data and line items. The OpenRouter API key is read from
the authenticated user's company setting `keys.openrouter`; the endpoint the authenticated user's company setting `keys.openrouter` (or the server's
`OPENROUTER_FREE_KEY` if not configured); the endpoint
returns `400` if the key is not configured. returns `400` if the key is not configured.
The stream ends after the final `result` event (or an `error` event on failure). The stream ends after the final `result` event (or an `error` event on failure).

View File

@@ -5,6 +5,7 @@ import multer from 'multer';
import { LLMOrbiter, OpenRouterProvider, defineTool, DirectReturnError } from 'libllmorbiter'; import { LLMOrbiter, OpenRouterProvider, defineTool, DirectReturnError } from 'libllmorbiter';
import * as dbCompanySetting from '../../db/companysetting.js'; import * as dbCompanySetting from '../../db/companysetting.js';
import * as dbSupplier from '../../db/supplier.js'; import * as dbSupplier from '../../db/supplier.js';
import env from '../../config/env.js';
// ─── Multer — memory storage (no temp files); accepts PDF or text ─────────────── // ─── Multer — memory storage (no temp files); accepts PDF or text ───────────────
const upload = multer({ const upload = multer({
@@ -113,14 +114,20 @@ ${supplierList}`;
* @param {string} opts.sourceLabel e.g. 'PDF' or 'Text' * @param {string} opts.sourceLabel e.g. 'PDF' or 'Text'
*/ */
async function parseDocumentManifest({ req, res, pages, sourceLabel }) { async function parseDocumentManifest({ req, res, pages, sourceLabel }) {
// ── Fetch OpenRouter API key from company settings ──────────────── // ── Fetch OpenRouter API key from company settings (or fallback) ──
let apiKey; let apiKey;
try { try {
const setting = await dbCompanySetting.getByKey(req.user.id, 'keys.openrouter', { maskWriteonly: false }); const setting = await dbCompanySetting.getByKey(req.user.id, 'keys.openrouter', { maskWriteonly: false });
apiKey = setting?.value; apiKey = setting?.value?.trim();
} catch (err) { } catch (err) {
console.error('delivery parse: failed to read OpenRouter key:', err); console.error('delivery parse: failed to read OpenRouter key:', err);
} }
if (!apiKey) {
apiKey = env.openrouterFreeKey?.trim();
if (apiKey) {
console.log('delivery parse: using fallback OPENROUTER_FREE_KEY');
}
}
if (!apiKey) { if (!apiKey) {
return res.status(400).json({ error: 'OpenRouter API key not configured (companysetting: keys.openrouter)' }); return res.status(400).json({ error: 'OpenRouter API key not configured (companysetting: keys.openrouter)' });
} }
@@ -447,7 +454,8 @@ export function register(app, basePath = '') {
* *
* The extracted pages are fed to an LLM (OpenRouter) which fills a structured * The extracted pages are fed to an LLM (OpenRouter) which fills a structured
* manifest of header data and line items. The OpenRouter API key is read from * manifest of header data and line items. The OpenRouter API key is read from
* the authenticated user's company setting `keys.openrouter`; the endpoint * the authenticated user's company setting `keys.openrouter` (or the server's
* `OPENROUTER_FREE_KEY` if not configured); the endpoint
* returns `400` if the key is not configured. * returns `400` if the key is not configured.
* *
* The stream ends after the final `result` event (or an `error` event on failure). * The stream ends after the final `result` event (or an `error` event on failure).

View File

@@ -16,6 +16,7 @@ export default {
implicitCreate: process.env.IMPLICIT_CREATE === 'true', implicitCreate: process.env.IMPLICIT_CREATE === 'true',
jwtSecret: process.env.JWT_SECRET || 'fallback_secret_please_change_in_production', jwtSecret: process.env.JWT_SECRET || 'fallback_secret_please_change_in_production',
dbProvider: process.env.DB_PROVIDER || (process.env.SQLITE_PATH ? 'sqlite' : 'mssql'), dbProvider: process.env.DB_PROVIDER || (process.env.SQLITE_PATH ? 'sqlite' : 'mssql'),
openrouterFreeKey: process.env.OPENROUTER_FREE_KEY,
sqlite: { sqlite: {
filename: process.env.SQLITE_PATH || process.env.SQLITE_FILENAME || resolve(__dirname, '../../data/dash.db'), filename: process.env.SQLITE_PATH || process.env.SQLITE_FILENAME || resolve(__dirname, '../../data/dash.db'),
}, },