@gobing-ai/knowledge-kit 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.css +178 -0
  3. package/dist/board/collect/index.js +1164 -345
  4. package/dist/board/daily-voice/index.css +178 -0
  5. package/dist/board/daily-voice/index.js +32 -8
  6. package/dist/board/itc/index.css +178 -0
  7. package/dist/board/itc/index.js +32 -8
  8. package/dist/index.js +3991 -3032
  9. package/package.json +4 -4
  10. package/plugins/generations/content-gen/dist/index.js +11 -11
  11. package/plugins/generations/core-facts-gen/dist/index.js +14 -14
  12. package/plugins/generations/daily-article-gen/dist/index.js +14 -14
  13. package/plugins/generations/dailynews-gen/dist/index.js +11 -11
  14. package/plugins/generations/episode-plan-gen/dist/index.js +14 -14
  15. package/plugins/generations/gemini-tts/dist/index.js +11 -11
  16. package/plugins/generations/image-gen/dist/index.js +11 -11
  17. package/plugins/generations/news-report-gen/dist/index.js +14 -14
  18. package/plugins/generations/voice-gen/dist/index.js +11 -11
  19. package/plugins/generations/voxcpm-tts/dist/index.js +11 -11
  20. package/plugins/ingestions/aihot-ingest/dist/index.js +14 -14
  21. package/plugins/ingestions/horizon-ingest/dist/index.js +14 -14
  22. package/plugins/ingestions/karakeep-local/dist/index.js +15 -15
  23. package/plugins/ingestions/last30days-ingest/dist/index.js +14 -14
  24. package/plugins/ingestions/web-search/dist/index.js +12 -12
  25. package/plugins/kk/commands/workflow-run.md +8 -4
  26. package/plugins/kk/decisions/extract/ai-news.yaml +7 -1
  27. package/plugins/kk/plugin.json +1 -1
  28. package/plugins/kk/scripts/kk-workflow-stages.ts +4 -3
  29. package/plugins/kk/workflows/feature-verification.yaml +1 -9
  30. package/plugins/kk/workflows/kk-itc-author.yaml +12 -9
  31. package/plugins/kk/workflows/kk-itc-publish-tail.yaml +90 -0
  32. package/plugins/kk/workflows/kk-solo-podcast.yaml +6 -0
  33. package/plugins/publishings/bilibili-pub/dist/index.js +24 -24
  34. package/plugins/publishings/bilibili-pub/dist/profile-check.js +83 -32
  35. package/plugins/publishings/cnblogs-pub/dist/index.js +24 -24
  36. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +83 -32
  37. package/plugins/publishings/csdn-pub/dist/index.js +24 -24
  38. package/plugins/publishings/csdn-pub/dist/profile-check.js +83 -32
  39. package/plugins/publishings/emdash-pub/dist/index.js +11 -11
  40. package/plugins/publishings/infoq-pub/dist/index.js +24 -24
  41. package/plugins/publishings/infoq-pub/dist/profile-check.js +83 -32
  42. package/plugins/publishings/jike-pub/dist/index.js +24 -24
  43. package/plugins/publishings/jike-pub/dist/profile-check.js +83 -32
  44. package/plugins/publishings/juejin-pub/dist/index.js +24 -24
  45. package/plugins/publishings/juejin-pub/dist/profile-check.js +83 -32
  46. package/plugins/publishings/medium-pub/dist/index.js +11 -11
  47. package/plugins/publishings/podcast-pub/dist/index.js +11 -11
  48. package/plugins/publishings/qiita-pub/dist/index.js +11 -11
  49. package/plugins/publishings/shipinhao-pub/dist/index.js +24 -24
  50. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +83 -32
  51. package/plugins/publishings/sspai-pub/dist/index.js +24 -24
  52. package/plugins/publishings/sspai-pub/dist/profile-check.js +83 -32
  53. package/plugins/publishings/substack-pub/dist/index.js +24 -24
  54. package/plugins/publishings/substack-pub/dist/profile-check.js +83 -32
  55. package/plugins/publishings/surfdash-pub/dist/index.js +11 -11
  56. package/plugins/publishings/threads-pub/dist/index.js +11 -11
  57. package/plugins/publishings/wechatmp-pub/dist/index.js +29 -25
  58. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +24 -24
  59. package/plugins/publishings/wechatmp-pub/src/modes.ts +11 -1
  60. package/plugins/publishings/weibo-pub/dist/index.js +24 -24
  61. package/plugins/publishings/weibo-pub/dist/profile-check.js +83 -32
  62. package/plugins/publishings/x-pub/dist/index.js +24 -24
  63. package/plugins/publishings/x-pub/dist/profile-check.js +83 -32
  64. package/plugins/publishings/x-pub/plugin.json +18 -0
  65. package/plugins/publishings/xhs-pub/dist/index.js +24 -24
  66. package/plugins/publishings/xhs-pub/dist/profile-check.js +24 -24
  67. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +24 -24
  68. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +83 -32
  69. package/plugins/publishings/zenn-pub/dist/index.js +11 -11
@@ -1,7 +1,30 @@
1
1
  // ../board/src/collect/index.tsx
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState
8
+ } from "react";
3
9
 
4
10
  // ../board/src/shared/api.ts
11
+ var MAX_OVERVIEW_RUNS = 25;
12
+ function storeItemsQueryString(query) {
13
+ const params = new URLSearchParams;
14
+ if (query.page !== undefined)
15
+ params.set("page", String(query.page));
16
+ if (query.source !== undefined)
17
+ params.set("source", query.source);
18
+ if (query.seen !== undefined)
19
+ params.set("seen", query.seen);
20
+ if (query.decision !== undefined && query.value !== undefined) {
21
+ params.set("decision", query.decision);
22
+ params.set("value", query.value);
23
+ }
24
+ if (query.selection !== undefined)
25
+ params.set("selection", query.selection);
26
+ return params.toString();
27
+ }
5
28
  var DEFAULT_READER_ORIGIN = "http://127.0.0.1:4340";
6
29
  function readerOriginFrom(search) {
7
30
  const override = new URLSearchParams(search).get("kkApi");
@@ -63,6 +86,12 @@ function createBoardClient(options) {
63
86
  params.set("cursor", cursor);
64
87
  return getJson(`${board}/api/runs?${params.toString()}`);
65
88
  },
89
+ storeOverview: (runIds) => {
90
+ const params = new URLSearchParams({ runs: runIds.slice(0, MAX_OVERVIEW_RUNS).join(",") });
91
+ return getJson(`${reader}/api/workflows/kk-collect/store/overview?${params.toString()}`);
92
+ },
93
+ storeRun: (runId) => getJson(`${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store`),
94
+ storeItems: (runId, query = {}) => getJson(`${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store/items?${storeItemsQueryString(query)}`),
66
95
  runDetail: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}`),
67
96
  progress: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}/progress`)
68
97
  };
@@ -293,11 +322,124 @@ function artifactViews(results, readerOrigin) {
293
322
  href: artifact.availability === "available" ? `${readerOrigin}/api/workflows/${results.workflow}/runs/${results.runId}/artifacts/${artifact.id}` : null
294
323
  }));
295
324
  }
