diff --git a/config/config.js b/config/config.js index 78cf22c6..b1b0b211 100644 --- a/config/config.js +++ b/config/config.js @@ -242,7 +242,8 @@ const limitFunctions = { activateCorrespondents: parseEnvBoolean(process.env.ACTIVATE_CORRESPONDENTS, 'yes'), activateDocumentType: parseEnvBoolean(process.env.ACTIVATE_DOCUMENT_TYPE, 'yes'), activateTitle: parseEnvBoolean(process.env.ACTIVATE_TITLE, 'yes'), - activateCustomFields: parseEnvBoolean(process.env.ACTIVATE_CUSTOM_FIELDS, 'yes') + activateCustomFields: parseEnvBoolean(process.env.ACTIVATE_CUSTOM_FIELDS, 'yes'), + activateNotes: parseEnvBoolean(process.env.ACTIVATE_NOTES, 'yes') }; // Initialize AI restrictions with defaults @@ -371,7 +372,8 @@ module.exports = { activateCorrespondents: limitFunctions.activateCorrespondents, activateDocumentType: limitFunctions.activateDocumentType, activateTitle: limitFunctions.activateTitle, - activateCustomFields: limitFunctions.activateCustomFields + activateCustomFields: limitFunctions.activateCustomFields, + activateNotes: limitFunctions.activateNotes }, specialPromptPreDefinedTags: `You are a document analysis AI. You will analyze the document. You take the main information to associate tags with the document. @@ -398,6 +400,7 @@ module.exports = { "document_type": "Invoice/Contract/...", "document_date": "YYYY-MM-DD", "language": "en/de/es/...", + "notes": "Brief summary of the document: what it is about, key details, amounts, parties involved, deadlines, and any required actions (2-4 sentences)", %CUSTOMFIELDS% }`, }; diff --git a/server.js b/server.js index f339faa5..b2d0c4c2 100644 --- a/server.js +++ b/server.js @@ -754,6 +754,11 @@ async function buildUpdateData(analysis, doc) { } } + // Process notes (added as a note via API, not via PATCH) + if (config.limitFunctions?.activateNotes !== 'no' && analysis.document.notes) { + updateData._notesForPost = analysis.document.notes; + } + // Only process correspondent if correspondent detection is activated if (config.limitFunctions?.activateCorrespondents !== 'no' && analysis.document.correspondent) { try { @@ -789,11 +794,20 @@ async function saveDocumentChanges(docId, updateData, analysis, originalData) { await documentModel.saveOriginalData(docId, originalTags, originalCorrespondent, originalTitle, origDocType, origLanguage); + // Extract notes before removing from updateData + const noteText = updateData._notesForPost || null; + delete updateData._notesForPost; + const updatedDocument = await paperlessService.updateDocument(docId, updateData); if (!updatedDocument) { throw new Error(`Paperless update failed for document ${docId}`); } + // Add note via Paperless notes API (POST /api/documents/{id}/notes/) + if (noteText) { + await paperlessService.addDocumentNote(docId, noteText); + } + const persistenceTasks = [ documentModel.addProcessedDocument(docId, updateData.title), documentModel.addToHistory( diff --git a/services/ollamaService.js b/services/ollamaService.js index e81c9c07..541f9301 100644 --- a/services/ollamaService.js +++ b/services/ollamaService.js @@ -44,7 +44,8 @@ class OllamaService { custom_fields: { type: "object", additionalProperties: true - } + }, + notes: { type: "string" } }, required: ["title", "correspondent", "tags", "document_type", "document_date", "language"] }; @@ -461,6 +462,7 @@ class OllamaService { "document_type": "Invoice/Contract/...", "document_date": "YYYY-MM-DD", "language": "en/de/es/...", + "notes": "Brief summary of the document (2-3 sentences)", %CUSTOMFIELDS% } ALWAYS USE THE INFORMATION TO FILL OUT THE JSON OBJECT. DO NOT ASK BACK QUESTIONS. @@ -630,7 +632,8 @@ class OllamaService { document_date: responseData.response.document_date || null, document_type: responseData.response.document_type || null, language: responseData.response.language || null, - custom_fields: responseData.response.custom_fields || null + custom_fields: responseData.response.custom_fields || null, + notes: responseData.response.notes || null }; } else if (responseData.response) { // Fall back to parsing from text response @@ -685,7 +688,8 @@ class OllamaService { document_date: result.document_date || null, document_type: result.document_type || null, language: result.language || null, - custom_fields: result.custom_fields || null + custom_fields: result.custom_fields || null, + notes: result.notes || null }; } catch (jsonError) { @@ -702,7 +706,10 @@ class OllamaService { correspondent: sanitizedResult.correspondent || null, title: sanitizedResult.title || null, document_date: sanitizedResult.document_date || null, - language: sanitizedResult.language || null + document_type: sanitizedResult.document_type || null, + language: sanitizedResult.language || null, + custom_fields: sanitizedResult.custom_fields || null, + notes: sanitizedResult.notes || null }; } catch (finalError) { console.error('Final JSON parsing failed after sanitization. This happens when the JSON structure is too complex or invalid. That indicates an issue with the generated JSON string by Ollama. Switch to OpenAI for better results or fine tune your prompt.'); diff --git a/services/paperlessService.js b/services/paperlessService.js index 0b6b2e31..d7f1cb42 100644 --- a/services/paperlessService.js +++ b/services/paperlessService.js @@ -1705,6 +1705,26 @@ async getOrCreateDocumentType(name, options = {}) { } } + /** + * Add a note to a document via the Paperless notes API. + * Uses POST /api/documents/{id}/notes/ with {"note": "..."}. + * @param {number} documentId + * @param {string} noteText - The note content + * @returns {Promise} Created note or null on failure + */ + async addDocumentNote(documentId, noteText) { + this.initialize(); + if (!this.client || !noteText) return null; + try { + const response = await this.client.post(`/documents/${documentId}/notes/`, { note: noteText }); + console.log(`[SUCCESS] Added note to document ${documentId}`); + return response.data; + } catch (error) { + console.error(`[ERROR] Adding note to document ${documentId}: ${error.message}`); + return null; + } + } + /** * Restore a document to its original state (before AI processing). * Unlike updateDocument(), this method does NOT merge tags or skip correspondents —