Files
nico 8a2bf46b09 Recherche abbrechen + Chats löschen (mit Aufräumen) + Truncation-Fix
- Manueller Abbruch: POST /api/sessions/:id/abort (nur Owner), AbortController
  für laufende Jobs (auch mitten im API-Stream), cancelQueued für wartende,
  ⛔-Button in der Activity-Bar, 'abgebrochen'-Event + History-Eintrag
- Chats löschen: 🗑-Button in der Sidebar mit Bestätigung, Ownership-Check
  (vorher konnte jeder fremde Chats löschen), löscht jetzt auch Messages
  und Dashboard-Dateien (HTML/GPX/tour.json) mit
- Truncation-Fix: finish_reason 'length' wird erkannt (sichtbarer Fehler
  statt leerer Abschluss), max_tokens auf 131072 (GLM-Max), 10-Min-Timeout
  gegen hängende Streams
2026-08-28 07:17:06 +02:00

104 lines
5.8 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Research Agent</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/icons/icon-192.png">
<meta name="theme-color" content="#0f172a">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<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 h-screen flex overflow-hidden">
<!-- Sidebar -->
<aside id="sidebar" class="w-64 shrink-0 dark:bg-slate-900 bg-white border-r dark:border-slate-800 border-slate-200 flex flex-col hidden md:flex">
<div class="p-4 border-b dark:border-slate-800 border-slate-200 flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-xl">🔎</span>
<span class="font-bold">Research Agent</span>
</div>
<button id="logoutBtn" class="text-sm text-slate-400 hover:text-red-400" title="Abmelden">⎋</button>
</div>
<button id="newSessionBtn"
class="m-4 mb-2 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm">
+ Neue Recherche
</button>
<div class="mx-4 mb-2">
<label class="text-xs text-slate-400 block mb-1" for="modelSelectDesktop">Modell</label>
<select id="modelSelectDesktop"
class="w-full px-2 py-1.5 rounded-lg border text-sm dark:bg-slate-800 dark:border-slate-700 border-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
</div>
<div id="sessionList" class="flex-1 overflow-y-auto px-2 space-y-1"></div>
<div class="p-3 text-xs text-slate-400 border-t dark:border-slate-800 border-slate-200">
<div id="userName" class="font-medium text-slate-500 dark:text-slate-400 mb-1"></div>
<div id="queueStatus" class="hidden"><span class="inline-block w-2 h-2 bg-amber-400 rounded-full mr-1 animate-pulse"></span>Recherche läuft…</div>
</div>
</aside>
<!-- Main -->
<main class="flex-1 flex flex-col min-w-0">
<!-- Mobile header -->
<div class="md:hidden flex items-center gap-2 p-3 border-b dark:border-slate-800 border-slate-200 bg-white dark:bg-slate-900">
<button id="menuBtn" class="px-2 py-1 rounded bg-slate-200 dark:bg-slate-800">☰</button>
<span class="font-bold truncate flex-1" id="mobileTitle">Research Agent</span>
<select id="modelSelectMobile"
class="px-2 py-1 rounded-lg border text-xs dark:bg-slate-800 dark:border-slate-700 border-slate-300 focus:outline-none"></select>
</div>
<!-- Activity bar -->
<div id="activityBar" class="hidden px-4 py-2 text-sm border-b dark:border-slate-800 border-slate-200 bg-slate-50 dark:bg-slate-900">
<div id="activityLine" class="flex items-center gap-2">
<span class="inline-block w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></span>
<span id="activityText">Starte…</span>
<button id="abortBtn" type="button" title="Recherche abbrechen"
class="ml-auto hidden text-xs px-2 py-1 rounded-lg bg-red-600/15 text-red-400 hover:bg-red-600/30">⛔ Abbrechen</button>
</div>
<div id="activityLog" class="hidden mt-1 max-h-32 overflow-y-auto text-xs text-slate-400 space-y-0.5"></div>
<button id="toggleLog" class="text-xs text-blue-400 mt-1">Details zeigen</button>
</div>
<!-- Chat -->
<div id="chat" class="flex-1 overflow-y-auto p-4 space-y-4">
<div id="emptyState" class="h-full flex flex-col items-center justify-center text-center text-slate-400">
<div class="text-5xl mb-4">🔎</div>
<h2 class="text-lg font-semibold text-slate-500 dark:text-slate-300">Worüber soll ich recherchieren?</h2>
<p class="text-sm max-w-md mt-2">
Ich suche gründlich im Internet, verifiziere alle Quellen und baue dir ein
Dashboard mit Analyse, Empfehlungen und Handlungsplan.
</p>
</div>
</div>
<!-- Input -->
<div class="p-4 border-t dark:border-slate-800 border-slate-200 bg-white dark:bg-slate-900">
<div id="imagePreview" class="hidden mb-2 relative">
<img id="imagePreviewImg" src="" alt="Vorschau" class="max-h-40 rounded-xl border dark:border-slate-700 border-slate-300">
<button id="imageRemoveBtn" type="button" title="Bild entfernen"
class="absolute -top-2 -left-2 w-6 h-6 rounded-full bg-red-600 text-white text-xs font-bold">✕</button>
</div>
<form id="chatForm" class="flex gap-2 items-end">
<input type="file" id="imageInput" accept="image/jpeg,image/png,image/webp" class="hidden">
<button id="imageBtn" type="button" title="Bild anhängen (nur Vision-Modelle)"
class="px-3 py-2.5 rounded-xl border dark:border-slate-700 border-slate-300 dark:bg-slate-800 hover:dark:bg-slate-700 text-lg leading-none">📷</button>
<textarea id="input" rows="1" placeholder="Thema eingeben… z.B. „Beste Self-Hosted Social-Media-Alternativen 2026“"
class="flex-1 resize-none px-3 py-2.5 rounded-xl border dark:bg-slate-800 dark:border-slate-700 border-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 max-h-40"></textarea>
<button id="sendBtn" type="submit"
class="px-4 py-2.5 rounded-xl bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white font-semibold">
Senden
</button>
</form>
<p class="text-xs text-slate-400 mt-2" id="shareHint"></p>
</div>
</main>
<script src="/app.js"></script>
</body>
</html>