DeepSeek V4.1 Flash: native Bilderkennung, GLM/OpenRouter entfernt

- deepseek-flash als einziges Modell mit Vision, Preise aktualisiert
- OpenRouter/GLM samt Code, Config und Env-Variablen entfernt
- Legacy-Modell-IDs in Sessions werden automatisch migriert
- browse lädt Produktbilder lokal nach dashboards/ (Dedupe + Cleanup)
- Vision-Prompt: Workflow für visuelle Produktsuche, max. ~5 Screenshots
This commit is contained in:
nico committed 2026-10-05 12:55:31 +02:00
1 parent 12f01b86d6
commit b740ce1316
9 files changed
+130 -82

No files matched your search

+76 -5
View File
@@ -1,5 +1,6 @@
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const { chromium } = require('playwright');
const DASHBOARD_DIR = path.join(__dirname, '..', 'dashboards');
@@ -92,8 +93,76 @@ async function search(query, log) {
return results;
}
/** Seite besuchen und lesbaren Text extrahieren. */
async function browse(url, log) {
const IMAGE_TYPES = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp', 'image/gif': 'gif' };
const MAX_IMAGE_BYTES = 4 * 1024 * 1024;
const MAX_TOTAL_IMAGE_BYTES = 12 * 1024 * 1024;
const MAX_IMAGES = 8;
/**
* Extrahiert Produktbild-URLs (nur absolute http(s), ausreichend groß, dedupliziert).
* Läuft im Browser-Kontext der besuchten Seite.
*/
function collectImages() {
const images = [];
const seen = new Set();
for (const img of document.querySelectorAll('img')) {
const src = (img.currentSrc || img.src || '').split('#')[0];
if (!/^https?:/i.test(src) || seen.has(src)) continue;
const nw = img.naturalWidth || 0;
const nh = img.naturalHeight || 0;
const rect = img.getBoundingClientRect();
const w = nw || rect.width || img.width || 0;
const h = nh || rect.height || img.height || 0;
if (w < 120 || h < 120) continue;
seen.add(src);
images.push({ url: src, alt: (img.alt || '').trim().slice(0, 120) });
if (images.length >= 12) break;
}
return images;
}
/**
* Lädt extrahierte Bilder lokal in den Dashboards-Ordner, damit das Dashboard
* sie unabhängig von Hotlink-Schutz und ablaufenden CDN-URLs einbetten kann.
* Dateiname: <sessionId>-img-<hash>.<ext> – wird beim Session-Löschen mit entfernt.
*/
async function cacheImages(page, images, sessionId, log) {
if (!sessionId) return images;
fs.mkdirSync(DASHBOARD_DIR, { recursive: true });
const existingFiles = new Set(fs.readdirSync(DASHBOARD_DIR));
const out = [];
let total = 0;
for (const img of images) {
if (out.length >= MAX_IMAGES || total >= MAX_TOTAL_IMAGE_BYTES) break;
try {
const hash = crypto.createHash('sha1').update(img.url).digest('hex').slice(0, 12);
const known = [...existingFiles].find((f) => f.startsWith(`${sessionId}-img-${hash}.`));
if (known) {
out.push({ url: `/dashboards/${known}`, alt: img.alt });
continue;
}
const resp = await page.request.get(img.url, {
timeout: 15000,
headers: { Referer: page.url() },
});
const type = String(resp.headers()['content-type'] || '').split(';')[0].trim().toLowerCase();
const ext = IMAGE_TYPES[type];
if (!ext || !resp.ok()) continue;
const buf = await resp.body();
if (buf.length < 512 || buf.length > MAX_IMAGE_BYTES || total + buf.length > MAX_TOTAL_IMAGE_BYTES) continue;
const file = `${sessionId}-img-${hash}.${ext}`;
fs.writeFileSync(path.join(DASHBOARD_DIR, file), buf);
existingFiles.add(file);
total += buf.length;
out.push({ url: `/dashboards/${file}`, alt: img.alt });
} catch {}
}
log?.(`Produktbilder lokal gespeichert: ${out.length}`);
return out;
}
/** Seite besuchen und lesbaren Text + Produktbilder (lokal gespeichert) extrahieren. */
async function browse(url, log, opts = {}) {
return withPage(url, async (page) => {
const nav = await goto(page, url);
if (!nav.ok) return { nav, error: 'Seite konnte nicht geladen werden' };
@@ -101,10 +170,12 @@ async function browse(url, log) {
const data = await page.evaluate(() => {
const title = document.title || '';
const text = (document.body ? document.body.innerText : '').replace(/\s+\n/g, '\n');
return { title, text: text.slice(0, 15000) };
return { title, text: text.slice(0, 6000) };
});
log?.(`Besuche ${url} -> ${data.title}`);
return { nav, title: data.title, text: data.text };
const extracted = await page.evaluate(collectImages);
const images = await cacheImages(page, extracted, opts.sessionId, log);
log?.(`Besuche ${url} -> ${data.title} (${images.length} Bilder lokal)`);
return { nav, title: data.title, images, text: data.text };
});
}