Files
researchChat/lib/config.js
T
nico ab4f8e8570 Stale-Job-Reset, Ownership-Angleichung, robuste Dashboard-Erzeugung
- Server-Neustart setzt hängengebliebene Jobs (running/queued) zurück
  (markStaleJobs) mit Hinweis-Nachricht; /abort räumt auch verwaiste Sessions auf
- Löschen/Abbrechen/Modellwechsel ohne Owner-Beschränkung (konsistent zur
  offenen Sichtbarkeit im Ein-Nutzer-System)
- Dashboard-HTML wird nicht mehr per Tool-Aufruf übertragen, sondern in der
  finalen Antwort als ```html```-Codeblock gestreamt (GLM scheiterte
  reproduzierbar am riesigen Tool-Call); Extraktion mit klaren Fehlern bei
  fehlendem/unvollständigem Codeblock
- Reasoning-Effort pro Runde: volle Tiefe in Research-Runden, minimales Denken
  in der Dashboard-Liefer-Runde (DeepSeek: thinking aus, GLM: low) –
  Budget gehört dem HTML
- GLM-Reasoning-Default high (max verbrennt das Completion-Budget),
  DeepSeek max_tokens auf 300k
2026-08-28 09:17:13 +02:00

129 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const webpush = require('web-push');
const ENV_PATH = path.join(__dirname, '..', '.env');
function loadEnv() {
const env = {};
if (fs.existsSync(ENV_PATH)) {
for (const line of fs.readFileSync(ENV_PATH, 'utf8').split('\n')) {
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
if (m && !line.trim().startsWith('#')) env[m[1]] = m[2].replace(/^["']|["']$/g, '');
}
}
return env;
}
function saveEnv(env) {
const lines = [];
for (const [k, v] of Object.entries(env)) lines.push(`${k}=${v}`);
fs.writeFileSync(ENV_PATH, lines.join('\n') + '\n', 'utf8');
}
function ensure(key, generator) {
if (process.env[key] || env[key]) return process.env[key] || env[key];
const value = generator();
env[key] = value;
saveEnv(env);
return value;
}
const env = loadEnv();
function ensureVapidPair() {
if ((process.env.VAPID_PUBLIC_KEY || env.VAPID_PUBLIC_KEY) && (process.env.VAPID_PRIVATE_KEY || env.VAPID_PRIVATE_KEY)) {
return {
publicKey: process.env.VAPID_PUBLIC_KEY || env.VAPID_PUBLIC_KEY,
privateKey: process.env.VAPID_PRIVATE_KEY || env.VAPID_PRIVATE_KEY,
};
}
const pair = webpush.generateVAPIDKeys();
env.VAPID_PUBLIC_KEY = pair.publicKey;
env.VAPID_PRIVATE_KEY = pair.privateKey;
saveEnv(env);
return pair;
}
const vapid = ensureVapidPair();
const config = {
port: Number(process.env.PORT || env.PORT || 3000),
apiKey: process.env.DEEPSEEK_API_KEY || env.DEEPSEEK_API_KEY || '',
openrouterApiKey: process.env.OPENROUTER_API_KEY || env.OPENROUTER_API_KEY || '',
appPassword: process.env.APP_PASSWORD || env.APP_PASSWORD || '',
model: process.env.MODEL || env.MODEL || 'deepseek-v4-flash',
thinking: process.env.THINKING || env.THINKING || 'enabled',
maxTokens: Number(process.env.MAX_TOKENS || env.MAX_TOKENS || 64000),
baseUrl: process.env.DEEPSEEK_BASE_URL || env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com',
loginMaxAttempts: Number(process.env.LOGIN_MAX_ATTEMPTS || env.LOGIN_MAX_ATTEMPTS || 5),
loginLockMinutes: Number(process.env.LOGIN_LOCK_MINUTES || env.LOGIN_LOCK_MINUTES || 5),
nominatimEmail: process.env.NOMINATIM_EMAIL || env.NOMINATIM_EMAIL || '',
osrmUrl: process.env.OSRM_URL || env.OSRM_URL || 'https://router.project-osrm.org',
sessionSecret: ensure('SESSION_SECRET', () => crypto.randomBytes(32).toString('hex')),
vapidPublicKey: vapid.publicKey,
vapidPrivateKey: vapid.privateKey,
};
const envOr = (key, def) => process.env[key] || env[key] || def;
// ---------------------------------------------------------------------------
// Modell-Registry: DeepSeek direkt + OpenRouter-Modelle (z.B. GLM 5.3 Flash)
// ---------------------------------------------------------------------------
const MODELS = [
{
id: 'deepseek-v4-flash',
label: 'DeepSeek V4 Flash',
provider: 'deepseek',
vision: false,
// Max. Output laut Doku: 384K – DeepSeek hat 3x so viel Headroom wie GLM
maxTokens: 300000,
reasoningEffort: () => envOr('DEEPSEEK_REASONING_EFFORT', 'max'),
// Liefer-Runde des Dashboards: Denken komplett aus, Budget gehört dem HTML
deliveryEffort: 'none',
prices: { inputCacheHit: 0.014, inputCacheMiss: 0.44, output: 1.32 },
},
{
id: 'z-ai/glm-5.3-flash',
label: 'GLM 5.3 Flash (OpenRouter)',
provider: 'openrouter',
vision: true,
// Max. Completion laut OpenRouter: 131072
maxTokens: 131072,
// Research-Runden: 'high' (effort 'max' verbrennt das ganze Budget mit
// Denken; für lange Recherchen bleibt dann nichts fürs große HTML übrig)
reasoningEffort: () => envOr('GLM_REASONING_EFFORT', 'high'),
// Liefer-Runde des Dashboards: minimales Denken (GLM: Reasoning Pflicht)
deliveryEffort: 'low',
prices: { inputCacheHit: 0.015, inputCacheMiss: 0.075, output: 0.25 },
costNote: 'Schätzung (OpenRouter, Rabatt bis Sep 2026)',
},
];
/** Registry inkl. benutzerdefiniertem Modell aus der Env (MODEL), falls nicht bekannt. */
function getModels() {
if (MODELS.some((m) => m.id === config.model)) return MODELS;
return [
...MODELS,
{
id: config.model,
label: config.model,
provider: 'deepseek',
vision: false,
maxTokens: config.maxTokens,
reasoningEffort: () => envOr('DEEPSEEK_REASONING_EFFORT', 'max'),
prices: { inputCacheHit: 0.014, inputCacheMiss: 0.44, output: 1.32 },
},
];
}
/** Modell per ID auflösen (Fallback: Default-Modell aus Env). */
function getModel(id) {
const models = getModels();
return models.find((m) => m.id === id) || models.find((m) => m.id === config.model) || models[0];
}
module.exports = { config, MODELS, getModels, getModel };