Skip to content

Commit ecd0eba

Browse files
authored
feat(recall): add corpus browser page (kenn-io#1243)
Adds Recall as a top-level corpus browser. Accepted entries appear in a padded, single-row table for quick comparison; each row expands in place to show the full fact, trigger, uncertainty, extraction metadata, provenance state, and transcript evidence. Search and filters cover project, type, active extraction generation, and review state. Browsing stays bounded with cursor pagination. Ranked searches preserve relevance semantics, reject stale continuations after corpus mutations, and disclose the existing 500-result cap. The page also reports extraction coverage and stays unavailable on read-only backends. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
1 parent ca808ec commit ecd0eba

28 files changed

Lines changed: 2845 additions & 17 deletions

cmd/agentsview/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"go.kenn.io/agentsview/internal/config"
2424
"go.kenn.io/agentsview/internal/db"
2525
"go.kenn.io/agentsview/internal/parser"
26+
"go.kenn.io/agentsview/internal/recall/extract"
2627
"go.kenn.io/agentsview/internal/remotesync"
2728
"go.kenn.io/agentsview/internal/secrets"
2829
"go.kenn.io/agentsview/internal/server"
@@ -490,6 +491,10 @@ func runServe(cfg config.Config, opts serveOptions) {
490491
// no sync activity follows. Notify never blocks.
491492
srvOpts = append(srvOpts,
492493
server.WithSessionMutationNotifier(extractSched.Notify))
494+
if manager, ok := extractSched.mgr.(*extract.Manager); ok {
495+
srvOpts = append(srvOpts,
496+
server.WithRecallExtractionStatusProvider(manager))
497+
}
493498
}
494499
if engine != nil {
495500
srvOpts = append(srvOpts, server.WithLocalSyncRunner(

frontend/messages/en.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nav_more_navigation": "More navigation",
2020
"nav_more": "More",
2121
"nav_trends": "Trends",
22+
"nav_recall": "Recall",
2223
"nav_pinned": "Pinned",
2324
"nav_insights": "Insights",
2425
"nav_trash": "Trash",
@@ -1280,6 +1281,41 @@
12801281
"session_recall_provenance_revoked": "Provenance revoked",
12811282
"session_recall_evidence_range": "Messages {start}–{end}",
12821283
"session_recall_jump_evidence": "Jump to transcript messages {start}–{end}",
1284+
"recall_page_title": "Recall",
1285+
"recall_page_subtitle": "Browse distilled entries and extraction coverage.",
1286+
"recall_page_search_placeholder": "Search Recall entries…",
1287+
"recall_page_clear_search": "Clear Recall search",
1288+
"recall_page_type_filter": "Entry type",
1289+
"recall_page_all_types": "All types",
1290+
"recall_page_generation_filter": "Extraction generation",
1291+
"recall_page_all_generations": "All generations",
1292+
"recall_page_review_filter": "Review state",
1293+
"recall_page_all_review_states": "All review states",
1294+
"recall_page_table_label": "Recall entries",
1295+
"recall_page_fact_column": "Fact",
1296+
"recall_page_project_column": "Project",
1297+
"recall_page_expand_entry": "Expand {title}",
1298+
"recall_page_collapse_entry": "Collapse {title}",
1299+
"recall_page_trigger_label": "Trigger",
1300+
"recall_page_uncertainty_label": "Uncertainty",
1301+
"recall_page_loading": "Loading Recall entries…",
1302+
"recall_page_unavailable_read_only": "Recall is unavailable in read-only mode.",
1303+
"recall_page_empty": "No Recall entries match these filters.",
1304+
"recall_page_error": "Could not load Recall entries.",
1305+
"recall_page_load_more": "Load more",
1306+
"recall_page_loading_more": "Loading more…",
1307+
"recall_page_ranked_result_cap": "Ranked search is limited to the first {countLabel} matches.",
1308+
"recall_page_entries_shown": "Entries shown: {countLabel}",
1309+
"recall_page_extraction_title": "Extraction status",
1310+
"recall_page_extraction_unconfigured": "Recall extraction is not configured.",
1311+
"recall_page_extraction_error": "Could not load extraction status.",
1312+
"recall_page_status_done": "{countLabel} done",
1313+
"recall_page_status_failed": "{countLabel} failed",
1314+
"recall_page_status_eligible": "{countLabel} eligible",
1315+
"recall_page_status_pending": "{countLabel} pending",
1316+
"recall_page_status_partial": "{countLabel} partial",
1317+
"recall_page_status_units": "Units {doneLabel}/{totalLabel}",
1318+
"recall_page_status_entries": "{countLabel} entries",
12831319
"session_vitals_title": "Analysis",
12841320
"session_vitals_subtitle": "Session vital signs",
12851321
"session_vitals_close": "Close session analysis",

frontend/messages/fr.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nav_more_navigation": "Plus de navigation",
2020
"nav_more": "Plus",
2121
"nav_trends": "Tendances",
22+
"nav_recall": "Rappel",
2223
"nav_pinned": "Épinglés",
2324
"nav_insights": "Analyses",
2425
"nav_trash": "Corbeille",
@@ -1280,6 +1281,41 @@
12801281
"session_recall_provenance_revoked": "Provenance révoquée",
12811282
"session_recall_evidence_range": "Messages {start}–{end}",
12821283
"session_recall_jump_evidence": "Aller aux messages {start}–{end} de la transcription",
1284+
"recall_page_title": "Rappel",
1285+
"recall_page_subtitle": "Parcourir les entrées distillées et la couverture d’extraction.",
1286+
"recall_page_search_placeholder": "Rechercher dans le rappel…",
1287+
"recall_page_clear_search": "Effacer la recherche de rappel",
1288+
"recall_page_type_filter": "Type d’entrée",
1289+
"recall_page_all_types": "Tous les types",
1290+
"recall_page_generation_filter": "Génération d’extraction",
1291+
"recall_page_all_generations": "Toutes les générations",
1292+
"recall_page_review_filter": "État de révision",
1293+
"recall_page_all_review_states": "Tous les états de révision",
1294+
"recall_page_table_label": "Entrées de rappel",
1295+
"recall_page_fact_column": "Fait",
1296+
"recall_page_project_column": "Projet",
1297+
"recall_page_expand_entry": "Développer {title}",
1298+
"recall_page_collapse_entry": "Réduire {title}",
1299+
"recall_page_trigger_label": "Déclencheur",
1300+
"recall_page_uncertainty_label": "Incertitude",
1301+
"recall_page_loading": "Chargement des entrées de rappel…",
1302+
"recall_page_unavailable_read_only": "Le rappel n’est pas disponible en mode lecture seule.",
1303+
"recall_page_empty": "Aucune entrée de rappel ne correspond à ces filtres.",
1304+
"recall_page_error": "Impossible de charger les entrées de rappel.",
1305+
"recall_page_load_more": "Afficher plus",
1306+
"recall_page_loading_more": "Chargement…",
1307+
"recall_page_ranked_result_cap": "La recherche classée est limitée aux {countLabel} premiers résultats.",
1308+
"recall_page_entries_shown": "Entrées affichées : {countLabel}",
1309+
"recall_page_extraction_title": "État de l’extraction",
1310+
"recall_page_extraction_unconfigured": "L’extraction du rappel n’est pas configurée.",
1311+
"recall_page_extraction_error": "Impossible de charger l’état de l’extraction.",
1312+
"recall_page_status_done": "{countLabel} terminées",
1313+
"recall_page_status_failed": "{countLabel} échouées",
1314+
"recall_page_status_eligible": "{countLabel} éligibles",
1315+
"recall_page_status_pending": "{countLabel} en attente",
1316+
"recall_page_status_partial": "{countLabel} partielles",
1317+
"recall_page_status_units": "Unités {doneLabel}/{totalLabel}",
1318+
"recall_page_status_entries": "{countLabel} entrées",
12831319
"session_vitals_title": "Analyse",
12841320
"session_vitals_subtitle": "Signes vitaux de la session",
12851321
"session_vitals_close": "Fermer l'analyse de la session",

frontend/messages/ko.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nav_more_navigation": "추가 내비게이션",
2020
"nav_more": "더 보기",
2121
"nav_trends": "트렌드",
22+
"nav_recall": "리콜",
2223
"nav_pinned": "고정됨",
2324
"nav_insights": "인사이트",
2425
"nav_trash": "휴지통",
@@ -1250,6 +1251,41 @@
12501251
"session_recall_provenance_revoked": "출처가 취소됨",
12511252
"session_recall_evidence_range": "메시지 {start}–{end}",
12521253
"session_recall_jump_evidence": "대화 기록 메시지 {start}–{end}(으)로 이동",
1254+
"recall_page_title": "리콜",
1255+
"recall_page_subtitle": "추출된 항목과 추출 범위를 살펴봅니다.",
1256+
"recall_page_search_placeholder": "리콜 항목 검색…",
1257+
"recall_page_clear_search": "리콜 검색 지우기",
1258+
"recall_page_type_filter": "항목 유형",
1259+
"recall_page_all_types": "모든 유형",
1260+
"recall_page_generation_filter": "추출 세대",
1261+
"recall_page_all_generations": "모든 세대",
1262+
"recall_page_review_filter": "검토 상태",
1263+
"recall_page_all_review_states": "모든 검토 상태",
1264+
"recall_page_table_label": "리콜 항목",
1265+
"recall_page_fact_column": "사실",
1266+
"recall_page_project_column": "프로젝트",
1267+
"recall_page_expand_entry": "{title} 펼치기",
1268+
"recall_page_collapse_entry": "{title} 접기",
1269+
"recall_page_trigger_label": "트리거",
1270+
"recall_page_uncertainty_label": "불확실성",
1271+
"recall_page_loading": "리콜 항목을 불러오는 중…",
1272+
"recall_page_unavailable_read_only": "읽기 전용 모드에서는 리콜을 사용할 수 없습니다.",
1273+
"recall_page_empty": "이 필터와 일치하는 리콜 항목이 없습니다.",
1274+
"recall_page_error": "리콜 항목을 불러올 수 없습니다.",
1275+
"recall_page_load_more": "더 보기",
1276+
"recall_page_loading_more": "더 불러오는 중…",
1277+
"recall_page_ranked_result_cap": "순위 검색은 처음 {countLabel}개의 일치 항목으로 제한됩니다.",
1278+
"recall_page_entries_shown": "표시된 항목: {countLabel}",
1279+
"recall_page_extraction_title": "추출 상태",
1280+
"recall_page_extraction_unconfigured": "리콜 추출이 구성되지 않았습니다.",
1281+
"recall_page_extraction_error": "추출 상태를 불러올 수 없습니다.",
1282+
"recall_page_status_done": "완료 {countLabel}",
1283+
"recall_page_status_failed": "실패 {countLabel}",
1284+
"recall_page_status_eligible": "대상 {countLabel}",
1285+
"recall_page_status_pending": "대기 {countLabel}",
1286+
"recall_page_status_partial": "부분 완료 {countLabel}",
1287+
"recall_page_status_units": "단위 {doneLabel}/{totalLabel}",
1288+
"recall_page_status_entries": "항목 {countLabel}",
12531289
"session_vitals_title": "분석",
12541290
"session_vitals_subtitle": "세션 바이탈 사인",
12551291
"session_vitals_close": "세션 분석 닫기",

frontend/messages/zh-CN.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nav_more_navigation": "更多导航",
2020
"nav_more": "更多",
2121
"nav_trends": "趋势",
22+
"nav_recall": "召回",
2223
"nav_pinned": "已固定",
2324
"nav_insights": "洞察",
2425
"nav_trash": "回收站",
@@ -1248,6 +1249,41 @@
12481249
"session_recall_provenance_revoked": "来源已撤销",
12491250
"session_recall_evidence_range": "消息 {start}–{end}",
12501251
"session_recall_jump_evidence": "跳转到转录消息 {start}–{end}",
1252+
"recall_page_title": "召回",
1253+
"recall_page_subtitle": "浏览提炼条目和提取覆盖情况。",
1254+
"recall_page_search_placeholder": "搜索召回条目…",
1255+
"recall_page_clear_search": "清除召回搜索",
1256+
"recall_page_type_filter": "条目类型",
1257+
"recall_page_all_types": "所有类型",
1258+
"recall_page_generation_filter": "提取代次",
1259+
"recall_page_all_generations": "所有代次",
1260+
"recall_page_review_filter": "审核状态",
1261+
"recall_page_all_review_states": "所有审核状态",
1262+
"recall_page_table_label": "召回条目",
1263+
"recall_page_fact_column": "事实",
1264+
"recall_page_project_column": "项目",
1265+
"recall_page_expand_entry": "展开 {title}",
1266+
"recall_page_collapse_entry": "收起 {title}",
1267+
"recall_page_trigger_label": "触发条件",
1268+
"recall_page_uncertainty_label": "不确定性",
1269+
"recall_page_loading": "正在加载召回条目…",
1270+
"recall_page_unavailable_read_only": "只读模式下无法使用召回。",
1271+
"recall_page_empty": "没有符合这些筛选条件的召回条目。",
1272+
"recall_page_error": "无法加载召回条目。",
1273+
"recall_page_load_more": "加载更多",
1274+
"recall_page_loading_more": "正在加载更多…",
1275+
"recall_page_ranked_result_cap": "排序搜索仅显示前 {countLabel} 个匹配项。",
1276+
"recall_page_entries_shown": "显示条目:{countLabel}",
1277+
"recall_page_extraction_title": "提取状态",
1278+
"recall_page_extraction_unconfigured": "尚未配置召回提取。",
1279+
"recall_page_extraction_error": "无法加载提取状态。",
1280+
"recall_page_status_done": "已完成 {countLabel}",
1281+
"recall_page_status_failed": "失败 {countLabel}",
1282+
"recall_page_status_eligible": "符合条件 {countLabel}",
1283+
"recall_page_status_pending": "待处理 {countLabel}",
1284+
"recall_page_status_partial": "部分完成 {countLabel}",
1285+
"recall_page_status_units": "单元 {doneLabel}/{totalLabel}",
1286+
"recall_page_status_entries": "条目 {countLabel}",
12511287
"session_vitals_title": "分析",
12521288
"session_vitals_subtitle": "会话指标",
12531289
"session_vitals_close": "关闭会话分析",

frontend/messages/zh-TW.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nav_more_navigation": "更多導航",
2020
"nav_more": "更多",
2121
"nav_trends": "趨勢",
22+
"nav_recall": "召回",
2223
"nav_pinned": "已釘選",
2324
"nav_insights": "洞察",
2425
"nav_trash": "垃圾桶",
@@ -1248,6 +1249,41 @@
12481249
"session_recall_provenance_revoked": "來源已撤銷",
12491250
"session_recall_evidence_range": "訊息 {start}-{end}",
12501251
"session_recall_jump_evidence": "跳至逐字稿訊息 {start}-{end}",
1252+
"recall_page_title": "召回",
1253+
"recall_page_subtitle": "瀏覽提煉項目和擷取涵蓋情況。",
1254+
"recall_page_search_placeholder": "搜尋召回項目…",
1255+
"recall_page_clear_search": "清除召回搜尋",
1256+
"recall_page_type_filter": "項目類型",
1257+
"recall_page_all_types": "所有類型",
1258+
"recall_page_generation_filter": "擷取代次",
1259+
"recall_page_all_generations": "所有代次",
1260+
"recall_page_review_filter": "審核狀態",
1261+
"recall_page_all_review_states": "所有審核狀態",
1262+
"recall_page_table_label": "召回項目",
1263+
"recall_page_fact_column": "事實",
1264+
"recall_page_project_column": "專案",
1265+
"recall_page_expand_entry": "展開 {title}",
1266+
"recall_page_collapse_entry": "收合 {title}",
1267+
"recall_page_trigger_label": "觸發條件",
1268+
"recall_page_uncertainty_label": "不確定性",
1269+
"recall_page_loading": "正在載入召回項目…",
1270+
"recall_page_unavailable_read_only": "唯讀模式下無法使用召回。",
1271+
"recall_page_empty": "沒有符合這些篩選條件的召回項目。",
1272+
"recall_page_error": "無法載入召回項目。",
1273+
"recall_page_load_more": "載入更多",
1274+
"recall_page_loading_more": "正在載入更多…",
1275+
"recall_page_ranked_result_cap": "排序搜尋僅顯示前 {countLabel} 個相符項目。",
1276+
"recall_page_entries_shown": "顯示項目:{countLabel}",
1277+
"recall_page_extraction_title": "擷取狀態",
1278+
"recall_page_extraction_unconfigured": "尚未設定召回擷取。",
1279+
"recall_page_extraction_error": "無法載入擷取狀態。",
1280+
"recall_page_status_done": "已完成 {countLabel}",
1281+
"recall_page_status_failed": "失敗 {countLabel}",
1282+
"recall_page_status_eligible": "符合條件 {countLabel}",
1283+
"recall_page_status_pending": "待處理 {countLabel}",
1284+
"recall_page_status_partial": "部分完成 {countLabel}",
1285+
"recall_page_status_units": "單元 {doneLabel}/{totalLabel}",
1286+
"recall_page_status_entries": "項目 {countLabel}",
12511287
"session_vitals_title": "分析",
12521288
"session_vitals_subtitle": "對話指標",
12531289
"session_vitals_close": "關閉對話分析",

frontend/src/App.svelte

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
<script lang="ts">
4141
import { onMount, untrack } from "svelte";
42+
import { EmptyState } from "@kenn-io/kit-ui";
4243
import AppHeader from "./lib/components/layout/AppHeader.svelte";
4344
import ThreeColumnLayout from "./lib/components/layout/ThreeColumnLayout.svelte";
4445
import SessionBreadcrumb from "./lib/components/layout/SessionBreadcrumb.svelte";
@@ -60,6 +61,7 @@
6061
import UsagePage from "./lib/components/usage/UsagePage.svelte";
6162
import ActivityPage from "./lib/components/activity/ActivityPage.svelte";
6263
import TrendsPage from "./lib/components/trends/TrendsPage.svelte";
64+
import RecallPage from "./lib/components/recall/RecallPage.svelte";
6365
import InsightsPage from "./lib/components/insights/InsightsPage.svelte";
6466
import PinnedPage from "./lib/components/pinned/PinnedPage.svelte";
6567
import TrashPage from "./lib/components/trash/TrashPage.svelte";
@@ -98,6 +100,13 @@
98100
} from "./lib/stores/sessionRouteParams.js";
99101
100102
let globalAuthToken: string = $state("");
103+
const recallModeKnown = $derived(
104+
sync.serverVersion !== null ||
105+
(settings.loaded && settings.error === null),
106+
);
107+
const recallUnavailable = $derived(
108+
sync.readOnly || settings.readOnly,
109+
);
101110
102111
function handleGlobalAuth() {
103112
const token = globalAuthToken.trim();
@@ -667,6 +676,18 @@
667676
<div class="page-scroll">
668677
<TrendsPage />
669678
</div>
679+
{:else if router.route === "recall"}
680+
<div class="page-scroll">
681+
{#if recallUnavailable}
682+
<div class="recall-route-state">
683+
<EmptyState title={m.recall_page_unavailable_read_only()} />
684+
</div>
685+
{:else if recallModeKnown}
686+
<RecallPage />
687+
{:else}
688+
<p class="recall-route-state">{m.recall_page_loading()}</p>
689+
{/if}
690+
</div>
670691
{:else if router.route === "insights"}
671692
<div class="page-scroll">
672693
<InsightsPage />
@@ -792,6 +813,12 @@
792813
overflow: hidden;
793814
}
794815
816+
.recall-route-state {
817+
padding: 32px 24px;
818+
color: var(--text-muted);
819+
font-size: 13px;
820+
}
821+
795822
/* kit-ui-check-ignore: undo toast carries an inline restore action; kit-ui FlashBanner only supports text+dismiss today, so replacing this would change the delete/undo workflow. */
796823
.undo-toast {
797824
position: fixed;

0 commit comments

Comments
 (0)