All files / src/routes ChatSummary.svelte

0% Statements 0/91
0% Branches 0/1
0% Functions 0/1
0% Lines 0/91

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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
      .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>