Einstellungen-Seite: .env-Werte in DB (app_state), keine .env mehr

Neue /einstellungen-Seite (Zahnrad im Header) mit allen bisherigen .env-Werten
(Ollama, E-Mail, CalDAV, REST-API), gespeichert in SQLite (app_state, cfg:-Prefix).
Libs lesen per lib/config.js zur Laufzeit statt beim Start -> Aenderungen
wirken sofort, kein Neustart. Bestehende .env wird beim ersten Start einmalig
migriert.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-13 20:57:31 +02:00
co-authored by Claude
parent 41887dd56c
commit 97b48f9841
12 changed files with 410 additions and 72 deletions
+16 -18
View File
@@ -11,6 +11,7 @@ const fs = require('fs');
const path = require('path');
const promptStore = require('./prompts');
const designStore = require('./design');
const config = require('./config');
// Embedded typefaces (all SIL Open Font License — see lib/fonts/OFL*.txt):
// Lato — the classic layout's workhorse
@@ -47,10 +48,8 @@ function makeDoc(t) {
return doc;
}
// Ollama Cloud API (https://ollama.com). Override host/model via env if needed.
const OLLAMA_HOST = (process.env.OLLAMA_HOST || 'https://ollama.com').replace(/\/+$/, '');
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'gpt-oss:120b';
const OLLAMA_TIMEOUT_MS = Number(process.env.OLLAMA_TIMEOUT_MS || 300000);
// Ollama Cloud API (https://ollama.com). Host/model/timeout come from the DB
// (editable via /einstellungen) and are read fresh per call via config.ollama().
// ===========================================================================
// 1. LLM call — produce tailored TEXT for the template
@@ -177,12 +176,11 @@ function buildOutputSchema(dokumente) {
async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen = '', zusatzAnlagen = [], prompts = null, dokumente = null }) {
const doks = normalizeDokumente(dokumente);
const apiKey = process.env.OLLAMA_API_KEY;
const { host: ollamaHost, model: ollamaModel, timeoutMs: ollamaTimeoutMs, apiKey } = config.ollama();
if (!apiKey) {
throw new Error(
'OLLAMA_API_KEY ist nicht gesetzt. Bitte den API-Schlüssel als ' +
'Umgebungsvariable (z. B. in einer .env-Datei) hinterlegen, damit ' +
'Bewerbungsunterlagen generiert werden können.'
'Kein Ollama-API-Schlüssel konfiguriert. Bitte unter „Einstellungen“ den ' +
'API-Schlüssel eintragen, damit Bewerbungsunterlagen generiert werden können.'
);
}
if (!basisDokumente || basisDokumente.length === 0) {
@@ -428,15 +426,15 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen
`JSON-Objekt, ohne Markdown, ohne Code-Fences, ohne weiteren Text.`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), OLLAMA_TIMEOUT_MS);
const timeout = setTimeout(() => controller.abort(), ollamaTimeoutMs);
let res;
try {
res = await fetch(`${OLLAMA_HOST}/api/chat`, {
res = await fetch(`${ollamaHost}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: OLLAMA_MODEL,
model: ollamaModel,
stream: false,
format: buildOutputSchema(doks),
options: { temperature: 0.4 },
@@ -449,7 +447,7 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen
});
} catch (err) {
if (err.name === 'AbortError') {
throw new Error(`Zeitüberschreitung bei der KI-Anfrage (> ${Math.round(OLLAMA_TIMEOUT_MS / 1000)}s).`);
throw new Error(`Zeitüberschreitung bei der KI-Anfrage (> ${Math.round(ollamaTimeoutMs / 1000)}s).`);
}
throw new Error(`Verbindung zur Ollama-API fehlgeschlagen: ${err.message}`);
} finally {
@@ -1942,17 +1940,17 @@ async function generateApplicationDocuments({ job, basisDokumente, settings, zus
// Small shared Ollama JSON call (used by the reply drafter).
async function ollamaChatJSON({ system, user, schema, temperature = 0.5 }) {
const apiKey = process.env.OLLAMA_API_KEY;
if (!apiKey) throw new Error('OLLAMA_API_KEY ist nicht gesetzt.');
const { host: ollamaHost, model: ollamaModel, timeoutMs: ollamaTimeoutMs, apiKey } = config.ollama();
if (!apiKey) throw new Error('Kein Ollama-API-Schlüssel konfiguriert (unter „Einstellungen“ eintragen).');
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), OLLAMA_TIMEOUT_MS);
const timeout = setTimeout(() => controller.abort(), ollamaTimeoutMs);
let res;
try {
res = await fetch(`${OLLAMA_HOST}/api/chat`, {
res = await fetch(`${ollamaHost}/api/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({
model: OLLAMA_MODEL,
model: ollamaModel,
stream: false,
format: schema,
options: { temperature },
@@ -1964,7 +1962,7 @@ async function ollamaChatJSON({ system, user, schema, temperature = 0.5 }) {
signal: controller.signal,
});
} catch (err) {
if (err.name === 'AbortError') throw new Error(`Zeitüberschreitung bei der KI-Anfrage (> ${Math.round(OLLAMA_TIMEOUT_MS / 1000)}s).`);
if (err.name === 'AbortError') throw new Error(`Zeitüberschreitung bei der KI-Anfrage (> ${Math.round(ollamaTimeoutMs / 1000)}s).`);
throw new Error(`Verbindung zur Ollama-API fehlgeschlagen: ${err.message}`);
} finally {
clearTimeout(timeout);