Research Chat: Web-Chatoberfläche für den Research-Agent (DeepSeek v4 Flash + Playwright, Docker/Podman)
This commit is contained in:
commit
a0243d494c
23 files changed
+2253
No files matched your search
@@ -0,0 +1,108 @@
|
||||
<!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 Flash</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const content = document.getElementById('content');
|
||||
const loading = document.getElementById('loading');
|
||||
|
||||
const escapeHtml = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
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: '📊' };
|
||||
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">${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>
|
||||
Reference in new issue
Block a user