diff --git a/.dockerignore b/.dockerignore index 4ec2eb9..f3dc64b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,4 +19,7 @@ docker-compose-dev.yml public/images/ delete_all.js documents.json -apitest.js \ No newline at end of file +apitest.js +logs/* +api_correspondent.js +prompt.txt \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ff2f5c..e6f6628 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ docker-compose-dev.yml public/images/ delete_all.js documents.json -apitest.js \ No newline at end of file +apitest.js +logs/* +api_correspondent.js +prompt.txt \ No newline at end of file diff --git a/config/config.js b/config/config.js index d9e7b2f..ff44c52 100644 --- a/config/config.js +++ b/config/config.js @@ -11,7 +11,7 @@ console.log('Loaded environment variables:', { }); module.exports = { - PAPERLESS_AI_VERSION: '2.1.6', + PAPERLESS_AI_VERSION: '2.1.7', CONFIGURED: false, predefinedMode: process.env.PROCESS_PREDEFINED_DOCUMENTS, paperless: { diff --git a/routes/setup.js b/routes/setup.js index 8cb5883..099d9cc 100644 --- a/routes/setup.js +++ b/routes/setup.js @@ -512,8 +512,6 @@ router.get('/dashboard', async (req, res) => { const averageTotalTokens = metrics.length > 0 ? Math.round(metrics.reduce((acc, cur) => acc + cur.totalTokens, 0) / metrics.length) : 0; const tokensOverall = metrics.length > 0 ? metrics.reduce((acc, cur) => acc + cur.totalTokens, 0) : 0; const version = configFile.PAPERLESS_AI_VERSION || ' '; - console.log(tagCount); - console.log(correspondentCount); res.render('dashboard', { paperless_data: { tagCount, correspondentCount, documentCount, processedDocumentCount }, openai_data: { averagePromptTokens, averageCompletionTokens, averageTotalTokens, tokensOverall }, version }); }); @@ -611,14 +609,15 @@ router.get('/debug/correspondents', async (req, res) => { router.post('/manual/analyze', express.json(), async (req, res) => { try { const { content, existingTags, id } = req.body; - + let existingCorrespondentList = await paperlessService.listCorrespondentsNames(); + existingCorrespondentList = existingCorrespondentList.map(correspondent => correspondent.name); if (!content || typeof content !== 'string') { console.log('Invalid content received:', content); return res.status(400).json({ error: 'Valid content string is required' }); } if (process.env.AI_PROVIDER === 'openai') { - const analyzeDocument = await openaiService.analyzeDocument(content, existingTags, id || []); + const analyzeDocument = await openaiService.analyzeDocument(content, existingTags, existingCorrespondentList, id || []); await documentModel.addOpenAIMetrics( id, analyzeDocument.metrics.promptTokens, @@ -853,100 +852,131 @@ router.post('/setup', express.json(), async (req, res) => { router.post('/settings', express.json(), async (req, res) => { try { - const { - paperlessUrl, - paperlessToken, - aiProvider, - openaiKey, - openaiModel, - ollamaUrl, - ollamaModel, - scanInterval, - systemPrompt, - showTags, - tags, - aiProcessedTag, - aiTagName, - usePromptTags, - promptTags, - paperlessUsername - } = req.body; - - const normalizeArray = (value) => { - if (!value) return []; - if (Array.isArray(value)) return value; - if (typeof value === 'string') return value.split(',').filter(Boolean).map(item => item.trim()); - return []; - }; + const { + paperlessUrl, + paperlessToken, + aiProvider, + openaiKey, + openaiModel, + ollamaUrl, + ollamaModel, + scanInterval, + systemPrompt, + showTags, + tags, + aiProcessedTag, + aiTagName, + usePromptTags, + promptTags, + paperlessUsername + } = req.body; + + const currentConfig = { + PAPERLESS_API_URL: process.env.PAPERLESS_API_URL || '', + PAPERLESS_API_TOKEN: process.env.PAPERLESS_API_TOKEN || '', + PAPERLESS_USERNAME: process.env.PAPERLESS_USERNAME || '', + AI_PROVIDER: process.env.AI_PROVIDER || '', + OPENAI_API_KEY: process.env.OPENAI_API_KEY || '', + OPENAI_MODEL: process.env.OPENAI_MODEL || '', + OLLAMA_API_URL: process.env.OLLAMA_API_URL || '', + OLLAMA_MODEL: process.env.OLLAMA_MODEL || '', + SCAN_INTERVAL: process.env.SCAN_INTERVAL || '*/30 * * * *', + SYSTEM_PROMPT: process.env.SYSTEM_PROMPT || '', + PROCESS_PREDEFINED_DOCUMENTS: process.env.PROCESS_PREDEFINED_DOCUMENTS || 'no', + TAGS: process.env.TAGS || '', + ADD_AI_PROCESSED_TAG: process.env.ADD_AI_PROCESSED_TAG || 'no', + AI_PROCESSED_TAG_NAME: process.env.AI_PROCESSED_TAG_NAME || 'ai-processed', + USE_PROMPT_TAGS: process.env.USE_PROMPT_TAGS || 'no', + PROMPT_TAGS: process.env.PROMPT_TAGS || '' + }; - const processedPrompt = systemPrompt - ? systemPrompt.replace(/\r\n/g, '\n').replace(/\n/g, '\\n') - : ''; + const normalizeArray = (value) => { + if (!value) return []; + if (Array.isArray(value)) return value; + if (typeof value === 'string') return value.split(',').filter(Boolean).map(item => item.trim()); + return []; + }; - // Validate Paperless config + if (paperlessUrl !== currentConfig.PAPERLESS_API_URL?.replace('/api', '') || + paperlessToken !== currentConfig.PAPERLESS_API_TOKEN) { const isPaperlessValid = await setupService.validatePaperlessConfig(paperlessUrl, paperlessToken); if (!isPaperlessValid) { - return res.status(400).json({ - error: 'Paperless-ngx connection failed. Please check URL and Token.' - }); + return res.status(400).json({ + error: 'Paperless-ngx connection failed. Please check URL and Token.' + }); } + } - // Prepare base config - const config = { - PAPERLESS_API_URL: paperlessUrl + '/api', - PAPERLESS_API_TOKEN: paperlessToken, - PAPERLESS_USERNAME: paperlessUsername, - AI_PROVIDER: aiProvider, - SCAN_INTERVAL: scanInterval || '*/30 * * * *', - SYSTEM_PROMPT: processedPrompt, - PROCESS_PREDEFINED_DOCUMENTS: showTags || 'no', - TAGS: normalizeArray(tags), - ADD_AI_PROCESSED_TAG: aiProcessedTag || 'no', - AI_PROCESSED_TAG_NAME: aiTagName || 'ai-processed', - USE_PROMPT_TAGS: usePromptTags || 'no', - PROMPT_TAGS: normalizeArray(promptTags) - }; + const updatedConfig = {}; - // Validate AI provider config - if (aiProvider === 'openai') { - const isOpenAIValid = await setupService.validateOpenAIConfig(openaiKey); - if (!isOpenAIValid) { - return res.status(400).json({ - error: 'OpenAI API Key is not valid. Please check the key.' - }); - } - config.OPENAI_API_KEY = openaiKey; - config.OPENAI_MODEL = openaiModel || 'gpt-4o-mini'; - } else if (aiProvider === 'ollama') { - const isOllamaValid = await setupService.validateOllamaConfig(ollamaUrl, ollamaModel); - if (!isOllamaValid) { - return res.status(400).json({ - error: 'Ollama connection failed. Please check URL and Model.' - }); - } - config.OLLAMA_API_URL = ollamaUrl || 'http://localhost:11434'; - config.OLLAMA_MODEL = ollamaModel || 'llama3.2'; + if (paperlessUrl) updatedConfig.PAPERLESS_API_URL = paperlessUrl + '/api'; + if (paperlessToken) updatedConfig.PAPERLESS_API_TOKEN = paperlessToken; + if (paperlessUsername) updatedConfig.PAPERLESS_USERNAME = paperlessUsername; + + if (aiProvider) { + updatedConfig.AI_PROVIDER = aiProvider; + + if (aiProvider === 'openai' && openaiKey) { + const isOpenAIValid = await setupService.validateOpenAIConfig(openaiKey); + if (!isOpenAIValid) { + return res.status(400).json({ + error: 'OpenAI API Key is not valid. Please check the key.' + }); + } + updatedConfig.OPENAI_API_KEY = openaiKey; + if (openaiModel) updatedConfig.OPENAI_MODEL = openaiModel; + } + else if (aiProvider === 'ollama' && (ollamaUrl || ollamaModel)) { + const isOllamaValid = await setupService.validateOllamaConfig( + ollamaUrl || currentConfig.OLLAMA_API_URL, + ollamaModel || currentConfig.OLLAMA_MODEL + ); + if (!isOllamaValid) { + return res.status(400).json({ + error: 'Ollama connection failed. Please check URL and Model.' + }); + } + if (ollamaUrl) updatedConfig.OLLAMA_API_URL = ollamaUrl; + if (ollamaModel) updatedConfig.OLLAMA_MODEL = ollamaModel; } + } - // Save configuration - await setupService.saveConfig(config); - // Send success response - res.json({ - success: true, - message: 'Configuration saved successfully.', - restart: true - }); + if (scanInterval) updatedConfig.SCAN_INTERVAL = scanInterval; + if (systemPrompt) updatedConfig.SYSTEM_PROMPT = systemPrompt.replace(/\r\n/g, '\n').replace(/\n/g, '\\n'); + if (showTags) updatedConfig.PROCESS_PREDEFINED_DOCUMENTS = showTags; + if(tags !== undefined || tags !== null || tags !== ''){ + updatedConfig.TAGS = normalizeArray(tags); + }else{ + updatedConfig.TAGS = ''; + } + if (aiProcessedTag) updatedConfig.ADD_AI_PROCESSED_TAG = aiProcessedTag; + if (aiTagName) updatedConfig.AI_PROCESSED_TAG_NAME = aiTagName; + if (usePromptTags) updatedConfig.USE_PROMPT_TAGS = usePromptTags; + if (promptTags) updatedConfig.PROMPT_TAGS = normalizeArray(promptTags); + + const mergedConfig = { + ...currentConfig, + ...updatedConfig + }; - // Trigger application restart - setTimeout(() => { - process.exit(0); - }, 5000); + await setupService.saveConfig(mergedConfig); + + res.json({ + success: true, + message: 'Configuration saved successfully.', + restart: true + }); + + // Trigger application restart + setTimeout(() => { + process.exit(0); + }, 5000); } catch (error) { - console.error('Setup error:', error); - res.status(500).json({ - error: 'An error occurred: ' + error.message - }); + console.error('Setup error:', error); + res.status(500).json({ + error: 'An error occurred: ' + error.message + }); } }); diff --git a/server.js b/server.js index d32501c..6c0bde4 100644 --- a/server.js +++ b/server.js @@ -10,6 +10,22 @@ const setupService = require('./services/setupService'); const setupRoutes = require('./routes/setup'); const cors = require('cors'); const cookieParser = require('cookie-parser'); +const Logger = require('./services/loggerService'); +const { max } = require('date-fns'); + +const htmlLogger = new Logger({ + logFile: 'logs.html', + format: 'html', + timestamp: true, + maxFileSize: 1024 * 1024 * 10 +}); + +const txtLogger = new Logger({ + logFile: 'logs.txt', + format: 'txt', + timestamp: true, + maxFileSize: 1024 * 1024 * 10 +}); const app = express(); let runningTask = false; @@ -49,7 +65,7 @@ async function initializeDataDirectory() { } // Document processing functions -async function processDocument(doc, existingTags, ownUserId) { +async function processDocument(doc, existingTags, existingCorrespondentList, ownUserId) { const isProcessed = await documentModel.isDocumentProcessed(doc.id); if (isProcessed) return null; @@ -75,7 +91,7 @@ async function processDocument(doc, existingTags, ownUserId) { } const aiService = AIServiceFactory.getService(); - const analysis = await aiService.analyzeDocument(content, existingTags, doc.id); + const analysis = await aiService.analyzeDocument(content, existingTags, existingCorrespondentList, doc.id); if (analysis.error) { throw new Error(`[ERROR] Document analysis failed: ${analysis.error}`); @@ -140,15 +156,18 @@ async function scanInitial() { return; } - const [existingTags, documents, ownUserId] = await Promise.all([ + let [existingTags, documents, ownUserId, existingCorrespondentList] = await Promise.all([ paperlessService.getTags(), paperlessService.getAllDocuments(), - paperlessService.getOwnUserID() + paperlessService.getOwnUserID(), + paperlessService.listCorrespondentsNames() ]); + //get existing correspondent list + existingCorrespondentList = existingCorrespondentList.map(correspondent => correspondent.name); for (const doc of documents) { try { - const result = await processDocument(doc, existingTags, ownUserId); + const result = await processDocument(doc, existingTags, existingCorrespondentList, ownUserId); if (!result) continue; const { analysis, originalData } = result; @@ -171,15 +190,19 @@ async function scanDocuments() { runningTask = true; try { - const [existingTags, documents, ownUserId] = await Promise.all([ + let [existingTags, documents, ownUserId, existingCorrespondentList] = await Promise.all([ paperlessService.getTags(), paperlessService.getAllDocuments(), - paperlessService.getOwnUserID() + paperlessService.getOwnUserID(), + paperlessService.listCorrespondentsNames() ]); + //get existing correspondent list + existingCorrespondentList = existingCorrespondentList.map(correspondent => correspondent.name); + for (const doc of documents) { try { - const result = await processDocument(doc, existingTags, ownUserId); + const result = await processDocument(doc, existingTags, existingCorrespondentList, ownUserId); if (!result) continue; const { analysis, originalData } = result; diff --git a/services/loggerService.js b/services/loggerService.js new file mode 100644 index 0000000..815a3b4 --- /dev/null +++ b/services/loggerService.js @@ -0,0 +1,241 @@ +const fs = require('fs'); +const util = require('util'); +const path = require('path'); + +class Logger { + constructor(options = {}) { + this.logFile = options.logFile || 'application.log'; + this.logDir = options.logDir || 'logs'; + this.timestamp = options.timestamp !== false; + this.format = options.format || 'txt'; + this.maxFileSize = options.maxFileSize || 1024 * 1024 * 10; // Standard: 10MB + + if (!fs.existsSync(this.logDir)) { + fs.mkdirSync(this.logDir, { recursive: true }); + } + + this.logPath = path.join(this.logDir, this.logFile); + + // Initialisiere Log-Datei + this.initLogFile(); + + this.originalConsole = { + log: console.log, + error: console.error, + warn: console.warn, + info: console.info, + debug: console.debug + }; + + this.overrideConsoleMethods(); + } + + initLogFile() { + // Prüfe ob die Datei die maximale Größe überschreitet + if (this.checkFileSize()) { + // Lösche die alte Datei + try { + fs.unlinkSync(this.logPath); + } catch (error) { + // Ignoriere Fehler wenn Datei nicht existiert + } + } + + // Initialisiere HTML-Datei wenn nötig + if (this.format === 'html') { + this.initHtmlFile(); + } + } + + checkFileSize() { + if (fs.existsSync(this.logPath)) { + const stats = fs.statSync(this.logPath); + return stats.size >= this.maxFileSize; + } + return false; + } + + initHtmlFile() { + const htmlHeader = ` + + + + + Application Logs + + + + +
+`; + + if (!fs.existsSync(this.logPath) || fs.statSync(this.logPath).size === 0) { + fs.writeFileSync(this.logPath, htmlHeader); + } + } + + getTimestamp() { + return new Date().toISOString(); + } + + formatLogMessage(type, args) { + const msg = util.format(...args); + if (this.format === 'html') { + const timestamp = this.timestamp ? + `[${this.getTimestamp()}]` : ''; + return `
+ ${timestamp} + [${type.toUpperCase()}] + ${this.escapeHtml(msg)} +
\n`; + } else { + return this.timestamp ? + `[${this.getTimestamp()}] [${type.toUpperCase()}] ${msg}\n` : + `[${type.toUpperCase()}] ${msg}\n`; + } + } + + escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/\n/g, "
") + .replace(/\s/g, " "); + } + + writeToFile(message) { + // Prüfe Dateigröße vor dem Schreiben + if (this.checkFileSize()) { + // Lösche die alte Datei + fs.unlinkSync(this.logPath); + + // Bei HTML-Format müssen wir den Header neu schreiben + if (this.format === 'html') { + this.initHtmlFile(); + } + } + + fs.appendFileSync(this.logPath, message); + } + + overrideConsoleMethods() { + console.log = (...args) => { + const logMessage = this.formatLogMessage('info', args); + this.originalConsole.log(...args); + this.writeToFile(logMessage); + }; + + console.error = (...args) => { + const logMessage = this.formatLogMessage('error', args); + this.originalConsole.error(...args); + this.writeToFile(logMessage); + }; + + console.warn = (...args) => { + const logMessage = this.formatLogMessage('warn', args); + this.originalConsole.warn(...args); + this.writeToFile(logMessage); + }; + + console.info = (...args) => { + const logMessage = this.formatLogMessage('info', args); + this.originalConsole.info(...args); + this.writeToFile(logMessage); + }; + + console.debug = (...args) => { + const logMessage = this.formatLogMessage('debug', args); + this.originalConsole.debug(...args); + this.writeToFile(logMessage); + }; + } + + closeHtmlFile() { + if (this.format === 'html') { + const htmlFooter = `
+ + +`; + this.writeToFile(htmlFooter); + } + } + + restore() { + Object.assign(console, this.originalConsole); + if (this.format === 'html') { + this.closeHtmlFile(); + } + } +} + +module.exports = Logger; \ No newline at end of file diff --git a/services/openaiService.js b/services/openaiService.js index f930283..cd879ab 100644 --- a/services/openaiService.js +++ b/services/openaiService.js @@ -65,7 +65,7 @@ class OpenAIService { return text.slice(0, Math.floor(text.length * ratio)); } - async analyzeDocument(content, existingTags = [], id) { + async analyzeDocument(content, existingTags = [], existingCorrespondentList = [], id) { const cachePath = path.join('./public/images', `${id}.png`); try { this.initialize(); @@ -99,13 +99,18 @@ class OpenAIService { .join(', '); // Get system prompt and model - let systemPrompt = process.env.SYSTEM_PROMPT; + let systemPrompt = ` + Prexisting tags: ${existingTagsList}\n\n + Prexisiting correspondent: ${existingCorrespondentList}\n\n + ` + process.env.SYSTEM_PROMPT; const model = process.env.OPENAI_MODEL; let promptTags = ''; if (process.env.USE_PROMPT_TAGS === 'yes') { promptTags = process.env.PROMPT_TAGS; - systemPrompt = config.specialPromptPreDefinedTags; + systemPrompt = ` + Take these tags and try to match one or more to the document content.\n\n + ` + config.specialPromptPreDefinedTags; } // Calculate total prompt tokens including all components @@ -122,6 +127,8 @@ class OpenAIService { // Truncate content if necessary const truncatedContent = await this.truncateToTokenLimit(content, availableTokens); + // write complete Prompt to file for debugging + await this.writePromptToFile(systemPrompt, truncatedContent); // Make API request const response = await this.client.chat.completions.create({ model: model, @@ -185,6 +192,28 @@ class OpenAIService { } } + async writePromptToFile(systemPrompt, truncatedContent) { + const filePath = './logs/prompt.txt'; + const maxSize = 10 * 1024 * 1024; + + try { + const stats = await fs.stat(filePath); + if (stats.size > maxSize) { + await fs.unlink(filePath); // Delete the file if is biger 10MB + } + } catch (error) { + if (error.code !== 'ENOENT') { + console.warn('[WARNING] Error checking file size:', error); + } + } + + try { + await fs.appendFile(filePath, systemPrompt + truncatedContent + '\n\n'); + } catch (error) { + console.error('[ERROR] Error writing to file:', error); + } + } + async analyzePlayground(content, prompt) { const musthavePrompt = ` Return the result EXCLUSIVELY as a JSON object. The Tags and Title MUST be in the language that is used in the document.: diff --git a/services/paperlessService.js b/services/paperlessService.js index ef4e8e6..f163d2b 100644 --- a/services/paperlessService.js +++ b/services/paperlessService.js @@ -349,7 +349,6 @@ class PaperlessService { } } - console.log(allCorrespondents); return allCorrespondents; } catch (error) { @@ -708,40 +707,40 @@ class PaperlessService { } } - async searchForExistingCorrespondent(correspondent) { - try { - const response = await this.client.get('/correspondents/', { - params: { - name__icontains: correspondent - } - }); +async searchForExistingCorrespondent(correspondent) { + try { + const response = await this.client.get('/correspondents/', { + params: { + name__icontains: correspondent + } + }); - const results = response.data.results; - - if (results.length === 0) { - console.log(`[DEBUG] No correspondent with name "${correspondent}" found`); - return null; - } - - if (results.length > 1) { - console.log(`[DEBUG] Multiple correspondents found:`); - results.forEach(c => { - console.log(`- ID: ${c.id}, Name: ${c.name}`); - }); - return results; - } + const results = response.data.results; + + if (results.length === 0) { + console.log(`[DEBUG] No correspondent with name "${correspondent}" found`); + return null; + } + + // Check for exact match in the results - thanks to @skius for the hint! + const exactMatch = results.find(c => c.name.toLowerCase() === correspondent.toLowerCase()); + if (exactMatch) { + console.log(`[DEBUG] Found exact match for correspondent "${correspondent}" with ID ${exactMatch.id}`); + return { + id: exactMatch.id, + name: exactMatch.name + }; + } - // Genau ein Ergebnis gefunden - return { - id: results[0].id, - name: results[0].name - }; + // No exact match found, return null + console.log(`[DEBUG] No exact match found for "${correspondent}"`); + return null; - } catch (error) { - console.error('[ERROR] while seraching for existing correspondent:', error.message); - throw error; - } + } catch (error) { + console.error('[ERROR] while searching for existing correspondent:', error.message); + throw error; } +} async getOrCreateCorrespondent(name) { this.initialize(); @@ -879,24 +878,20 @@ class PaperlessService { this.initialize(); if (!this.client) return; try { - // Hole aktuelles Dokument mit existierenden Tags const currentDoc = await this.getDocument(documentId); - // Wenn das Update Tags enthält, füge sie zu den existierenden hinzu if (updates.tags) { console.log(`[DEBUG] Current tags for document ${documentId}:`, currentDoc.tags); console.log(`[DEBUG] Adding new tags:`, updates.tags); console.log(`[DEBUG] Current correspondent:`, currentDoc.correspondent); console.log(`[DEBUG] New correspondent:`, updates.correspondent); - // Kombiniere existierende und neue Tags const combinedTags = [...new Set([...currentDoc.tags, ...updates.tags])]; updates.tags = combinedTags; console.log(`[DEBUG] Combined tags:`, combinedTags); } - // Entferne den correspondent aus updates wenn bereits einer existiert if (currentDoc.correspondent && updates.correspondent) { console.log('[DEBUG] Document already has a correspondent, keeping existing one:', currentDoc.correspondent); delete updates.correspondent; @@ -907,17 +902,18 @@ class PaperlessService { if (updates.created) { let dateObject; - // First try to parse as ISO dateObject = parseISO(updates.created); - // If not valid, try German format if (!isValid(dateObject)) { dateObject = parse(updates.created, 'dd.MM.yyyy', new Date()); + if (!isValid(dateObject)) { + dateObject = parse(updates.created, 'dd-MM-yyyy', new Date()); + } } - // Check if we got a valid date if (!isValid(dateObject)) { - throw new Error(`Invalid date format: ${updates.created}`); + console.warn(`[WARN] Invalid date format: ${updates.created}, using fallback date: 01.01.1990`); + dateObject = new Date(1990, 0, 1); } updateData = { @@ -928,18 +924,20 @@ class PaperlessService { updateData = { ...updates }; } } catch (error) { - console.error(`[ERROR] updating document ${documentId}:`, error.message); - console.error('[DEBUG-ERROR] Received Date:', updates); - return; + console.warn('[WARN] Error parsing date:', error.message); + console.warn('[DEBUG] Received Date:', updates); + updateData = { + ...updates, + created: new Date(1990, 0, 1).toISOString() + }; } - // Update the document with the new data await this.client.patch(`/documents/${documentId}/`, updateData); console.log(`[SUCCESS] Updated document ${documentId} with:`, updateData); - return await this.getDocument(documentId); // Optional: Give back the updated document + return await this.getDocument(documentId); } catch (error) { console.error(`[ERROR] updating document ${documentId}:`, error.message); - return null; // Oder eine andere geeignete Rückgabe + return null; } } } diff --git a/views/manual.ejs b/views/manual.ejs index 8845e14..671bb2a 100644 --- a/views/manual.ejs +++ b/views/manual.ejs @@ -139,6 +139,7 @@

AI Suggestions

+
@@ -475,14 +476,17 @@ async function handleAnalysis() { } } -// Document Update Functions async function updateDocumentTags() { if (!currentDocument || isUpdating) return; isUpdating = true; try { - const titleValue = document.getElementById('titleName').textContent; - const tagIds = document.getElementById('tags').value.split(',').filter(id => id); + const titleElement = document.getElementById('titleName'); + const tagsElement = document.getElementById('tags'); + + // Add null checks + const titleValue = titleElement ? titleElement.textContent : ''; + const tagIds = tagsElement && tagsElement.value ? tagsElement.value.split(',').filter(id => id) : []; const response = await fetch('/manual/updateDocument', { method: 'POST', @@ -502,7 +506,7 @@ async function updateDocumentTags() { throw new Error(errorData.error || 'Failed to update document'); } - await response.json(); + const result = await response.json(); showMessage('Tags updated successfully', 'success'); } catch (error) { showMessage('Error updating tags: ' + error.message);