Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | <script> import { onMount } from "svelte"; import { get, post } from "../lib/api.js"; let dates = $state([]); let selectedDate = $state(""); let summary = $state(null); let messages = $state([]); let showMessages = $state(false); let loading = $state(false); let triggering = $state(false); let error = $state(""); onMount(async () => { try { dates = await get("/api/chat-summary/dates"); if (dates.length) selectDate(dates[0]); } catch (e) { error = e.body?.detail || "Failed to load dates"; } }); async function selectDate(d) { selectedDate = d; showMessages = false; loading = true; error = ""; try { summary = await get(`/api/chat-summary/summary/${d}`); } catch (e) { summary = null; if (e.status !== 404) error = e.body?.detail || "Failed to load summary"; } finally { loading = false; } } async function loadMessages() { if (showMessages) { showMessages = false; return; } try { messages = await get(`/api/chat-summary/messages/${selectedDate}`); showMessages = true; } catch (e) { error = "Failed to load messages"; } } async function trigger() { triggering = true; try { await post("/api/chat-summary/trigger"); } catch (e) { error = "Trigger failed"; } finally { triggering = false; } } function markdownToHtml(md) { if (!md) return ""; return md .replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") .replace(/^### (.+)$/gm, '<h3 class="text-lg font-semibold mt-4 mb-2">$1</h3>') .replace(/^## (.+)$/gm, '<h2 class="text-xl font-bold mt-5 mb-2">$1</h2>') .replace(/^# (.+)$/gm, '<h1 class="text-2xl font-bold mt-6 mb-3">$1</h1>') .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') .replace(/\*(.+?)\*/g, '<em>$1</em>') .replace(/^- (.+)$/gm, '<li class="ml-4">$1</li>') .replace(/\n{2,}/g, '<br><br>') .replace(/\n/g, '<br>'); } </script> <div> <div class="flex items-center justify-between mb-6"> <h2 class="text-xl font-bold text-surface-800 dark:text-surface-100">Chat Digest</h2> <button onclick={trigger} disabled={triggering} class="px-3 py-1.5 text-sm font-medium rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50"> {triggering ? "Generating..." : "Generate Now"} </button> </div> {#if error} <div class="mb-4 p-3 rounded-lg bg-red-50 text-red-700 text-sm dark:bg-red-900/20 dark:text-red-400">{error}</div> {/if} <div class="flex gap-2 mb-6 flex-wrap"> {#each dates as d} <button onclick={() => selectDate(d)} class="px-3 py-1.5 text-sm rounded-lg transition-colors {selectedDate === d ? 'bg-primary-600 text-white' : 'bg-surface-100 text-surface-600 hover:bg-surface-200 dark:bg-surface-700 dark:text-surface-300 dark:hover:bg-surface-600'}"> {d} </button> {/each} {#if !dates.length && !error} <p class="text-sm text-surface-400">No summaries yet.</p> {/if} </div> {#if loading} <div class="flex justify-center py-12"><div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div></div> {:else if summary} <div class="prose dark:prose-invert max-w-none bg-white dark:bg-surface-800 rounded-xl p-6 shadow-sm border border-surface-200 dark:border-surface-700"> {@html markdownToHtml(summary.content)} </div> <button onclick={loadMessages} class="mt-4 text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400"> {showMessages ? "Hide raw messages" : "Show raw messages"} </button> {#if showMessages} <div class="mt-3 bg-white dark:bg-surface-800 rounded-xl p-4 shadow-sm border border-surface-200 dark:border-surface-700 max-h-96 overflow-y-auto"> {#each messages as m} <div class="py-1.5 border-b border-surface-100 dark:border-surface-700 last:border-0 text-sm"> <span class="text-surface-400 text-xs">{m.time?.slice(11,16)}</span> <span class="font-medium text-primary-600 dark:text-primary-400 ml-1">[{m.group}] {m.sender}:</span> <span class="text-surface-700 dark:text-surface-300 ml-1">{m.text}</span> </div> {/each} </div> {/if} {/if} </div> |