325
+ var CADENCE_SCHEDULE_HOURS = 6;
326
+ function cadenceState(lastStoredAt, now) {
327
+ const since = lastStoredAt === null ? Number.NaN : Date.parse(lastStoredAt);
328
+ if (Number.isNaN(since)) {
329
+ return { state: "unknown", sinceHours: null, detail: "No stored collection recorded." };
330
+ }
331
+ const sinceHours = Math.max(0, (now - since) / 3600000);
332
+ const state = sinceHours <= CADENCE_SCHEDULE_HOURS ? "on schedule" : sinceHours <= 2 * CADENCE_SCHEDULE_HOURS ? "late" : "stalled";
333
+ return {
334
+ state,
335
+ sinceHours,
336
+ detail: `${sinceHours.toFixed(1)} h since the last stored collection (schedule ${CADENCE_SCHEDULE_HOURS} h).`
337
+ };
338
+ }
339
+ function fallbackShare(row) {
340
+ if (row.labelRows <= 0)
341
+ return "—";
342
+ const fallback = row.labelRowsBySource.default ?? 0;
343
+ return `${Math.round(fallback / row.labelRows * 100)}%`;
344
+ }
345
+ function sourceSegments(sourceCounts) {
346
+ const entries = Object.entries(sourceCounts).filter(([, count]) => Number.isFinite(count) && count > 0).sort(([a], [b]) => a.localeCompare(b));
347
+ const total = entries.reduce((sum, [, count]) => sum + count, 0);
348
+ return entries.map(([source, count]) => ({ source, count, share: total === 0 ? 0 : count / total }));
349
+ }
350
+ function bannerCounts(runs, rows) {
351
+ const counts = { failed: 0, doneWithoutRow: 0, inProgress: 0 };
352
+ for (const run of runs) {
353
+ if (rows[run.id] !== undefined && rows[run.id] !== null)
354
+ continue;
355
+ if (run.status === "failed")
356
+ counts.failed += 1;
357
+ else if (run.status === "done")
358
+ counts.doneWithoutRow += 1;
359
+ else if (run.status === "running")
360
+ counts.inProgress += 1;
361
+ }
362
+ return counts;
363
+ }
364
+ var NAMED_MAKERS = ["heuristic", "model", "default"];
365
+ function makerBreakdown(labelRowsBySource) {
366
+ const named = NAMED_MAKERS.map((maker) => `${labelRowsBySource[maker] ?? 0} ${maker}`);
367
+ const other = Object.entries(labelRowsBySource).filter(([maker]) => !NAMED_MAKERS.includes(maker)).reduce((sum, [, count]) => sum + count, 0);
368
+ return (other > 0 ? [...named, `${other} other`] : named).join(" · ");
369
+ }
370
+ function funnelCards(row, results) {
371
+ if (row === null) {
372
+ const unavailable = {
373
+ title: "Collected",
374
+ value: "unavailable",
375
+ unit: "items",
376
+ detail: "This run wrote no store row.",
377
+ availability: "unavailable"
378
+ };
379
+ return [unavailable];
380
+ }
381
+ const segments = sourceSegments(row.sourceCounts);
382
+ const collected = segments.reduce((sum, segment) => sum + segment.count, 0);
383
+ const unchanged = results === null ? null : collectSummary(results).unchanged;
384
+ const stored = row.inserted + row.updated;
385
+ const yieldTotal = row.yield.selected + row.yield.published + row.yield.dropped;
386
+ return [
387
+ {
388
+ title: "Collected",
389
+ value: String(collected),
390
+ unit: "items",
391
+ detail: segments.length === 0 ? "No source counts recorded." : segments.map((s) => `${s.source} ${s.count}`).join(" · "),
392
+ availability: "available"
393
+ },
394
+ {
395
+ title: "Stored",
396
+ value: String(stored),
397
+ unit: "items",
398
+ detail: `${row.inserted} inserted · ${row.updated} updated · ${unchanged === null ? "unavailable" : `${unchanged} unchanged`}`,
399
+ availability: "available"
400
+ },
401
+ {
402
+ title: "Labeled",
403
+ value: String(row.labeledItems),
404
+ unit: "items",
405
+ detail: `${row.labelRows} label rows: ${makerBreakdown(row.labelRowsBySource)}`,
406
+ availability: "available"
407
+ },
408
+ {
409
+ title: "Downstream",
410
+ value: String(yieldTotal),
411
+ unit: "items",
412
+ detail: `${row.yield.selected} selected · ${row.yield.published} published · ${row.yield.dropped} dropped`,
413
+ availability: "available"
414
+ }
415
+ ];
416
+ }
417
+ function decisionValues(quality, decisionId) {
418
+ const entry = quality.find((candidate) => candidate.decisionId === decisionId);
419
+ return entry === undefined ? [] : entry.run.map((row) => row.value);
420
+ }
421
+ function runDuration(startedAt, completedAt) {
422
+ if (startedAt === null || startedAt === undefined)
423
+ return "—";
424
+ if (completedAt === null || completedAt === undefined)
425
+ return "—";
426
+ const end = Date.parse(completedAt);
427
+ if (Number.isNaN(Date.parse(startedAt)) || Number.isNaN(end))
428
+ return "—";
429
+ return formatElapsed(startedAt, end);
430
+ }
431
+ function pager(page, total, pageSize = 25) {
432
+ return { page, total, pages: Math.max(1, Math.ceil(total / pageSize)) };
433
+ }
296
434
 
297
435
  // ../board/src/shared/refresh.ts
298
436
  var ACTIVE_POLL_MS = 5000;
299
437
  var HISTORY_POLL_MS = 30000;
300
438
  var MAX_BACKOFF_MS = 30000;
439
+ function requestSequence() {
440
+ let newest = 0;
441
+ return { begin: () => ++newest, isCurrent: (token) => token === newest };
442
+ }
301
443
  function initialRefresh(intervalMs) {
302
444
  return { pending: false, failures: 0, backoffMs: intervalMs, lastSuccessAt: null, failingSince: null };
303
445
  }
