Multi-Modell + Vision: OpenRouter (GLM 5.3 Flash), Bild-Upload, Tour-Feature, Dashboard-Fixes

- Modell-Registry (DeepSeek direkt + OpenRouter z-ai/glm-5.3-flash), Modellwahl pro Session, Reasoning-Effort max (env-konfigurierbar)
- Bild-Upload für Vision-Modelle (User) + Agent-screenshot()-Tool (nur Vision)
- Tour-Planung: geocode + save_tour mit GPX-Export und Karten-Sektion
- Fix: System-Prompt wird jetzt wirklich an die API gesendet, inkl. aktuellem Dashboard-Dateinamen für Folge-Updates
- Fix: History-Sanitizer gegen 400-Fehler (nie mitten in Tool-Gruppen kürzen)
- Fix: reasoning_content-Roundtrip (Pflicht bei DeepSeek mit tools)
- Fix: Dashboards ohne Browser-Cache ausliefern
This commit is contained in:
nico committed 2026-08-27 17:43:13 +02:00
1 parent eee59363d4
commit 87eb7029f1
10 files changed
+843 -70

No files matched your search

+62 -7
View File
@@ -4,7 +4,7 @@ const crypto = require('crypto');
const express = require('express');
const webpush = require('web-push');
const { config } = require('./lib/config');
const { config, getModel, getModels } = require('./lib/config');
const db = require('./lib/db');
const queue = require('./lib/queue');
const { runResearchJob } = require('./lib/research');
@@ -159,10 +159,36 @@ app.get('/api/sessions', requireAuth, (req, res) => {
});
app.post('/api/sessions', requireAuth, (req, res) => {
const session = db.createSession(req.user.n);
const session = db.createSession(req.user.n, 'Neue Recherche', getModel(config.model).id);
res.json(session);
});
app.patch('/api/sessions/:id', requireAuth, (req, res) => {
const session = db.getSession(req.params.id);
if (!session || session.owner_name !== req.user.n) {
return res.status(404).json({ error: 'Session nicht gefunden' });
}
const modelId = String(req.body?.model || '');
if (!getModels().some((m) => m.id === modelId)) {
return res.status(400).json({ error: 'Unbekanntes Modell' });
}
db.setSessionModel(session.id, modelId);
res.json(db.getSession(session.id));
});
app.get('/api/models', requireAuth, (req, res) => {
res.json({
default: getModel(config.model).id,
models: getModels().map((m) => ({
id: m.id,
label: m.label,
provider: m.provider,
vision: m.vision,
costNote: m.costNote || null,
})),
});
});
app.get('/api/sessions/:id', requireAuth, (req, res) => {
const session = db.getSession(req.params.id);
if (!session || (session.owner_name !== req.user.n && !session.share_token)) {
@@ -204,7 +230,22 @@ app.post('/api/sessions/:id/messages', requireAuth, (req, res) => {
const session = db.getSession(req.params.id);
if (!session) return res.status(404).json({ error: 'Session nicht gefunden' });
const text = String(req.body?.text || '').trim();
if (!text) return res.status(400).json({ error: 'Leere Nachricht' });
const image = typeof req.body?.image === 'string' && req.body.image ? req.body.image : null;
if (!text && !image) return res.status(400).json({ error: 'Leere Nachricht' });
if (image) {
if (!/^data:image\/(jpeg|png|webp);base64,/.test(image)) {
return res.status(400).json({ error: 'Ungültiges Bild (JPEG/PNG/WebP als Data-URL erwartet)' });
}
if (image.length > 4000000) {
return res.status(400).json({ error: 'Bild zu groß (max. ~3 MB)' });
}
const model = getModel(session.model);
if (!model.vision) {
return res.status(400).json({
error: `Das Modell "${model.label}" kann keine Bilder verarbeiten – bitte auf ein Modell mit Bilderkennung wechseln (z.B. GLM 5.3 Flash).`,
});
}
}
if (session.status === 'running' || session.status === 'queued') {
return res.status(409).json({ error: 'Für diese Session läuft bereits ein Job.' });
}
@@ -212,7 +253,7 @@ app.post('/api/sessions/:id/messages', requireAuth, (req, res) => {
return res.status(409).json({ error: 'Eine andere Recherche läuft gerade – bitte warten.' });
}
db.createMessage(session.id, { role: 'user', content: text });
db.createMessage(session.id, { role: 'user', content: text, image });
if (session.title === 'Neue Recherche') {
db.setSessionTitle(session.id, text.slice(0, 70));
}
@@ -238,7 +279,7 @@ app.post('/api/sessions/:id/messages', requireAuth, (req, res) => {
};
await runResearchJob(
{ apiKey: config.apiKey, model: config.model, thinking: config.thinking, baseUrl: config.baseUrl },
config,
db.getSession(session.id),
text,
emit,
@@ -338,7 +379,18 @@ function sendPush(session, result) {
// Statische Dateien & Dashboard-Dateien
// ---------------------------------------------------------------------------
app.use('/dashboards', express.static(path.join(__dirname, 'dashboards'), { maxAge: '1h' }));
app.use(
'/dashboards',
express.static(path.join(__dirname, 'dashboards'), {
// Kein Caching: Dashboards werden bei Folge-Nachrichten überschrieben,
// der Browser soll immer die aktuelle Version sehen.
maxAge: 0,
setHeaders: (res, filePath) => {
if (filePath.endsWith('.gpx')) res.setHeader('Content-Type', 'application/gpx+xml');
if (filePath.endsWith('.tour.json')) res.setHeader('Content-Type', 'application/json');
},
})
);
app.use('/manifest.json', express.static(path.join(__dirname, 'public', 'manifest.json')));
app.use('/sw.js', express.static(path.join(__dirname, 'public', 'sw.js')));
app.use('/icons', express.static(path.join(__dirname, 'public', 'icons')));
@@ -361,7 +413,10 @@ app.get('/', (req, res) => {
// ---------------------------------------------------------------------------
if (!config.apiKey) {
console.error('WARNUNG: DEEPSEEK_API_KEY fehlt in .env – Recherche wird nicht funktionieren.');
console.error('WARNUNG: DEEPSEEK_API_KEY fehlt in .env – DeepSeek-Modelle werden nicht funktionieren.');
}
if (!config.openrouterApiKey) {
console.error('WARNUNG: OPENROUTER_API_KEY fehlt in .env – OpenRouter-Modelle (z.B. GLM 5.3 Flash) werden nicht funktionieren.');
}
if (!config.appPassword) {
console.error('WARNUNG: APP_PASSWORD fehlt in .env – Login ohne Passwort-Check.');