Files
nico b740ce1316 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
2026-10-05 12:55:31 +02:00

111 lines
5.2 KiB
HTML
Raw Permalink 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.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Geteilte Recherche</title>
<link rel="icon" href="/icons/icon-192.png">
<meta name="theme-color" content="#0f172a">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = { darkMode: 'class' };
document.documentElement.classList.add('dark');
</script>
</head>
<body class="dark:bg-slate-950 bg-slate-100 dark:text-slate-100 text-slate-900 min-h-screen">
<div class="max-w-3xl mx-auto p-4 md:p-8">
<header class="flex items-center justify-between mb-6">
<div class="flex items-center gap-2">
<span class="text-2xl">🔎</span>
<span class="font-bold">Research Agent</span>
<span class="text-xs px-2 py-0.5 rounded-full bg-slate-700 text-slate-300">geteilt</span>
</div>
<span id="dateLabel" class="text-sm text-slate-400"></span>
</header>
<div id="loading" class="text-center py-16 text-slate-400">Lade Recherche…</div>
<div id="content" class="hidden space-y-4"></div>
<footer class="mt-8 text-center text-xs text-slate-500">
<p>Generiert mit dem Research Agent · DeepSeek V4.1 Flash</p>
</footer>
</div>
<script>
const content = document.getElementById('content');
const loading = document.getElementById('loading');
const escapeHtml = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const mdLite = (text) => {
let s = escapeHtml(text);
s = s.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 rounded bg-slate-700 text-amber-200 text-xs">$1</code>');
s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-400 underline break-all">$1</a>');
s = s.replace(/https?:\/\/[^\s<]+/g, '<a href="$&" target="_blank" rel="noopener noreferrer" class="text-blue-400 underline break-all">$&</a>');
return s.replace(/\n/g, '<br>');
};
const toolLabel = (m) => {
const icons = { search: '🔍', browse: '🌐', verify: '✅', save_dashboard: '📊', screenshot: '📷' };
const icon = icons[m.tool_name] || '🔧';
const text = m.tool_name === 'search' ? `Suche: ${m.content}` : m.content;
return { icon, text: (text || '').slice(0, 200) };
};
const renderDashboardCard = (url) => `
<div class="dark:bg-slate-800 bg-white rounded-2xl border dark:border-slate-700 border-slate-200 overflow-hidden">
<div class="p-4 flex flex-wrap items-center gap-3">
<div class="flex-1 min-w-[200px]">
<div class="text-sm font-semibold">📊 Dashboard</div>
<div class="text-xs text-slate-400">Analyse, verifizierte Quellen, Empfehlungen & Handlungsplan</div>
</div>
<a href="${url}" target="_blank" rel="noopener noreferrer"
class="px-4 py-2 rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold">Dashboard öffnen ↗</a>
<a href="${url}" download
class="px-4 py-2 rounded-lg bg-slate-600 hover:bg-slate-500 text-white text-sm">Download</a>
</div>
<iframe src="${url}" class="w-full border-t dark:border-slate-700 border-slate-200" style="height: 65vh; min-height: 400px;" loading="lazy"></iframe>
</div>`;
async function load() {
const token = location.pathname.split('/').pop();
const res = await fetch(`/api/share/${token}`);
if (!res.ok) {
loading.textContent = '⚠️ Dieser Link ist ungültig oder wurde gelöscht.';
return;
}
const { session, messages } = await res.json();
loading.classList.add('hidden');
content.classList.remove('hidden');
document.title = `${session.title} – Research Agent`;
document.getElementById('dateLabel').textContent = new Date(session.created_at).toLocaleDateString('de-DE');
content.innerHTML = `
<h1 class="text-2xl font-bold">${escapeHtml(session.title)}</h1>
${session.dashboard ? renderDashboardCard(session.dashboard) : ''}`;
const chat = document.createElement('div');
chat.className = 'space-y-3 mt-2';
for (const m of messages) {
if (m.role === 'user') {
chat.innerHTML += `<div class="flex justify-end"><div class="max-w-[80%] px-4 py-2.5 rounded-2xl bg-blue-600 text-white whitespace-pre-wrap break-words">${
m.image ? `<img src="${m.image}" alt="Angehängtes Bild" class="rounded-lg mb-2 max-h-48">` : ''
}${mdLite(m.content)}</div></div>`;
} else if (m.role === 'assistant') {
if (m.content)
chat.innerHTML += `<div class="flex"><div class="max-w-[85%] px-4 py-2.5 rounded-2xl bg-slate-200 dark:bg-slate-800 whitespace-pre-wrap break-words">${mdLite(m.content)}</div></div>`;
} else if (m.role === 'tool') {
const { icon, text } = toolLabel(m);
chat.innerHTML += `<div class="flex"><div class="text-xs text-slate-400 px-1 flex items-center gap-1"><span>${icon}</span><span class="break-all">${escapeHtml(text)}</span></div></div>`;
}
}
content.appendChild(chat);
}
load();
</script>
</body>
</html>