@@ -332,22 +474,25 @@ function freshnessLabel(state) {
332
474
 
333
475
  // ../board/src/shared/run-selection.ts
334
476
  var BOARD_TABS = ["summary", "results", "progress", "config"];
335
- function isBoardTab(value) {
336
- return value !== null && value !== undefined && BOARD_TABS.includes(value);
337
- }
338
- function parseDeepLink(search) {
477
+ var COLLECT_TABS = ["overview", "summary", "items", "progress", "config"];
478
+ var COLLECT_TAB_ALIASES = { results: "summary" };
479
+ function parseDeepLink(search, tabs = BOARD_TABS, aliases = {}) {
339
480
  const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
340
481
  const diagnostics = [];
341
482
  const rawTab = params.get("tab");
342
483
  const rawRun = params.get("run");
484
+ const known = tabs;
343
485
  let tab;
344
486
  if (rawTab !== null) {
345
- if (isBoardTab(rawTab)) {
487
+ const aliased = Object.hasOwn(aliases, rawTab) ? aliases[rawTab] : undefined;
488
+ if (known.includes(rawTab)) {
346
489
  tab = rawTab;
490
+ } else if (aliased !== undefined && known.includes(aliased)) {
491
+ tab = aliased;
347
492
  } else {
348
493
  diagnostics.push({
349
494
  code: "board.deeplink.unknown-tab",
350
- message: `Unknown tab '${rawTab}'; showing Summary.`
495
+ message: `Unknown tab '${rawTab}'; showing the default tab.`
351
496
  });
352
497
  }
353
498
  }
@@ -361,8 +506,8 @@ function parseDeepLink(search) {
361
506
  }
362
507
  return { ...runId === undefined ? {} : { runId }, ...tab === undefined ? {} : { tab }, diagnostics };
363
508
  }
364
- function resolveSelection(rows, link) {
365
- const tab = link.tab ?? "summary";
509
+ function resolveSelection(rows, link, defaultTab = "summary") {
510
+ const tab = link.tab ?? defaultTab;
366
511
  const diagnostics = [...link.diagnostics];
367
512
  if (link.runId !== undefined) {
368
513
  if (rows.some((row) => row.id === link.runId)) {
@@ -406,380 +551,315 @@ function mergeRunPages(existing, page) {
406
551
  return { runs: merged, count: merged.length, nextCursor: page.nextCursor, hasMore: page.hasMore };
407
552
  }
408
553
 
409
- // ../board/src/collect/index.tsx
410
- import { jsx, jsxs } from "react/jsx-runtime";
411
- var TAB_LABELS = {
412
- summary: "Summary",
413
- results: "Results",
414
- progress: "Execution progress",
415
- config: "Workflow Config Summary"
416
- };
417
- function messageOf(error) {
418
- return error instanceof Error ? error.message : String(error);
554
+ // ../board/src/collect/panels.tsx
555
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
556
+ function cadenceBadge(cadence) {
557
+ switch (cadence?.state) {
558
+ case "on schedule":
559
+ return "kk-badge-success";
560
+ case "late":
561
+ return "kk-badge-warning";
562
+ case "stalled":
563
+ return "kk-badge-failed";
564
+ default:
565
+ return "kk-badge-neutral";
566
+ }
419
567
  }
420
- function usePolling(loader, intervalMs, refresh) {
421
- const loaderRef = useRef(loader);
422
- loaderRef.current = loader;
423
- const refreshRef = useRef(refresh);
424
- refreshRef.current = refresh;
425
- useEffect(() => {
426
- let cancelled = false;
427
- let timer;
428
- const loop = async () => {
429
- if (!document.hidden) {
430
- await loaderRef.current();
431
- }
432
- if (!cancelled)
433
- timer = setTimeout(() => void loop(), delayMs(refreshRef.current, intervalMs));
434
- };
435
- loop();
436
- return () => {
437
- cancelled = true;
438
- clearTimeout(timer);
439
- };
440
- }, [intervalMs]);
568
+ function StoreState({ diagnostics }) {
569
+ if (diagnostics.length === 0)
570
+ return null;
571
+ return /* @__PURE__ */ jsx(Fragment, {
572
+ children: dedupeDiagnostics(diagnostics).map((diagnostic) => /* @__PURE__ */ jsx("p", {
573
+ role: "status",
574
+ className: "kk-board-diagnostic",
575
+ children: diagnosticLine(diagnostic)
576
+ }, diagnostic.code))
577
+ });
441
578
  }
442
- function CollectModule() {
443
- const [link] = useState(() => parseDeepLink(window.location.search));
444
- const [reader] = useState(() => readerOriginFrom(window.location.search));
445
- const readerOrigin = reader.origin;
446
- const client = useMemo(() => createBoardClient({ readerOrigin, boardOrigin: window.location.origin }), [readerOrigin]);
447
- const [rows, setRows] = useState([]);
448
- const [cursor, setCursor] = useState(null);
449
- const [selection, setSelection] = useState({ runId: link.runId ?? null, tab: link.tab ?? "summary" });
450
- const [diagnostics, setDiagnostics] = useState(() => [
451
- ...link.diagnostics,
452
- ...reader.diagnostic === null ? [] : [reader.diagnostic]
453
- ]);
454
- const [historyRefresh, setHistoryRefresh] = useState(() => initialRefresh(HISTORY_POLL_MS));
455
- const [readerRefresh, setReaderRefresh] = useState(() => initialRefresh(ACTIVE_POLL_MS));
456
- const [results, setResults] = useState(null);
457
- const [config, setConfig] = useState(null);
458
- const [progress, setProgress] = useState(null);
459
- const [filterQuery, setFilterQuery] = useState("");
460
- const historyBusy = useRef(false);
461
- const readerBusy = useRef(false);
462
- const loadHistory = useCallback(async () => {
463
- if (historyBusy.current)
464
- return;
465
- historyBusy.current = true;
466
- setHistoryRefresh((state) => tick(state));
467
- try {
468
- const page = await client.runs("kk-collect");
469
- setRows((existing) => mergeRunPages(existing, page).runs);
470
- setCursor(page.nextCursor);
471
- setHistoryRefresh(() => succeeded(Date.now(), HISTORY_POLL_MS));
472
- } catch (error) {
473
- setDiagnostics((existing) => [
474
- ...existing,
475
- { code: "board.history.unavailable", message: messageOf(error) }
476
- ]);
477
- setHistoryRefresh((state) => failed(state, Date.now()));
478
- } finally {
479
- historyBusy.current = false;
480
- }
481
- }, [client]);
482
- const loadReader = useCallback(async () => {
483
- if (readerBusy.current)
484
- return;
485
- const runId = selectionRef.current.runId;
486
- if (runId === null)
487
- return;
488
- readerBusy.current = true;
489
- setReaderRefresh((state) => tick(state));
490
- try {
491
- const [nextResults, nextConfig] = await Promise.all([
492
- client.results("kk-collect", runId),
493
- client.config("kk-collect", runId)
494
- ]);
495
- setResults(nextResults);
496
- setConfig(nextConfig);
497
- setReaderRefresh(() => succeeded(Date.now(), ACTIVE_POLL_MS));
498
- } catch (error) {
499
- setDiagnostics((existing) => [
500
- ...existing,
501
- { code: "board.reader.unavailable", message: messageOf(error) }
502
- ]);
503
- setReaderRefresh((state) => failed(state, Date.now()));
504
- } finally {
505
- readerBusy.current = false;
506
- }
507
- try {
508
- setProgress(await client.progress(runId));
509
- } catch {}
510
- }, [client]);
511
- const selectionRef = useRef(selection);
512
- selectionRef.current = selection;
513
- usePolling(loadHistory, HISTORY_POLL_MS, historyRefresh);
514
- usePolling(loadReader, ACTIVE_POLL_MS, readerRefresh);
515
- useEffect(() => {
516
- if (selection.runId === null && rows.length > 0) {
517
- setSelection((state) => state.runId === null ? resolveSelection(rows, link) : state);
518
- }
519
- }, [rows, selection.runId, link]);
520
- useEffect(() => {
521
- syncSelectionUrl(selection, { location: window.location, history: window.history });
522
- }, [selection]);
523
- const tab = selection.tab;
524
- const now = Date.now();
525
- const facts = results === null ? [] : collectSummaryFacts(results, now);
526
- const artifacts = results === null ? [] : artifactViews(results, readerOrigin);
527
- const steps = progressRows(progress);
528
- const selectedRow = rows.find((row) => row.id === selection.runId);
529
- const filteredRows = useMemo(() => {
530
- const q = filterQuery.trim().toLowerCase();
531
- if (q === "")
532
- return rows;
533
- return rows.filter((r) => r.id.toLowerCase().includes(q) || r.status.toLowerCase().includes(q));
534
- }, [rows, filterQuery]);
535
- return /* @__PURE__ */ jsxs("div", {
536
- className: "kk-board kk-board-collect",
579
+ function storeCellText(status) {
580
+ return status === "running" ? "in progress" : "no store row";
581
+ }
582
+ function OverviewPanel({
583
+ runs,
584
+ overview,
585
+ now,
586
+ runHref
587
+ }) {
588
+ const page = runs.slice(0, MAX_OVERVIEW_RUNS);
589
+ const storeState = overview === null ? "loading" : overview.availability;
590
+ const counts = overview !== null && storeState === "available" ? bannerCounts(page, overview.rows) : null;
591
+ const cadence = overview === null ? null : cadenceState(overview.lastStoredAt, now);
592
+ return /* @__PURE__ */ jsxs("section", {
593
+ className: "kk-card kk-collect-overview",
537
594
  children: [
538
- /* @__PURE__ */ jsxs("header", {
539
- className: "kk-board-header",
595
+ /* @__PURE__ */ jsxs("div", {
596
+ className: "kk-collect-banner",
540
597
  children: [
541
598
  /* @__PURE__ */ jsxs("div", {
542
- className: "kk-board-header-left",
599
+ className: "kk-collect-banner-cell",
543
600
  children: [
544
- /* @__PURE__ */ jsxs("div", {
545
- className: "kk-board-header-title-row",
546
- children: [
547
- /* @__PURE__ */ jsx("h1", {
548
- children: "Collect"
549
- }),
550
- selectedRow ? /* @__PURE__ */ jsx("span", {
551
- className: `kk-badge ${selectedRow.status === "done" ? "kk-badge-success" : selectedRow.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
552
- children: selectedRow.status
553
- }) : null
554
- ]
601
+ /* @__PURE__ */ jsx("span", {
602
+ className: "kk-stat-label",
603
+ children: "Collection cadence"
555
604
  }),
556
- /* @__PURE__ */ jsxs("p", {
557
- children: [
558
- "Run ",
559
- /* @__PURE__ */ jsx("code", {
560
- children: selection.runId ?? "none selected"
561
- }),
562
- " · ",
563
- freshnessLabel(readerRefresh),
564
- results === null ? "" : ` · Results ${results.availability}`
565
- ]
605
+ /* @__PURE__ */ jsx("span", {
606
+ className: "kk-collect-banner-value",
607
+ children: /* @__PURE__ */ jsx("span", {
608
+ className: `kk-badge ${cadenceBadge(cadence)}`,
609
+ children: cadence?.state ?? "unknown"
610
+ })
611
+ }),
612
+ /* @__PURE__ */ jsx("span", {
613
+ className: "kk-stat-sub",
614
+ children: cadence?.detail ?? "The store has not answered yet."
566
615
  })
567
616
  ]
568
617
  }),
569
- /* @__PURE__ */ jsxs("div", {
570
- className: "kk-board-header-right",
618
+ counts === null ? null : /* @__PURE__ */ jsxs("div", {
619
+ className: "kk-collect-banner-cell",
571
620
  children: [
572
- /* @__PURE__ */ jsxs("div", {
573
- className: "kk-board-run-filter-group",
574
- children: [
575
- /* @__PURE__ */ jsx("input", {
576
- type: "text",
577
- className: "kk-board-run-filter-input",
578
- placeholder: "Filter runs...",
579
- value: filterQuery,
580
- onChange: (e) => setFilterQuery(e.target.value),
581
- "aria-label": "Filter runs"
582
- }),
583
- /* @__PURE__ */ jsxs("label", {
584
- className: "kk-board-run-selector",
585
- children: [
586
- "Run",
587
- /* @__PURE__ */ jsxs("select", {
588
- value: selection.runId ?? "",
589
- onChange: (event) => setSelection((state) => ({ ...state, runId: event.target.value || null })),
590
- children: [
591
- /* @__PURE__ */ jsx("option", {
592
- value: "",
593
- children: rows.length === 0 ? "No runs recorded" : filteredRows.length === 0 ? "No matching runs" : "Select a run"
594
- }),
595
- filteredRows.map((row) => /* @__PURE__ */ jsxs("option", {
596
- value: row.id,
597
- children: [
598
- row.id,
599
- " — ",
600
- row.status
601
- ]
602
- }, row.id))
603
- ]
604
- })
605
- ]
606
- })
607
- ]
621
+ /* @__PURE__ */ jsx("span", {
622
+ className: "kk-stat-label",
623
+ children: "Needs attention"
608
624
  }),
609
- /* @__PURE__ */ jsx("button", {
610
- type: "button",
611
- onClick: () => void loadReader(),
612
- title: "Refresh run data",
613
- children: "↻ Refresh"
625
+ /* @__PURE__ */ jsx("span", {
626
+ className: "kk-stat-value",
627
+ children: counts.failed + counts.doneWithoutRow
614
628
  }),
615
- selection.runId === null ? null : /* @__PURE__ */ jsx("a", {
616
- href: selectionPath(selection, window.location.search),
617
- className: "kk-btn-action",
618
- title: "Permanent link",
619
- children: "\uD83D\uDD17 Link"
629
+ /* @__PURE__ */ jsxs("span", {
630
+ className: "kk-stat-sub",
631
+ children: [
632
+ counts.failed,
633
+ " failed · ",
634
+ counts.doneWithoutRow,
635
+ " done without a store row ·",
636
+ " ",
637
+ counts.inProgress,
638
+ " in progress"
639
+ ]
620
640
  })
621
641
  ]
622
642
  })
623
643
  ]
624
644
  }),
625
- /* @__PURE__ */ jsx("nav", {
626
- className: "kk-board-tabs",
627
- "aria-label": "Collect tabs",
628
- children: BOARD_TABS.map((candidate) => /* @__PURE__ */ jsx("button", {
629
- type: "button",
630
- "aria-current": candidate === tab ? "page" : undefined,
631
- onClick: () => setSelection((state) => ({ ...state, tab: candidate })),
632
- children: TAB_LABELS[candidate]
633
- }, candidate))
634
- }),
635
- rows.length === 0 ? /* @__PURE__ */ jsxs("p", {
636
- className: "kk-board-empty",
645
+ overview !== null && overview.availability === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
637
646
  children: [
638
- "No runs recorded for kk-collect. Configure the workflow in",
639
- " ",
640
- /* @__PURE__ */ jsx("a", {
641
- href: "/board/settings/workflows",
642
- children: "Settings → Workflows"
647
+ /* @__PURE__ */ jsx("p", {
648
+ children: "The store could not be read for this page; no counts are shown."
643
649
  }),
644
- "."
650
+ /* @__PURE__ */ jsx(StoreState, {
651
+ diagnostics: overview.diagnostics
652
+ })
645
653
  ]
646
654
  }) : null,
647
- tab === "summary" ? /* @__PURE__ */ jsx(SummaryPanel, {
648
- facts,
649
- results
650
- }) : null,
651
- tab === "results" ? /* @__PURE__ */ jsx(ResultsPanel, {
652
- artifacts
653
- }) : null,
654
- tab === "progress" ? /* @__PURE__ */ jsx(ProgressPanel, {
655
- steps
656
- }) : null,
657
- tab === "config" ? /* @__PURE__ */ jsx(ConfigPanel, {
658
- config
655
+ overview === null ? /* @__PURE__ */ jsx("p", {
656
+ children: "The store overview has not loaded yet."
659
657
  }) : null,
660
- dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).map((diagnostic) => /* @__PURE__ */ jsx("p", {
661
- role: "status",
662
- className: "kk-board-diagnostic",
663
- children: diagnosticLine(diagnostic)
664
- }, diagnostic.code)),
665
- cursor === null ? null : /* @__PURE__ */ jsx("button", {
666
- type: "button",
667
- onClick: () => void loadHistory(),
668
- children: "Load more runs"
658
+ /* @__PURE__ */ jsx("div", {
659
+ className: "kk-table-container",
660
+ children: /* @__PURE__ */ jsxs("table", {
661
+ className: "kk-table kk-collect-table",
662
+ children: [
663
+ /* @__PURE__ */ jsx("thead", {
664
+ children: /* @__PURE__ */ jsxs("tr", {
665
+ children: [
666
+ /* @__PURE__ */ jsx("th", {
667
+ scope: "col",
668
+ children: "Run"
669
+ }),
670
+ /* @__PURE__ */ jsx("th", {
671
+ scope: "col",
672
+ children: "Started"
673
+ }),
674
+ /* @__PURE__ */ jsx("th", {
675
+ scope: "col",
676
+ children: "Duration"
677
+ }),
678
+ /* @__PURE__ */ jsx("th", {
679
+ scope: "col",
680
+ children: "Status"
681
+ }),
682
+ /* @__PURE__ */ jsx("th", {
683
+ scope: "col",
684
+ children: "Collected"
685
+ }),
686
+ /* @__PURE__ */ jsx("th", {
687
+ scope: "col",
688
+ children: "Inserted"
689
+ }),
690
+ /* @__PURE__ */ jsx("th", {
691
+ scope: "col",
692
+ children: "Updated"
693
+ }),
694
+ /* @__PURE__ */ jsx("th", {
695
+ scope: "col",
696
+ children: "Label rows"
697
+ }),
698
+ /* @__PURE__ */ jsx("th", {
699
+ scope: "col",
700
+ children: "Fallback"
701
+ }),
702
+ /* @__PURE__ */ jsx("th", {
703
+ scope: "col",
704
+ children: "Yield"
705
+ })
706
+ ]
707
+ })
708
+ }),
709
+ /* @__PURE__ */ jsx("tbody", {
710
+ children: page.map((run) => {
711
+ const row = overview?.rows[run.id] ?? null;
712
+ return /* @__PURE__ */ jsxs("tr", {
713
+ children: [
714
+ /* @__PURE__ */ jsx("td", {
715
+ children: /* @__PURE__ */ jsx("a", {
716
+ className: "kk-collect-run-link",
717
+ href: runHref(run.id),
718
+ children: run.id
719
+ })
720
+ }),
721
+ /* @__PURE__ */ jsx("td", {
722
+ children: formatTimestamp(run.startedAt)
723
+ }),
724
+ /* @__PURE__ */ jsx("td", {
725
+ children: runDuration(run.startedAt, run.completedAt)
726
+ }),
727
+ /* @__PURE__ */ jsx("td", {
728
+ children: run.status
729
+ }),
730
+ storeState !== "available" ? /* @__PURE__ */ jsx("td", {
731
+ colSpan: 6,
732
+ className: "kk-collect-nostore",
733
+ children: storeState === "loading" ? "store not loaded yet" : "store unavailable"
734
+ }) : row === null ? /* @__PURE__ */ jsx("td", {
735
+ colSpan: 6,
736
+ className: "kk-collect-nostore",
737
+ children: storeCellText(run.status)
738
+ }) : /* @__PURE__ */ jsx(StoredCells, {
739
+ row
740
+ })
741
+ ]
742
+ }, run.id);
743
+ })
744
+ })
745
+ ]
746
+ })
747
+ })
748
+ ]
749
+ });
750
+ }
751
+ function StoredCells({ row }) {
752
+ const segments = sourceSegments(row.sourceCounts);
753
+ return /* @__PURE__ */ jsxs(Fragment, {
754
+ children: [
755
+ /* @__PURE__ */ jsx("td", {
756
+ children: segments.length === 0 ? /* @__PURE__ */ jsx("span", {
757
+ className: "kk-board-state",
758
+ children: "no sources recorded"
759
+ }) : /* @__PURE__ */ jsxs(Fragment, {
760
+ children: [
761
+ /* @__PURE__ */ jsx("span", {
762
+ className: "kk-collect-bar",
763
+ "aria-hidden": "true",
764
+ children: segments.map((segment, index) => /* @__PURE__ */ jsx("span", {
765
+ className: `kk-collect-bar-seg kk-collect-bar-seg-${index % 3}`,
766
+ style: { width: `${segment.share * 100}%` }
767
+ }, segment.source))
768
+ }),
769
+ /* @__PURE__ */ jsx("span", {
770
+ className: "kk-collect-bar-total",
771
+ children: segments.map((segment) => `${segment.source} ${segment.count}`).join(" · ")
772
+ })
773
+ ]
774
+ })
669
775
  }),
670
- /* @__PURE__ */ jsxs("p", {
671
- className: "kk-board-footnote",
776
+ /* @__PURE__ */ jsx("td", {
777
+ children: row.inserted
778
+ }),
779
+ /* @__PURE__ */ jsx("td", {
780
+ children: row.updated
781
+ }),
782
+ /* @__PURE__ */ jsx("td", {
783
+ children: row.labelRows
784
+ }),
785
+ /* @__PURE__ */ jsx("td", {
786
+ children: fallbackShare(row)
787
+ }),
788
+ /* @__PURE__ */ jsxs("td", {
672
789
  children: [
673
- "Run history every ",
674
- HISTORY_POLL_MS / 1000,
675
- "s; selected run every ",
676
- ACTIVE_POLL_MS / 1000,
677
- "s."
790
+ row.yield.selected,
791
+ " selected · ",
792
+ row.yield.published,
793
+ " published · ",
794
+ row.yield.dropped,
795
+ " dropped"
678
796
  ]
679
797
  })
680
798
  ]
681
799
  });
682
800
  }
683
- function SummaryPanel({
801
+ function RunDetailPanel({
802
+ storeRun,
803
+ results,
684
804
  facts,
685
- results
805
+ artifacts,
806
+ showEffective,
807
+ onToggleQuality
686
808
  }) {
687
- if (results === null)
688
- return /* @__PURE__ */ jsx("p", {
689
- children: "No captured results for the selected run."
690
- });
691
- const summary = collectSummary(results);
692
- const totalItems = (summary.inserted ?? 0) + (summary.updated ?? 0) + (summary.unchanged ?? 0);
809
+ const cards = storeRun !== null && storeRun.availability === "available" ? funnelCards(storeRun.row, results) : [];
693
810
  return /* @__PURE__ */ jsxs("div", {
694
- style: { display: "flex", flexDirection: "column", gap: "0.875rem" },
811
+ className: "kk-collect-detail",
695
812
  children: [
696
- summary.labeled !== null || summary.inserted !== null ? /* @__PURE__ */ jsxs("div", {
697
- className: "kk-stat-grid",
813
+ cards.length === 0 ? null : /* @__PURE__ */ jsx("div", {
814
+ className: "kk-stat-grid kk-collect-funnel",
815
+ children: cards.map((card) => /* @__PURE__ */ jsxs("div", {
816
+ className: "kk-stat-card",
817
+ children: [
818
+ /* @__PURE__ */ jsx("span", {
819
+ className: "kk-stat-label",
820
+ children: card.title
821
+ }),
822
+ /* @__PURE__ */ jsx("span", {
823
+ className: "kk-stat-value",
824
+ children: card.value
825
+ }),
826
+ /* @__PURE__ */ jsx("span", {
827
+ className: "kk-stat-sub",
828
+ children: card.unit
829
+ }),
830
+ /* @__PURE__ */ jsx("span", {
831
+ className: "kk-stat-sub",
832
+ children: card.detail
833
+ })
834
+ ]
835
+ }, card.title))
836
+ }),
837
+ storeRun === null ? /* @__PURE__ */ jsx("p", {
838
+ children: "The store has not answered for the selected run yet."
839
+ }) : null,
840
+ storeRun !== null && storeRun.availability === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
698
841
  children: [
699
- /* @__PURE__ */ jsxs("div", {
700
- className: "kk-stat-card",
701
- children: [
702
- /* @__PURE__ */ jsx("span", {
703
- className: "kk-stat-label",
704
- children: "Ingested Items"
705
- }),
706
- /* @__PURE__ */ jsx("span", {
707
- className: "kk-stat-value",
708
- children: totalItems
709
- }),
710
- /* @__PURE__ */ jsxs("span", {
711
- className: "kk-stat-sub",
712
- children: [
713
- summary.inserted ?? 0,
714
- " new · ",
715
- summary.updated ?? 0,
716
- " updated"
717
- ]
718
- })
719
- ]
720
- }),
721
- /* @__PURE__ */ jsxs("div", {
722
- className: "kk-stat-card",
723
- children: [
724
- /* @__PURE__ */ jsx("span", {
725
- className: "kk-stat-label",
726
- children: "Labeled Pool"
727
- }),
728
- /* @__PURE__ */ jsx("span", {
729
- className: "kk-stat-value",
730
- children: summary.labeled ?? "—"
731
- }),
732
- /* @__PURE__ */ jsxs("span", {
733
- className: "kk-stat-sub",
734
- children: [
735
- summary.model ?? 0,
736
- " model · ",
737
- summary.fallback ?? 0,
738
- " fallback"
739
- ]
740
- })
741
- ]
742
- }),
743
- /* @__PURE__ */ jsxs("div", {
744
- className: "kk-stat-card",
745
- children: [
746
- /* @__PURE__ */ jsx("span", {
747
- className: "kk-stat-label",
748
- children: "Verdict"
749
- }),
750
- /* @__PURE__ */ jsx("span", {
751
- className: "kk-stat-value",
752
- children: summary.verdict ?? "—"
753
- }),
754
- /* @__PURE__ */ jsxs("span", {
755
- className: "kk-stat-sub",
756
- children: [
757
- "Confidence: ",
758
- summary.confidence ?? "—"
759
- ]
760
- })
761
- ]
842
+ /* @__PURE__ */ jsx("p", {
843
+ children: "The store could not be read for this run; no counts are shown."
762
844
  }),
763
- /* @__PURE__ */ jsxs("div", {
764
- className: "kk-stat-card",
765
- children: [
766
- /* @__PURE__ */ jsx("span", {
767
- className: "kk-stat-label",
768
- children: "Sources Active"
769
- }),
770
- /* @__PURE__ */ jsx("span", {
771
- className: "kk-stat-value",
772
- children: Object.keys(summary.sourceCounts ?? {}).length
773
- }),
774
- /* @__PURE__ */ jsx("span", {
775
- className: "kk-stat-sub",
776
- children: "Ingest channels"
777
- })
778
- ]
845
+ /* @__PURE__ */ jsx(StoreState, {
846
+ diagnostics: storeRun.diagnostics
779
847
  })
780
848
  ]
781
849
  }) : null,
782
- /* @__PURE__ */ jsx("dl", {
850
+ storeRun !== null && storeRun.row === null && storeRun.availability === "available" ? /* @__PURE__ */ jsx("p", {
851
+ children: "This run wrote no store row."
852
+ }) : null,
853
+ storeRun !== null && storeRun.row !== null ? /* @__PURE__ */ jsx(LabelQuality, {
854
+ quality: storeRun.labelQuality,
855
+ diagnostics: storeRun.diagnostics,
856
+ showEffective,
857
+ onToggle: onToggleQuality
858
+ }) : null,
859
+ results === null ? /* @__PURE__ */ jsx("p", {
860
+ children: "No captured results for the selected run."
861
+ }) : null,
862
+ facts.length === 0 ? null : /* @__PURE__ */ jsx("dl", {
783
863
  className: "kk-board-facts",
784
864
  children: facts.map((fact) => /* @__PURE__ */ jsxs("div", {
785
865
  children: [
@@ -798,11 +878,151 @@ function SummaryPanel({
798
878
  })
799
879
  ]
800
880
  }, fact.label))
881
+ }),
882
+ /* @__PURE__ */ jsx(ArtifactRows, {
883
+ artifacts
801
884
  })
802
885
  ]
803
886
  });
804
887
  }
805
- function ResultsPanel({ artifacts }) {
888
+ function LabelQuality({
889
+ quality,
890
+ diagnostics,
891
+ showEffective,
892
+ onToggle
893
+ }) {
894
+ return /* @__PURE__ */ jsxs("section", {
895
+ className: "kk-card kk-collect-quality",
896
+ children: [
897
+ /* @__PURE__ */ jsxs("div", {
898
+ className: "kk-card-header",
899
+ children: [
900
+ /* @__PURE__ */ jsx("h2", {
901
+ children: "Label quality"
902
+ }),
903
+ /* @__PURE__ */ jsx("button", {
904
+ type: "button",
905
+ onClick: onToggle,
906
+ children: showEffective ? "Show this run" : "Show effective labels"
907
+ })
908
+ ]
909
+ }),
910
+ quality.length === 0 ? /* @__PURE__ */ jsx("p", {
911
+ children: "No label decisions recorded for this run."
912
+ }) : null,
913
+ quality.map((entry) => /* @__PURE__ */ jsxs("div", {
914
+ className: "kk-collect-quality-block",
915
+ children: [
916
+ /* @__PURE__ */ jsx("h3", {
917
+ children: entry.decisionId
918
+ }),
919
+ /* @__PURE__ */ jsx(QualityTable, {
920
+ entry,
921
+ showEffective
922
+ })
923
+ ]
924
+ }, entry.decisionId)),
925
+ /* @__PURE__ */ jsx(StoreState, {
926
+ diagnostics
927
+ })
928
+ ]
929
+ });
930
+ }
931
+ function QualityTable({
932
+ entry,
933
+ showEffective
934
+ }) {
935
+ if (showEffective) {
936
+ if (entry.effective.length === 0)
937
+ return /* @__PURE__ */ jsx("p", {
938
+ children: "Effective label distribution unavailable."
939
+ });
940
+ return /* @__PURE__ */ jsxs("table", {
941
+ className: "kk-table",
942
+ children: [
943
+ /* @__PURE__ */ jsx("thead", {
944
+ children: /* @__PURE__ */ jsxs("tr", {
945
+ children: [
946
+ /* @__PURE__ */ jsx("th", {
947
+ scope: "col",
948
+ children: "Value"
949
+ }),
950
+ /* @__PURE__ */ jsx("th", {
951
+ scope: "col",
952
+ children: "Items"
953
+ })
954
+ ]
955
+ })
956
+ }),
957
+ /* @__PURE__ */ jsx("tbody", {
958
+ children: entry.effective.map((value) => /* @__PURE__ */ jsxs("tr", {
959
+ children: [
960
+ /* @__PURE__ */ jsx("td", {
961
+ children: value.value
962
+ }),
963
+ /* @__PURE__ */ jsx("td", {
964
+ children: value.count
965
+ })
966
+ ]
967
+ }, value.value))
968
+ })
969
+ ]
970
+ });
971
+ }
972
+ return /* @__PURE__ */ jsxs("table", {
973
+ className: "kk-table",
974
+ children: [
975
+ /* @__PURE__ */ jsx("thead", {
976
+ children: /* @__PURE__ */ jsxs("tr", {
977
+ children: [
978
+ /* @__PURE__ */ jsx("th", {
979
+ scope: "col",
980
+ children: "Value"
981
+ }),
982
+ /* @__PURE__ */ jsx("th", {
983
+ scope: "col",
984
+ children: "HIGH"
985
+ }),
986
+ /* @__PURE__ */ jsx("th", {
987
+ scope: "col",
988
+ children: "MEDIUM"
989
+ }),
990
+ /* @__PURE__ */ jsx("th", {
991
+ scope: "col",
992
+ children: "LOW"
993
+ }),
994
+ /* @__PURE__ */ jsx("th", {
995
+ scope: "col",
996
+ children: "none"
997
+ })
998
+ ]
999
+ })
1000
+ }),
1001
+ /* @__PURE__ */ jsx("tbody", {
1002
+ children: entry.run.map((row) => /* @__PURE__ */ jsxs("tr", {
1003
+ children: [
1004
+ /* @__PURE__ */ jsx("td", {
1005
+ children: row.value
1006
+ }),
1007
+ /* @__PURE__ */ jsx("td", {
1008
+ children: row.HIGH
1009
+ }),
1010
+ /* @__PURE__ */ jsx("td", {
1011
+ children: row.MEDIUM
1012
+ }),
1013
+ /* @__PURE__ */ jsx("td", {
1014
+ children: row.LOW
1015
+ }),
1016
+ /* @__PURE__ */ jsx("td", {
1017
+ children: row.none
1018
+ })
1019
+ ]
1020
+ }, row.value))
1021
+ })
1022
+ ]
1023
+ });
1024
+ }
1025
+ function ArtifactRows({ artifacts }) {
806
1026
  if (artifacts.length === 0)
807
1027
  return /* @__PURE__ */ jsx("p", {
808
1028
  children: "No artifact evidence for the selected run."
@@ -827,6 +1047,222 @@ function ResultsPanel({ artifacts }) {
827
1047
  }, artifact.id))
828
1048
  });
829
1049
  }
1050
+ function FilterSelect({
1051
+ label,
1052
+ name,
1053
+ value,
1054
+ options,
1055
+ onChange
1056
+ }) {
1057
+ return /* @__PURE__ */ jsxs("label", {
1058
+ className: "kk-collect-filter",
1059
+ children: [
1060
+ label,
1061
+ /* @__PURE__ */ jsxs("select", {
1062
+ name,
1063
+ value: value ?? "",
1064
+ onChange,
1065
+ children: [
1066
+ /* @__PURE__ */ jsx("option", {
1067
+ value: "",
1068
+ children: "All"
1069
+ }),
1070
+ options.map((option) => /* @__PURE__ */ jsx("option", {
1071
+ value: option,
1072
+ children: option
1073
+ }, option))
1074
+ ]
1075
+ })
1076
+ ]
1077
+ });
1078
+ }
1079
+ function ItemsPanel({
1080
+ runId,
1081
+ items,
1082
+ filters,
1083
+ quality,
1084
+ onFilterChange,
1085
+ onPageChange
1086
+ }) {
1087
+ const current = items !== null && items.runId === runId ? items : null;
1088
+ const storeState = current === null ? "loading" : current.availability === "available" ? "available" : "unavailable";
1089
+ const page = storeState === "available" ? current : null;
1090
+ const count = page === null ? null : pager(page.page, page.total, page.pageSize);
1091
+ return /* @__PURE__ */ jsxs("section", {
1092
+ className: "kk-card kk-collect-items",
1093
+ children: [
1094
+ /* @__PURE__ */ jsxs("div", {
1095
+ className: "kk-collect-filters",
1096
+ children: [
1097
+ /* @__PURE__ */ jsx(FilterSelect, {
1098
+ label: "Source",
1099
+ name: "source",
1100
+ value: filters.source,
1101
+ options: page?.sources ?? [],
1102
+ onChange: onFilterChange
1103
+ }),
1104
+ /* @__PURE__ */ jsx(FilterSelect, {
1105
+ label: "Seen",
1106
+ name: "seen",
1107
+ value: filters.seen,
1108
+ options: ["new", "reseen"],
1109
+ onChange: onFilterChange
1110
+ }),
1111
+ /* @__PURE__ */ jsx(FilterSelect, {
1112
+ label: "Decision",
1113
+ name: "decision",
1114
+ value: filters.decision,
1115
+ options: quality.map((entry) => entry.decisionId),
1116
+ onChange: onFilterChange
1117
+ }),
1118
+ /* @__PURE__ */ jsx(FilterSelect, {
1119
+ label: "Value",
1120
+ name: "value",
1121
+ value: filters.value,
1122
+ options: decisionValues(quality, filters.decision ?? ""),
1123
+ onChange: onFilterChange
1124
+ }),
1125
+ /* @__PURE__ */ jsx(FilterSelect, {
1126
+ label: "Selection",
1127
+ name: "selection",
1128
+ value: filters.selection,
1129
+ options: ["selected", "published", "dropped", "none"],
1130
+ onChange: onFilterChange
1131
+ })
1132
+ ]
1133
+ }),
1134
+ runId === null ? /* @__PURE__ */ jsx("p", {
1135
+ children: "Select a run to browse its items."
1136
+ }) : null,
1137
+ runId !== null && storeState === "loading" ? /* @__PURE__ */ jsx("p", {
1138
+ children: "The items page has not loaded yet."
1139
+ }) : null,
1140
+ storeState === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
1141
+ children: [
1142
+ /* @__PURE__ */ jsx("p", {
1143
+ children: "The store could not be read for this run; no items are shown."
1144
+ }),
1145
+ /* @__PURE__ */ jsx(StoreState, {
1146
+ diagnostics: current?.diagnostics ?? []
1147
+ })
1148
+ ]
1149
+ }) : null,
1150
+ page !== null && page.items.length === 0 ? /* @__PURE__ */ jsx("p", {
1151
+ children: "No items match these filters."
1152
+ }) : null,
1153
+ page === null || page.items.length === 0 ? null : /* @__PURE__ */ jsx("div", {
1154
+ className: "kk-table-container",
1155
+ children: /* @__PURE__ */ jsxs("table", {
1156
+ className: "kk-table kk-collect-items-table",
1157
+ children: [
1158
+ /* @__PURE__ */ jsx("thead", {
1159
+ children: /* @__PURE__ */ jsxs("tr", {
1160
+ children: [
1161
+ /* @__PURE__ */ jsx("th", {
1162
+ scope: "col",
1163
+ children: "Title"
1164
+ }),
1165
+ /* @__PURE__ */ jsx("th", {
1166
+ scope: "col",
1167
+ children: "Source"
1168
+ }),
1169
+ /* @__PURE__ */ jsx("th", {
1170
+ scope: "col",
1171
+ children: "Seen"
1172
+ }),
1173
+ /* @__PURE__ */ jsx("th", {
1174
+ scope: "col",
1175
+ children: "Story"
1176
+ }),
1177
+ /* @__PURE__ */ jsx("th", {
1178
+ scope: "col",
1179
+ children: "Labels"
1180
+ }),
1181
+ /* @__PURE__ */ jsx("th", {
1182
+ scope: "col",
1183
+ children: "Selection"
1184
+ })
1185
+ ]
1186
+ })
1187
+ }),
1188
+ /* @__PURE__ */ jsx("tbody", {
1189
+ children: page.items.map((item) => /* @__PURE__ */ jsx(ItemRow, {
1190
+ item
1191
+ }, item.id))
1192
+ })
1193
+ ]
1194
+ })
1195
+ }),
1196
+ count === null ? null : /* @__PURE__ */ jsxs("div", {
1197
+ className: "kk-collect-pager",
1198
+ children: [
1199
+ /* @__PURE__ */ jsx("button", {
1200
+ type: "button",
1201
+ name: "previous",
1202
+ disabled: count.page <= 1,
1203
+ onClick: onPageChange,
1204
+ children: "Previous"
1205
+ }),
1206
+ /* @__PURE__ */ jsxs("span", {
1207
+ children: [
1208
+ "Page ",
1209
+ count.page,
1210
+ " of ",
1211
+ count.pages,
1212
+ " · ",
1213
+ count.total,
1214
+ " items"
1215
+ ]
1216
+ }),
1217
+ /* @__PURE__ */ jsx("button", {
1218
+ type: "button",
1219
+ name: "next",
1220
+ disabled: count.page >= count.pages,
1221
+ onClick: onPageChange,
1222
+ children: "Next"
1223
+ })
1224
+ ]
1225
+ })
1226
+ ]
1227
+ });
1228
+ }
1229
+ function ItemRow({ item }) {
1230
+ const labels = Object.entries(item.labels);
1231
+ return /* @__PURE__ */ jsxs("tr", {
1232
+ children: [
1233
+ /* @__PURE__ */ jsx("td", {
1234
+ children: item.url === null ? item.title : /* @__PURE__ */ jsx("a", {
1235
+ href: item.url,
1236
+ target: "_blank",
1237
+ rel: "noreferrer",
1238
+ children: item.title
1239
+ })
1240
+ }),
1241
+ /* @__PURE__ */ jsx("td", {
1242
+ children: item.source
1243
+ }),
1244
+ /* @__PURE__ */ jsx("td", {
1245
+ children: item.seen
1246
+ }),
1247
+ /* @__PURE__ */ jsx("td", {
1248
+ children: item.storyId ?? "—"
1249
+ }),
1250
+ /* @__PURE__ */ jsx("td", {
1251
+ children: labels.length === 0 ? "—" : labels.map(([decision, value]) => /* @__PURE__ */ jsxs("span", {
1252
+ className: "kk-chip",
1253
+ children: [
1254
+ decision,
1255
+ ": ",
1256
+ value
1257
+ ]
1258
+ }, decision))
1259
+ }),
1260
+ /* @__PURE__ */ jsx("td", {
1261
+ children: item.selection
1262
+ })
1263
+ ]
1264
+ });
1265
+ }
830
1266
  function ProgressPanel({ steps }) {
831
1267
  if (steps.length === 0)
832
1268
  return /* @__PURE__ */ jsx("p", {
@@ -907,6 +1343,389 @@ function ConfigPanel({ config }) {
907
1343
  ]
908
1344
  });
909
1345
  }
1346
+
1347
+ // ../board/src/collect/index.tsx
1348
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1349
+ var TAB_LABELS = {
1350
+ overview: "Overview",
1351
+ summary: "Run detail",
1352
+ items: "Items",
1353
+ progress: "Execution progress",
1354
+ config: "Workflow Config Summary"
1355
+ };
1356
+ function messageOf(error) {
1357
+ return error instanceof Error ? error.message : String(error);
1358
+ }
1359
+ function usePolling(loader, intervalMs, refresh) {
1360
+ const loaderRef = useRef(loader);
1361
+ loaderRef.current = loader;
1362
+ const refreshRef = useRef(refresh);
1363
+ refreshRef.current = refresh;
1364
+ useEffect(() => {
1365
+ let cancelled = false;
1366
+ let timer;
1367
+ const loop = async () => {
1368
+ if (!document.hidden) {
1369
+ await loaderRef.current();
1370
+ }
1371
+ if (!cancelled)
1372
+ timer = setTimeout(() => void loop(), delayMs(refreshRef.current, intervalMs));
1373
+ };
1374
+ loop();
1375
+ return () => {
1376
+ cancelled = true;
1377
+ clearTimeout(timer);
1378
+ };
1379
+ }, [intervalMs]);
1380
+ }
1381
+ function CollectModule() {
1382
+ const [link] = useState(() => parseDeepLink(window.location.search, COLLECT_TABS, COLLECT_TAB_ALIASES));
1383
+ const [reader] = useState(() => readerOriginFrom(window.location.search));
1384
+ const readerOrigin = reader.origin;
1385
+ const client = useMemo(() => createBoardClient({ readerOrigin, boardOrigin: window.location.origin }), [readerOrigin]);
1386
+ const [rows, setRows] = useState([]);
1387
+ const [cursor, setCursor] = useState(null);
1388
+ const [selection, setSelection] = useState({
1389
+ runId: link.runId ?? null,
1390
+ tab: link.tab ?? "overview"
1391
+ });
1392
+ const [diagnostics, setDiagnostics] = useState(() => [
1393
+ ...link.diagnostics,
1394
+ ...reader.diagnostic === null ? [] : [reader.diagnostic]
1395
+ ]);
1396
+ const [historyRefresh, setHistoryRefresh] = useState(() => initialRefresh(HISTORY_POLL_MS));
1397
+ const [readerRefresh, setReaderRefresh] = useState(() => initialRefresh(ACTIVE_POLL_MS));
1398
+ const [results, setResults] = useState(null);
1399
+ const [config, setConfig] = useState(null);
1400
+ const [progress, setProgress] = useState(null);
1401
+ const [overview, setOverview] = useState(null);
1402
+ const [storeRun, setStoreRun] = useState(null);
1403
+ const [items, setItems] = useState(null);
1404
+ const [itemFilters, setItemFilters] = useState({ page: 1 });
1405
+ const [showEffective, setShowEffective] = useState(false);
1406
+ const [filterQuery, setFilterQuery] = useState("");
1407
+ const historyBusy = useRef(false);
1408
+ const readerBusy = useRef(false);
1409
+ const rowsRef = useRef(rows);
1410
+ rowsRef.current = rows;
1411
+ const selectionRef = useRef(selection);
1412
+ selectionRef.current = selection;
1413
+ const loadOverview = useCallback(async (runIds) => {
1414
+ if (runIds.length === 0)
1415
+ return;
1416
+ try {
1417
+ setOverview(await client.storeOverview(runIds));
1418
+ } catch (error) {
1419
+ setDiagnostics((existing) => [
1420
+ ...existing,
1421
+ { code: "board.store.unavailable", message: messageOf(error) }
1422
+ ]);
1423
+ }
1424
+ }, [client]);
1425
+ const loadHistory = useCallback(async () => {
1426
+ if (historyBusy.current)
1427
+ return;
1428
+ historyBusy.current = true;
1429
+ setHistoryRefresh((state) => tick(state));
1430
+ let loaded = rowsRef.current;
1431
+ try {
1432
+ const page = await client.runs("kk-collect");
1433
+ loaded = mergeRunPages(rowsRef.current, page).runs;
1434
+ setRows(loaded);
1435
+ setCursor(page.nextCursor);
1436
+ setHistoryRefresh(() => succeeded(Date.now(), HISTORY_POLL_MS));
1437
+ } catch (error) {
1438
+ setDiagnostics((existing) => [
1439
+ ...existing,
1440
+ { code: "board.history.unavailable", message: messageOf(error) }
1441
+ ]);
1442
+ setHistoryRefresh((state) => failed(state, Date.now()));
1443
+ } finally {
1444
+ historyBusy.current = false;
1445
+ }
1446
+ await loadOverview(loaded.slice(0, MAX_OVERVIEW_RUNS).map((row) => row.id));
1447
+ }, [client, loadOverview]);
1448
+ const loadReader = useCallback(async () => {
1449
+ if (readerBusy.current)
1450
+ return;
1451
+ const runId = selectionRef.current.runId;
1452
+ if (runId === null)
1453
+ return;
1454
+ readerBusy.current = true;
1455
+ setReaderRefresh((state) => tick(state));
1456
+ try {
1457
+ const [nextResults, nextConfig] = await Promise.all([
1458
+ client.results("kk-collect", runId),
1459
+ client.config("kk-collect", runId)
1460
+ ]);
1461
+ if (selectionRef.current.runId !== runId)
1462
+ return;
1463
+ setResults(nextResults);
1464
+ setConfig(nextConfig);
1465
+ setReaderRefresh(() => succeeded(Date.now(), ACTIVE_POLL_MS));
1466
+ } catch (error) {
1467
+ if (selectionRef.current.runId !== runId)
1468
+ return;
1469
+ setDiagnostics((existing) => [
1470
+ ...existing,
1471
+ { code: "board.reader.unavailable", message: messageOf(error) }
1472
+ ]);
1473
+ setReaderRefresh((state) => failed(state, Date.now()));
1474
+ } finally {
1475
+ readerBusy.current = false;
1476
+ }
1477
+ try {
1478
+ const nextStoreRun = await client.storeRun(runId);
1479
+ if (selectionRef.current.runId !== runId)
1480
+ return;
1481
+ setStoreRun(nextStoreRun);
1482
+ } catch (error) {
1483
+ if (selectionRef.current.runId !== runId)
1484
+ return;
1485
+ setDiagnostics((existing) => [
1486
+ ...existing,
1487
+ { code: "board.store.run-unavailable", message: messageOf(error) }
1488
+ ]);
1489
+ }
1490
+ try {
1491
+ const nextProgress = await client.progress(runId);
1492
+ if (selectionRef.current.runId !== runId)
1493
+ return;
1494
+ setProgress(nextProgress);
1495
+ } catch {}
1496
+ }, [client]);
1497
+ const itemsRequests = useRef(requestSequence());
1498
+ const loadItems = useCallback(async (runId) => {
1499
+ const token = itemsRequests.current.begin();
1500
+ try {
1501
+ const page = await client.storeItems(runId, itemFilters);
1502
+ if (!itemsRequests.current.isCurrent(token))
1503
+ return;
1504
+ setItems(page);
1505
+ } catch (error) {
1506
+ if (!itemsRequests.current.isCurrent(token))
1507
+ return;
1508
+ setItems(null);
1509
+ setDiagnostics((existing) => [
1510
+ ...existing,
1511
+ { code: "board.store.items-unavailable", message: messageOf(error) }
1512
+ ]);
1513
+ }
1514
+ }, [client, itemFilters]);
1515
+ usePolling(loadHistory, HISTORY_POLL_MS, historyRefresh);
1516
+ usePolling(loadReader, ACTIVE_POLL_MS, readerRefresh);
1517
+ useEffect(() => {
1518
+ if (selection.tab !== "items" || selection.runId === null)
1519
+ return;
1520
+ loadItems(selection.runId);
1521
+ }, [selection.tab, selection.runId, loadItems]);
1522
+ useEffect(() => {
1523
+ if (selection.runId === null && rows.length > 0) {
1524
+ setSelection((state) => state.runId === null ? resolveSelection(rows, link, "overview") : state);
1525
+ }
1526
+ }, [rows, selection.runId, link]);
1527
+ useEffect(() => {
1528
+ syncSelectionUrl(selection, { location: window.location, history: window.history });
1529
+ }, [selection]);
1530
+ const tab = selection.tab;
1531
+ const now = Date.now();
1532
+ const currentStoreRun = storeRun?.runId === selection.runId ? storeRun : null;
1533
+ const facts = results === null ? [] : collectSummaryFacts(results, now);
1534
+ const artifacts = results === null ? [] : artifactViews(results, readerOrigin);
1535
+ const steps = progressRows(progress);
1536
+ const selectedRow = rows.find((row) => row.id === selection.runId);
1537
+ const filteredRows = useMemo(() => {
1538
+ const q = filterQuery.trim().toLowerCase();
1539
+ if (q === "")
1540
+ return rows;
1541
+ return rows.filter((r) => r.id.toLowerCase().includes(q) || r.status.toLowerCase().includes(q));
1542
+ }, [rows, filterQuery]);
1543
+ const runHref = useCallback((runId) => selectionPath({ runId, tab: "summary" }, window.location.search), []);
1544
+ const toggleQuality = useCallback(() => setShowEffective((state) => !state), []);
1545
+ const filterChanged = useCallback((event) => {
1546
+ const name = event.currentTarget.name;
1547
+ const value = event.currentTarget.value === "" ? undefined : event.currentTarget.value;
1548
+ setItemFilters((state) => ({
1549
+ ...state,
1550
+ [name]: value,
1551
+ page: 1,
1552
+ ...name === "decision" ? { value: undefined } : {}
1553
+ }));
1554
+ }, []);
1555
+ const pageChanged = useCallback((event) => {
1556
+ const delta = event.currentTarget.name === "previous" ? -1 : 1;
1557
+ setItemFilters((state) => ({ ...state, page: Math.max(1, (state.page ?? 1) + delta) }));
1558
+ }, []);
1559
+ return /* @__PURE__ */ jsxs2("div", {
1560
+ className: "kk-board kk-board-collect",
1561
+ children: [
1562
+ /* @__PURE__ */ jsxs2("header", {
1563
+ className: "kk-board-header",
1564
+ children: [
1565
+ /* @__PURE__ */ jsxs2("div", {
1566
+ className: "kk-board-header-left",
1567
+ children: [
1568
+ /* @__PURE__ */ jsxs2("div", {
1569
+ className: "kk-board-header-title-row",
1570
+ children: [
1571
+ /* @__PURE__ */ jsx2("h1", {
1572
+ children: "Collect"
1573
+ }),
1574
+ selectedRow ? /* @__PURE__ */ jsx2("span", {
1575
+ className: `kk-badge ${selectedRow.status === "done" ? "kk-badge-success" : selectedRow.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
1576
+ children: selectedRow.status
1577
+ }) : null
1578
+ ]
1579
+ }),
1580
+ /* @__PURE__ */ jsxs2("p", {
1581
+ children: [
1582
+ "Run ",
1583
+ /* @__PURE__ */ jsx2("code", {
1584
+ children: selection.runId ?? "none selected"
1585
+ }),
1586
+ " · ",
1587
+ freshnessLabel(readerRefresh),
1588
+ results === null ? "" : ` · Results ${results.availability}`
1589
+ ]
1590
+ })
1591
+ ]
1592
+ }),
1593
+ /* @__PURE__ */ jsxs2("div", {
1594
+ className: "kk-board-header-right",
1595
+ children: [
1596
+ /* @__PURE__ */ jsxs2("div", {
1597
+ className: "kk-board-run-filter-group",
1598
+ children: [
1599
+ /* @__PURE__ */ jsx2("input", {
1600
+ type: "text",
1601
+ className: "kk-board-run-filter-input",
1602
+ placeholder: "Filter runs...",
1603
+ value: filterQuery,
1604
+ onChange: (e) => setFilterQuery(e.target.value),
1605
+ "aria-label": "Filter runs"
1606
+ }),
1607
+ /* @__PURE__ */ jsxs2("label", {
1608
+ className: "kk-board-run-selector",
1609
+ children: [
1610
+ "Run",
1611
+ /* @__PURE__ */ jsxs2("select", {
1612
+ value: selection.runId ?? "",
1613
+ onChange: (event) => {
1614
+ setItemFilters((state) => ({ ...state, page: 1 }));
1615
+ setResults(null);
1616
+ setConfig(null);
1617
+ setProgress(null);
1618
+ setStoreRun(null);
1619
+ setSelection((state) => ({ ...state, runId: event.target.value || null }));
1620
+ },
1621
+ children: [
1622
+ /* @__PURE__ */ jsx2("option", {
1623
+ value: "",
1624
+ children: rows.length === 0 ? "No runs recorded" : filteredRows.length === 0 ? "No matching runs" : "Select a run"
1625
+ }),
1626
+ filteredRows.map((row) => /* @__PURE__ */ jsxs2("option", {
1627
+ value: row.id,
1628
+ children: [
1629
+ row.id,
1630
+ " — ",
1631
+ row.status
1632
+ ]
1633
+ }, row.id))
1634
+ ]
1635
+ })
1636
+ ]
1637
+ })
1638
+ ]
1639
+ }),
1640
+ /* @__PURE__ */ jsx2("button", {
1641
+ type: "button",
1642
+ onClick: () => void loadReader(),
1643
+ title: "Refresh run data",
1644
+ children: "↻ Refresh"
1645
+ }),
1646
+ selection.runId === null ? null : /* @__PURE__ */ jsx2("a", {
1647
+ href: selectionPath(selection, window.location.search),
1648
+ className: "kk-btn-action",
1649
+ title: "Permanent link",
1650
+ children: "\uD83D\uDD17 Link"
1651
+ })
1652
+ ]
1653
+ })
1654
+ ]
1655
+ }),
1656
+ /* @__PURE__ */ jsx2("nav", {
1657
+ className: "kk-board-tabs",
1658
+ "aria-label": "Collect tabs",
1659
+ children: COLLECT_TABS.map((candidate) => /* @__PURE__ */ jsx2("button", {
1660
+ type: "button",
1661
+ "aria-current": candidate === tab ? "page" : undefined,
1662
+ onClick: () => setSelection((state) => ({ ...state, tab: candidate })),
1663
+ children: TAB_LABELS[candidate]
1664
+ }, candidate))
1665
+ }),
1666
+ rows.length === 0 ? /* @__PURE__ */ jsxs2("p", {
1667
+ className: "kk-board-empty",
1668
+ children: [
1669
+ "No runs recorded for kk-collect. Configure the workflow in",
1670
+ " ",
1671
+ /* @__PURE__ */ jsx2("a", {
1672
+ href: "/board/settings/workflows",
1673
+ children: "Settings → Workflows"
1674
+ }),
1675
+ "."
1676
+ ]
1677
+ }) : null,
1678
+ tab === "overview" ? /* @__PURE__ */ jsx2(OverviewPanel, {
1679
+ runs: rows,
1680
+ overview,
1681
+ now,
1682
+ runHref
1683
+ }) : null,
1684
+ tab === "summary" ? /* @__PURE__ */ jsx2(RunDetailPanel, {
1685
+ storeRun: currentStoreRun,
1686
+ results,
1687
+ facts,
1688
+ artifacts,
1689
+ showEffective,
1690
+ onToggleQuality: toggleQuality
1691
+ }) : null,
1692
+ tab === "items" ? /* @__PURE__ */ jsx2(ItemsPanel, {
1693
+ runId: selection.runId,
1694
+ items,
1695
+ filters: itemFilters,
1696
+ quality: currentStoreRun?.labelQuality ?? [],
1697
+ onFilterChange: filterChanged,
1698
+ onPageChange: pageChanged
1699
+ }) : null,
1700
+ tab === "progress" ? /* @__PURE__ */ jsx2(ProgressPanel, {
1701
+ steps
1702
+ }) : null,
1703
+ tab === "config" ? /* @__PURE__ */ jsx2(ConfigPanel, {
1704
+ config
1705
+ }) : null,
1706
+ dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).map((diagnostic) => /* @__PURE__ */ jsx2("p", {
1707
+ role: "status",
1708
+ className: "kk-board-diagnostic",
1709
+ children: diagnosticLine(diagnostic)
1710
+ }, diagnostic.code)),
1711
+ cursor === null ? null : /* @__PURE__ */ jsx2("button", {
1712
+ type: "button",
1713
+ onClick: () => void loadHistory(),
1714
+ children: "Load more runs"
1715
+ }),
1716
+ /* @__PURE__ */ jsxs2("p", {
1717
+ className: "kk-board-footnote",
1718
+ children: [
1719
+ "Run history every ",
1720
+ HISTORY_POLL_MS / 1000,
1721
+ "s; selected run every ",
1722
+ ACTIVE_POLL_MS / 1000,
1723
+ "s."
1724
+ ]
1725
+ })
1726
+ ]
1727
+ });
1728
+ }
910
1729
  var webModule = { apiVersion: 1, component: CollectModule };
911
1730
  export {
912
1731
  CollectModule,