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:
1 parent
eee59363d4
commit
87eb7029f1
10 files changed
+843
-70
No files matched your search
@@ -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.');
|
||||
|
||||
Reference in new issue
Block a user