diff --git a/frontend/src/lib/components/ask/AskAnswer.svelte b/frontend/src/lib/components/ask/AskAnswer.svelte
new file mode 100644
index 0000000..82d7dcd
--- /dev/null
+++ b/frontend/src/lib/components/ask/AskAnswer.svelte
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+ AI Answer
+
+
근거 기반 답변
+
+
+ {#if data && !loading}
+
+
+ 신뢰도 {confidenceLabel(data.confidence)}
+
+
+ {STATUS_LABEL[data.synthesis_status]}
+
+ {#if data.synthesis_ms > 0}
+
+ {Math.round(data.synthesis_ms)}ms
+
+ {/if}
+
+ {/if}
+
+
+
+ {#if loading}
+
+
+
+
+
+
+ 근거 기반 답변 생성 중… 약 15초 소요
+
+
+ {:else if showAnswer && data}
+
+ {#each tokens as tok}
+ {#if tok.type === 'cite'}
+
+ {:else}
+ {tok.value}
+ {/if}
+ {/each}
+
+ {:else if showWarning && data}
+
+
+
+ {/if}
+
diff --git a/frontend/src/lib/components/ask/AskEvidence.svelte b/frontend/src/lib/components/ask/AskEvidence.svelte
new file mode 100644
index 0000000..55733c4
--- /dev/null
+++ b/frontend/src/lib/components/ask/AskEvidence.svelte
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+ Evidence Highlights
+
+
인용 근거
+
+ {#if data && !loading}
+
{citations.length}개
+ {/if}
+
+
+ {#if loading}
+
+ {#each Array(2) as _}
+
+
+
+
+
+
+ {/each}
+
+ {:else if citations.length === 0}
+
+ {:else}
+
+ {#each citations as citation (citation.n)}
+ {@const isActive = activeCitation === citation.n}
+
+
+
[{citation.n}]
+
+
+ {citation.title ?? `문서 ${citation.doc_id}`}
+
+ {#if citation.section_title}
+
{citation.section_title}
+ {/if}
+
+
+
+
+
+ {citation.span_text}
+
+
+
+ relevance {citation.relevance.toFixed(2)}
+ rerank {citation.rerank_score.toFixed(2)}
+
+
+ {/each}
+
+ {/if}
+
diff --git a/frontend/src/lib/components/ask/AskResults.svelte b/frontend/src/lib/components/ask/AskResults.svelte
new file mode 100644
index 0000000..1272988
--- /dev/null
+++ b/frontend/src/lib/components/ask/AskResults.svelte
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+ Search Results
+
+
검색 결과
+
+ {#if data && !loading}
+
{data.total}개
+ {/if}
+
+
+ {#if loading}
+
+ {#each Array(5) as _}
+
+
+
+
+
+ {/each}
+
+ {:else if results.length === 0}
+
+ {:else}
+
+ {/if}
+
diff --git a/frontend/src/lib/types/ask.ts b/frontend/src/lib/types/ask.ts
new file mode 100644
index 0000000..4302ef1
--- /dev/null
+++ b/frontend/src/lib/types/ask.ts
@@ -0,0 +1,64 @@
+/**
+ * Phase 3.4: `/api/search/ask` 응답 타입.
+ *
+ * Backend Pydantic 모델 (`app/api/search.py::AskResponse`) 과 1:1 매칭.
+ * 필드 변경 시 양쪽 동시 수정 필수.
+ */
+
+export type SynthesisStatus =
+ | 'completed'
+ | 'timeout'
+ | 'skipped'
+ | 'no_evidence'
+ | 'parse_failed'
+ | 'llm_error';
+
+export type Confidence = 'high' | 'medium' | 'low';
+
+export interface Citation {
+ n: number;
+ chunk_id: number | null;
+ doc_id: number;
+ title: string | null;
+ section_title: string | null;
+ /** LLM이 추출한 50~300자 핵심 span. UI에서 이것만 노출. */
+ span_text: string;
+ /**
+ * 원본 800자 window.
+ *
+ * ⚠ UI 기본 경로에서 절대 렌더 금지. debug 모드에서 hover tooltip 용도로만
+ * 조건부 노출 가능. full_snippet을 보여주면 backend span-precision UX
+ * 가치가 사라진다 (plan §Evidence 표시 규칙).
+ */
+ full_snippet: string;
+ relevance: number;
+ rerank_score: number;
+}
+
+export interface SearchResult {
+ id: number;
+ title: string | null;
+ ai_domain: string | null;
+ ai_summary: string | null;
+ file_format: string;
+ score: number;
+ snippet: string | null;
+ match_reason: string | null;
+ chunk_id: number | null;
+ chunk_index: number | null;
+ section_title: string | null;
+ rerank_score: number | null;
+}
+
+export interface AskResponse {
+ results: SearchResult[];
+ ai_answer: string | null;
+ citations: Citation[];
+ synthesis_status: SynthesisStatus;
+ synthesis_ms: number;
+ confidence: Confidence | null;
+ refused: boolean;
+ no_results_reason: string | null;
+ query: string;
+ total: number;
+}
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 2356889..6ca056d 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -80,6 +80,7 @@
+
diff --git a/frontend/src/routes/ask/+page.svelte b/frontend/src/routes/ask/+page.svelte
new file mode 100644
index 0000000..730e5c9
--- /dev/null
+++ b/frontend/src/routes/ask/+page.svelte
@@ -0,0 +1,150 @@
+
+
+
+
+ 질문 - PKM
+
+
+
+
+
+
+
+
+ {#if !queryInput && !loading && !data}
+
+
+
+ {:else}
+
+ {/if}
+
+
diff --git a/frontend/src/routes/documents/+page.svelte b/frontend/src/routes/documents/+page.svelte
index 5076069..b4d7f46 100644
--- a/frontend/src/routes/documents/+page.svelte
+++ b/frontend/src/routes/documents/+page.svelte
@@ -3,7 +3,7 @@
import { goto } from '$app/navigation';
import { api } from '$lib/api';
import { addToast } from '$lib/stores/toast';
- import { Info, List, LayoutGrid, ChevronLeft, X, Plus, Trash2, Tag, FolderTree, Rows3, Rows2 } from 'lucide-svelte';
+ import { Info, List, LayoutGrid, ChevronLeft, X, Plus, Trash2, Tag, FolderTree, Rows3, Rows2, Sparkles } from 'lucide-svelte';
import DocumentCard from '$lib/components/DocumentCard.svelte';
import DocumentTable from '$lib/components/DocumentTable.svelte';
import DocumentViewer from '$lib/components/DocumentViewer.svelte';
@@ -441,6 +441,15 @@
+ {#if searchQuery.trim()}
+
+ AI 답변
+
+ {/if}