Skip to content

Commit 9b99c90

Browse files
committed
feat embeddingを使って概要を出すようにする
1 parent f8e0129 commit 9b99c90

4 files changed

Lines changed: 108 additions & 77 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<script lang="ts">
2+
import { _ } from "svelte-i18n";
3+
import "$lib/i18n";
4+
5+
export let embeddingStatus: Promise<{
6+
indexed: boolean;
7+
modelVersion: string;
8+
createdAt: number;
9+
updatedAt: number;
10+
chunkCount: number;
11+
chunkSummaries: string[];
12+
} | null> | null;
13+
14+
// モバイル用トグル(デフォルトは非表示)
15+
let mobileOpen = false;
16+
</script>
17+
18+
{#await embeddingStatus}
19+
<!-- ローディング中は何も表示しない -->
20+
{:then status}
21+
{#if status?.indexed && status.chunkSummaries.length > 0}
22+
<div class="rounded-xl border border-indigo-200 dark:border-indigo-800/50 bg-white dark:bg-gray-800/50 shadow-sm overflow-hidden">
23+
<!-- ヘッダー行 -->
24+
<div class="flex items-center justify-between px-4 py-2.5 bg-indigo-50 dark:bg-indigo-950/30 border-b border-indigo-100 dark:border-indigo-800/40">
25+
<span class="text-xs font-semibold text-indigo-700 dark:text-indigo-300 tracking-wide uppercase">
26+
{$_("diary.chunkTimeline.title")}
27+
</span>
28+
<!-- モバイルのみトグルボタン表示 -->
29+
<button
30+
type="button"
31+
on:click={() => (mobileOpen = !mobileOpen)}
32+
class="sm:hidden flex items-center gap-1 text-xs text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors"
33+
aria-expanded={mobileOpen}
34+
>
35+
{mobileOpen ? $_("diary.chunkTimeline.hide") : $_("diary.chunkTimeline.show")}
36+
<svg
37+
class="w-3.5 h-3.5 transition-transform {mobileOpen ? 'rotate-180' : ''}"
38+
fill="none"
39+
stroke="currentColor"
40+
viewBox="0 0 24 24"
41+
>
42+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
43+
</svg>
44+
</button>
45+
</div>
46+
47+
<!-- タイムライン本体: PC は常時表示、モバイルはトグル -->
48+
<div class="hidden sm:block px-4 pt-3 pb-1">
49+
<!-- チャンク一覧 -->
50+
<div class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-5 space-y-2 pb-2">
51+
{#each status.chunkSummaries as summary}
52+
<div class="relative">
53+
<div class="absolute -left-[1.6rem] top-1 w-2.5 h-2.5 rounded-full border-2 border-indigo-400 dark:border-indigo-500 bg-white dark:bg-gray-800"></div>
54+
<p class="text-sm text-gray-700 dark:text-gray-300 leading-snug">
55+
{summary || $_("diary.embedding.chunkSummaryEmpty")}
56+
</p>
57+
</div>
58+
{/each}
59+
</div>
60+
<!-- メタ情報フッター -->
61+
<div class="mt-1 pb-2 flex flex-wrap gap-x-4 gap-y-0.5 text-[11px] text-gray-400 dark:text-gray-500">
62+
<span>{$_("diary.embedding.modelVersion")}: <span class="font-mono">{status.modelVersion}</span></span>
63+
<span>{$_("diary.embedding.updatedAt")}: {new Date(status.updatedAt * 1000).toLocaleString()}</span>
64+
</div>
65+
</div>
66+
67+
<!-- モバイル展開時 -->
68+
{#if mobileOpen}
69+
<div class="sm:hidden px-4 pt-3 pb-1">
70+
<!-- チャンク一覧 -->
71+
<div class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-5 space-y-2 pb-2">
72+
{#each status.chunkSummaries as summary}
73+
<div class="relative">
74+
<div class="absolute -left-[1.6rem] top-1 w-2.5 h-2.5 rounded-full border-2 border-indigo-400 dark:border-indigo-500 bg-white dark:bg-gray-800"></div>
75+
<p class="text-sm text-gray-700 dark:text-gray-300 leading-snug">
76+
{summary || $_("diary.embedding.chunkSummaryEmpty")}
77+
</p>
78+
</div>
79+
{/each}
80+
</div>
81+
<!-- メタ情報フッター -->
82+
<div class="mt-1 pb-2 flex flex-wrap gap-x-4 gap-y-0.5 text-[11px] text-gray-400 dark:text-gray-500">
83+
<span>{$_("diary.embedding.modelVersion")}: <span class="font-mono">{status.modelVersion}</span></span>
84+
<span>{$_("diary.embedding.updatedAt")}: {new Date(status.updatedAt * 1000).toLocaleString()}</span>
85+
</div>
86+
</div>
87+
{/if}
88+
</div>
89+
{/if}
90+
{/await}

frontend/src/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@
262262
"chunkCount": "Chunks",
263263
"chunkSummaries": "Chunk list",
264264
"chunkSummaryEmpty": "(no summary)"
265+
},
266+
"chunkTimeline": {
267+
"title": "Diary overview",
268+
"show": "Show",
269+
"hide": "Hide"
265270
}
266271
},
267272
"date": {

frontend/src/locales/ja.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@
262262
"chunkCount": "チャンク数",
263263
"chunkSummaries": "チャンク一覧",
264264
"chunkSummaryEmpty": "(概要なし)"
265+
},
266+
"chunkTimeline": {
267+
"title": "この日記の概要",
268+
"show": "表示",
269+
"hide": "非表示"
265270
}
266271
},
267272
"date": {

frontend/src/routes/[id]/+page.svelte

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import HighlightDisplay from "$lib/components/molecules/HighlightDisplay.svelte";
1515
import Modal from "$lib/components/molecules/Modal.svelte";
1616
import PastEntriesLinks from "$lib/components/molecules/PastEntriesLinks.svelte";
17+
import DiaryChunkTimeline from "$lib/components/molecules/DiaryChunkTimeline.svelte";
1718
import SummaryDisplay from "$lib/components/molecules/SummaryDisplay.svelte";
1819
import { getDayOfWeekKey } from "$lib/utils/date-utils";
1920
import { createSubmitHandler } from "$lib/utils/form-utils";
@@ -70,7 +71,6 @@
7071
// ハイライトデータ
7172
let highlightData: HighlightData | null = null;
7273
let highlightVisible = true; // ハイライトの表示・非表示状態
73-
let embeddingDetailOpen = false; // vectorの詳細表示トグル
7474
7575
// 検索ハイライト(URLの?searchパラメータから取得)
7676
$: searchKeyword = $page.url.searchParams.get("search") ?? "";
@@ -418,6 +418,13 @@
418418
</div>
419419
{/if}
420420

421+
<!-- チャンクタイムライン(embeddingがある場合のみ表示) -->
422+
{#if data.semanticSearchEnabled && data.entry}
423+
<DiaryChunkTimeline
424+
embeddingStatus={data.embeddingStatus}
425+
/>
426+
{/if}
427+
421428
<DiaryCard
422429
title=""
423430
entry={data.entry}
@@ -513,82 +520,6 @@ use:enhance={createSubmitHandler(
513520
{/if}
514521
</div>
515522

516-
<!-- RAGインデックス状態 -->
517-
{#if data.semanticSearchEnabled && data.entry}
518-
<div class="mt-2 text-xs">
519-
{#await data.embeddingStatus}
520-
<!-- ローディング中 -->
521-
<span class="inline-flex items-center px-2 py-0.5 rounded-full bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400">
522-
<svg class="w-3 h-3 mr-1 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
523-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
524-
</svg>
525-
{$_("common.loading")}
526-
</span>
527-
{:then embeddingStatus}
528-
<div class="flex items-center gap-2">
529-
{#if embeddingStatus?.indexed}
530-
<button
531-
type="button"
532-
class="inline-flex items-center px-2 py-0.5 rounded-full bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300 hover:bg-purple-200 dark:hover:bg-purple-900/50 transition-colors cursor-pointer"
533-
on:click={() => (embeddingDetailOpen = !embeddingDetailOpen)}
534-
>
535-
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
536-
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
537-
</svg>
538-
{$_("diary.embedding.indexed")}
539-
<svg class="w-3 h-3 ml-1 transition-transform {embeddingDetailOpen ? 'rotate-180' : ''}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
540-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
541-
</svg>
542-
</button>
543-
{:else}
544-
<span class="inline-flex items-center px-2 py-0.5 rounded-full bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400">
545-
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
546-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
547-
</svg>
548-
{$_("diary.embedding.notIndexed")}
549-
</span>
550-
{/if}
551-
</div>
552-
553-
{#if embeddingStatus?.indexed && embeddingDetailOpen}
554-
<div class="mt-2 p-3 rounded-lg bg-purple-50 dark:bg-purple-950/20 border border-purple-200 dark:border-purple-800/40 space-y-1.5">
555-
<div class="flex items-center gap-2">
556-
<span class="text-gray-500 dark:text-gray-400 w-28 shrink-0">{$_("diary.embedding.modelVersion")}:</span>
557-
<span class="font-mono text-purple-700 dark:text-purple-300">{embeddingStatus.modelVersion}</span>
558-
</div>
559-
<div class="flex items-center gap-2">
560-
<span class="text-gray-500 dark:text-gray-400 w-28 shrink-0">{$_("diary.embedding.chunkCount")}:</span>
561-
<span class="text-gray-700 dark:text-gray-300">{embeddingStatus.chunkCount}</span>
562-
</div>
563-
{#if embeddingStatus.chunkSummaries.length > 0}
564-
<div>
565-
<span class="text-gray-500 dark:text-gray-400">{$_("diary.embedding.chunkSummaries")}:</span>
566-
<ol class="mt-1 space-y-0.5 list-decimal list-inside">
567-
{#each embeddingStatus.chunkSummaries as chunkSummary}
568-
<li class="text-gray-600 dark:text-gray-400">
569-
{#if chunkSummary}
570-
{chunkSummary}
571-
{:else}
572-
{$_("diary.embedding.chunkSummaryEmpty")}
573-
{/if}
574-
</li>
575-
{/each}
576-
</ol>
577-
</div>
578-
{/if}
579-
<div class="flex items-center gap-2">
580-
<span class="text-gray-500 dark:text-gray-400 w-28 shrink-0">{$_("diary.embedding.indexedAt")}:</span>
581-
<span class="text-gray-700 dark:text-gray-300">{new Date(embeddingStatus.createdAt * 1000).toLocaleString()}</span>
582-
</div>
583-
<div class="flex items-center gap-2">
584-
<span class="text-gray-500 dark:text-gray-400 w-28 shrink-0">{$_("diary.embedding.updatedAt")}:</span>
585-
<span class="text-gray-700 dark:text-gray-300">{new Date(embeddingStatus.updatedAt * 1000).toLocaleString()}</span>
586-
</div>
587-
</div>
588-
{/if}
589-
{/await}
590-
</div>
591-
{/if}
592523

593524

594525
<div class="flex justify-between">

0 commit comments

Comments
 (0)