@happyvertical/smrt-ingestion 0.55.5 → 0.55.7

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 (85) hide show
  1. package/AGENTS.md +13 -3
  2. package/FEEDBACK.md +117 -0
  3. package/PROPOSALS.md +173 -0
  4. package/README.md +14 -0
  5. package/REVIEW.md +136 -0
  6. package/agents/execution.md +132 -0
  7. package/dist/chunks/execution-CYjtj9_l.js +8031 -0
  8. package/dist/chunks/execution-CYjtj9_l.js.map +1 -0
  9. package/dist/chunks/execution-internal-DWteDHM2.js +7 -0
  10. package/dist/chunks/execution-internal-DWteDHM2.js.map +1 -0
  11. package/dist/chunks/feedback-B_NILilH.js +577 -0
  12. package/dist/chunks/feedback-B_NILilH.js.map +1 -0
  13. package/dist/chunks/proposal-errors-z24VKWnQ.js +301 -0
  14. package/dist/chunks/proposal-errors-z24VKWnQ.js.map +1 -0
  15. package/dist/dto.d.ts +3 -0
  16. package/dist/dto.d.ts.map +1 -1
  17. package/dist/execution-contracts.d.ts +3 -0
  18. package/dist/execution-contracts.d.ts.map +1 -1
  19. package/dist/execution-internal.d.ts +9 -0
  20. package/dist/execution-internal.d.ts.map +1 -0
  21. package/dist/execution.d.ts +23 -1
  22. package/dist/execution.d.ts.map +1 -1
  23. package/dist/execution.js +1 -7614
  24. package/dist/extraction.d.ts.map +1 -1
  25. package/dist/feedback-contracts.d.ts +40 -0
  26. package/dist/feedback-contracts.d.ts.map +1 -0
  27. package/dist/feedback-dto.d.ts +100 -0
  28. package/dist/feedback-dto.d.ts.map +1 -0
  29. package/dist/feedback-internal.d.ts +19 -0
  30. package/dist/feedback-internal.d.ts.map +1 -0
  31. package/dist/feedback.d.ts +53 -0
  32. package/dist/feedback.d.ts.map +1 -0
  33. package/dist/manifest.json +13 -1
  34. package/dist/models.js +1 -1
  35. package/dist/models.js.map +1 -1
  36. package/dist/proposal-contracts.d.ts +89 -0
  37. package/dist/proposal-contracts.d.ts.map +1 -0
  38. package/dist/proposal-dto.d.ts +124 -0
  39. package/dist/proposal-dto.d.ts.map +1 -0
  40. package/dist/proposal-errors.d.ts +5 -0
  41. package/dist/proposal-errors.d.ts.map +1 -0
  42. package/dist/proposal-sdk.d.ts +12 -0
  43. package/dist/proposal-sdk.d.ts.map +1 -0
  44. package/dist/proposal-validation.d.ts +188 -0
  45. package/dist/proposal-validation.d.ts.map +1 -0
  46. package/dist/proposals.d.ts +44 -0
  47. package/dist/proposals.d.ts.map +1 -0
  48. package/dist/proposals.js +964 -0
  49. package/dist/proposals.js.map +1 -0
  50. package/dist/review-dto.d.ts +115 -0
  51. package/dist/review-dto.d.ts.map +1 -0
  52. package/dist/server.d.ts +56 -0
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +263 -341
  55. package/dist/server.js.map +1 -1
  56. package/dist/smrt-knowledge.json +87 -12
  57. package/dist/svelte/components/ActionReview.svelte +232 -0
  58. package/dist/svelte/components/ActionReview.svelte.d.ts +15 -0
  59. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -0
  60. package/dist/svelte/components/EvidenceViewer.svelte +31 -0
  61. package/dist/svelte/components/EvidenceViewer.svelte.d.ts +8 -0
  62. package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -0
  63. package/dist/svelte/components/InboxView.svelte +119 -0
  64. package/dist/svelte/components/InboxView.svelte.d.ts +9 -0
  65. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -0
  66. package/dist/svelte/components/IntakeInbox.svelte +11 -0
  67. package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -0
  68. package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -0
  69. package/dist/svelte/components/IntakeReview.svelte +11 -0
  70. package/dist/svelte/components/IntakeReview.svelte.d.ts +10 -0
  71. package/dist/svelte/components/IntakeReview.svelte.d.ts.map +1 -0
  72. package/dist/svelte/components/ReviewView.svelte +243 -0
  73. package/dist/svelte/components/ReviewView.svelte.d.ts +9 -0
  74. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -0
  75. package/dist/svelte/i18n.d.ts +72 -0
  76. package/dist/svelte/i18n.d.ts.map +1 -0
  77. package/dist/svelte/i18n.js +72 -0
  78. package/dist/svelte/index.d.ts +5 -0
  79. package/dist/svelte/index.d.ts.map +1 -0
  80. package/dist/svelte/index.js +3 -0
  81. package/dist/svelte/view-utils.d.ts +13 -0
  82. package/dist/svelte/view-utils.d.ts.map +1 -0
  83. package/dist/svelte/view-utils.js +96 -0
  84. package/package.json +53 -22
  85. package/dist/execution.js.map +0 -1
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
4
+ import type { EvidenceView } from '../../review-dto.js';
5
+ import { M } from '../i18n.js';
6
+ import { evidenceUrl } from '../view-utils.js';
7
+ export interface Props {
8
+ evidence: EvidenceView[];
9
+ }
10
+ let { evidence }: Props = $props();
11
+ const { t } = useI18n();
12
+ </script>
13
+ <section data-testid="evidence-viewer" aria-label={t(M['ingestion.evidence'])}>
14
+ <h2>{t(M['ingestion.evidence'])}</h2>
15
+ {#each evidence as view (view.evidence.id)}
16
+ {@const url = evidenceUrl(view.viewUrl)}
17
+ <article id={`evidence-${view.evidence.id}`}>
18
+ <h3>{view.label}</h3>
19
+ {#if url}
20
+ {#if view.evidence.mediaType.startsWith('image/')}<img src={url} alt={view.label} />
21
+ {:else if view.evidence.mediaType === 'application/pdf'}<iframe src={url} title={view.label}></iframe>
22
+ {:else if view.evidence.mediaType.startsWith('audio/')}<audio controls src={url} aria-label={view.label}></audio>{/if}
23
+ <Button href={url} target="_blank" rel="noopener noreferrer">{t(M['ingestion.original'])}</Button>
24
+ {:else}<p>{t(M['ingestion.noOriginal'])}</p>{/if}
25
+ {#if view.text !== undefined}<pre>{view.text}</pre>{/if}
26
+ </article>
27
+ {/each}
28
+ </section>
29
+ <style>
30
+ section,article{min-width:0}article{margin-block:1rem}img,iframe,audio{display:block;max-width:100%;width:100%;margin-block:.5rem}img{object-fit:contain;max-height:32rem}iframe{height:32rem;border:1px solid var(--smrt-color-outline-variant,#ccc)}pre{white-space:pre-wrap;overflow-wrap:anywhere}
31
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { EvidenceView } from '../../review-dto.js';
2
+ export interface Props {
3
+ evidence: EvidenceView[];
4
+ }
5
+ declare const EvidenceViewer: import("svelte").Component<Props, {}, "">;
6
+ type EvidenceViewer = ReturnType<typeof EvidenceViewer>;
7
+ export default EvidenceViewer;
8
+ //# sourceMappingURL=EvidenceViewer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EvidenceViewer.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/EvidenceViewer.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AA+BD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ import { FilePicker, Input, Select } from '@happyvertical/smrt-ui/forms';
3
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
+ import { Button } from '@happyvertical/smrt-ui/ui';
5
+ import { onDestroy, onMount } from 'svelte';
6
+ import type {
7
+ InboxEntry,
8
+ InboxState,
9
+ IntakeReviewHost,
10
+ } from '../../review-dto.js';
11
+ import { M } from '../i18n.js';
12
+ export interface Props {
13
+ host: IntakeReviewHost;
14
+ onselect?: (itemId: string) => void;
15
+ }
16
+ let { host, onselect }: Props = $props();
17
+ const { t } = useI18n();
18
+ const states: InboxState[] = [
19
+ 'processing',
20
+ 'unresolved',
21
+ 'waiting',
22
+ 'failed',
23
+ 'deferred',
24
+ 'partially_completed',
25
+ 'completed',
26
+ ];
27
+ let items = $state<InboxEntry[]>([]),
28
+ selectedState = $state(''),
29
+ assignee = $state(''),
30
+ assignments = $state<Record<string, string>>({}),
31
+ files = $state<File[]>([]),
32
+ cursor = $state<string>(),
33
+ pending = $state(false),
34
+ error = $state(false);
35
+ let alive = true,
36
+ epoch = 0;
37
+ onDestroy(() => {
38
+ alive = false;
39
+ epoch++;
40
+ });
41
+ async function load(more = false) {
42
+ const ticket = ++epoch;
43
+ pending = true;
44
+ error = false;
45
+ const prior = more ? items : [];
46
+ const next = more ? cursor : undefined;
47
+ items = [];
48
+ assignments = {};
49
+ try {
50
+ const page = await host.list({
51
+ state: selectedState ? (selectedState as InboxState) : undefined,
52
+ assigneeId: assignee || undefined,
53
+ cursor: next,
54
+ });
55
+ if (!alive || ticket !== epoch) return;
56
+ items = [...prior, ...page.items];
57
+ cursor = page.nextCursor;
58
+ assignments = Object.fromEntries(
59
+ items.map((entry) => [entry.item.id, entry.assignment.assigneeId ?? '']),
60
+ );
61
+ } catch {
62
+ if (alive && ticket === epoch) {
63
+ items = [];
64
+ cursor = undefined;
65
+ error = true;
66
+ }
67
+ } finally {
68
+ if (alive && ticket === epoch) pending = false;
69
+ }
70
+ }
71
+ async function mutate(operation: () => Promise<unknown>, selected = false) {
72
+ if (pending) return;
73
+ pending = true;
74
+ error = false;
75
+ const ticket = ++epoch;
76
+ try {
77
+ const result = await operation();
78
+ if (!alive || ticket !== epoch) return;
79
+ files = [];
80
+ if (selected && result && typeof result === 'object' && 'itemId' in result)
81
+ onselect?.(String(result.itemId));
82
+ await load();
83
+ } catch {
84
+ if (alive && ticket === epoch) {
85
+ items = [];
86
+ assignments = {};
87
+ files = [];
88
+ error = true;
89
+ pending = false;
90
+ }
91
+ }
92
+ }
93
+ onMount(() => {
94
+ void load();
95
+ });
96
+ </script>
97
+ <section data-testid="intake-inbox" aria-busy={pending}>
98
+ <h1>{t(M['ingestion.inbox'])}</h1>
99
+ <div class="toolbar">
100
+ <Select aria-label={t(M['ingestion.filter'])} bind:value={selectedState} disabled={pending} onchange={() => load()}><option value="">{t(M['ingestion.all'])}</option>{#each states as value}<option {value}>{t(M[`ingestion.${value}`])}</option>{/each}</Select>
101
+ <Input aria-label={t(M['ingestion.assignee'])} bind:value={assignee} disabled={pending} />
102
+ <Button onclick={() => load()} disabled={pending}>{t(M['ingestion.refresh'])}</Button>
103
+ </div>
104
+ <FilePicker bind:files multiple label={t(M['ingestion.files'])} description={t(M['ingestion.filesHelp'])} disabled={pending} />
105
+ <Button disabled={pending || !files.length} onclick={() => mutate(() => host.upload([...files],crypto.randomUUID()),true)}>{t(M['ingestion.upload'])}</Button>
106
+ {#if error}<p role="alert">{t(M['ingestion.error'])}</p>{/if}
107
+ {#if pending}<p role="status">{t(M['ingestion.loading'])}</p>{/if}
108
+ {#if !pending && !error && !items.length}<p>{t(M['ingestion.empty'])}</p>{/if}
109
+ <ul>{#each items as entry (entry.item.id)}<li>
110
+ <h2>{entry.label}</h2><p>{t(M[`ingestion.${entry.state}`])}</p>
111
+ <Button onclick={() => onselect?.(entry.item.id)} disabled={pending}>{t(M['ingestion.open'])}</Button>
112
+ <Input aria-label={`${t(M['ingestion.assignee'])}: ${entry.label}`} bind:value={assignments[entry.item.id]} disabled={pending} />
113
+ <Button disabled={pending} onclick={() => mutate(() => host.assign({itemId:entry.item.id,expectedVersion:entry.assignment.version,assigneeId:assignments[entry.item.id] || null,requestId:crypto.randomUUID()}))}>{t(M['ingestion.assign'])}</Button>
114
+ </li>{/each}</ul>
115
+ {#if cursor}<Button disabled={pending} onclick={() => load(true)}>{t(M['ingestion.more'])}</Button>{/if}
116
+ </section>
117
+ <style>
118
+ section{max-width:72rem;margin-inline:auto;padding:1rem;color:var(--smrt-color-on-surface,#222)}.toolbar{display:flex;flex-wrap:wrap;gap:.75rem;margin-block:1rem}ul{list-style:none;padding:0}li{padding:1rem;border-block-end:1px solid var(--smrt-color-outline-variant,#ccc);display:grid;gap:.75rem}h2{font-size:1.1rem}
119
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { IntakeReviewHost } from '../../review-dto.js';
2
+ export interface Props {
3
+ host: IntakeReviewHost;
4
+ onselect?: (itemId: string) => void;
5
+ }
6
+ declare const InboxView: import("svelte").Component<Props, {}, "">;
7
+ type InboxView = ReturnType<typeof InboxView>;
8
+ export default InboxView;
9
+ //# sourceMappingURL=InboxView.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InboxView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InboxView.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAkHD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import type { IntakeReviewHost } from '../../review-dto.js';
3
+ import InboxView from './InboxView.svelte';
4
+ export interface Props {
5
+ host: IntakeReviewHost;
6
+ contextKey?: string;
7
+ onselect?: (itemId: string) => void;
8
+ }
9
+ let { host, contextKey = '', onselect }: Props = $props();
10
+ </script>
11
+ {#key host}{#key contextKey}<InboxView {host} {onselect} />{/key}{/key}
@@ -0,0 +1,10 @@
1
+ import type { IntakeReviewHost } from '../../review-dto.js';
2
+ export interface Props {
3
+ host: IntakeReviewHost;
4
+ contextKey?: string;
5
+ onselect?: (itemId: string) => void;
6
+ }
7
+ declare const IntakeInbox: import("svelte").Component<Props, {}, "">;
8
+ type IntakeInbox = ReturnType<typeof IntakeInbox>;
9
+ export default IntakeInbox;
10
+ //# sourceMappingURL=IntakeInbox.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntakeInbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeInbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAWD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import type { IntakeReviewHost } from '../../review-dto.js';
3
+ import ReviewView from './ReviewView.svelte';
4
+ export interface Props {
5
+ host: IntakeReviewHost;
6
+ itemId: string;
7
+ contextKey?: string;
8
+ }
9
+ let { host, itemId, contextKey = '' }: Props = $props();
10
+ </script>
11
+ {#key host}{#key contextKey}{#key itemId}<ReviewView {host} {itemId} />{/key}{/key}{/key}
@@ -0,0 +1,10 @@
1
+ import type { IntakeReviewHost } from '../../review-dto.js';
2
+ export interface Props {
3
+ host: IntakeReviewHost;
4
+ itemId: string;
5
+ contextKey?: string;
6
+ }
7
+ declare const IntakeReview: import("svelte").Component<Props, {}, "">;
8
+ type IntakeReview = ReturnType<typeof IntakeReview>;
9
+ export default IntakeReview;
10
+ //# sourceMappingURL=IntakeReview.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IntakeReview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeReview.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,243 @@
1
+ <script lang="ts">
2
+ import { Textarea } from '@happyvertical/smrt-ui/forms';
3
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
+ import { Button } from '@happyvertical/smrt-ui/ui';
5
+ import { onDestroy, onMount } from 'svelte';
6
+ import type { EvidenceLocation } from '../../extraction-types.js';
7
+ import type { IntakeReviewHost, ItemReviewView } from '../../review-dto.js';
8
+ import { M } from '../i18n.js';
9
+ import { evidenceUrl, extractedContent, pageGroups } from '../view-utils.js';
10
+ import ActionReview from './ActionReview.svelte';
11
+ import EvidenceViewer from './EvidenceViewer.svelte';
12
+ export interface Props {
13
+ host: IntakeReviewHost;
14
+ itemId: string;
15
+ }
16
+ let { host, itemId }: Props = $props();
17
+ const { t } = useI18n();
18
+ let view = $state<ItemReviewView>(),
19
+ pending = $state(false),
20
+ error = $state(false),
21
+ splits = $state<Record<string, string>>({}),
22
+ invalid = $state(false);
23
+ let alive = true,
24
+ epoch = 0,
25
+ loadedPages = 1;
26
+ onDestroy(() => {
27
+ alive = false;
28
+ epoch++;
29
+ });
30
+ function deny() {
31
+ epoch++;
32
+ view = undefined;
33
+ splits = {};
34
+ error = true;
35
+ pending = false;
36
+ }
37
+ async function load(pageCount = loadedPages) {
38
+ const ticket = ++epoch;
39
+ view = undefined;
40
+ splits = {};
41
+ pending = true;
42
+ error = false;
43
+ invalid = false;
44
+ try {
45
+ let cursor: string | undefined;
46
+ let result: ItemReviewView | undefined;
47
+ const actions: ItemReviewView['reviews']['actions'] = [];
48
+ const actionIds = new Set<string>();
49
+ const cursors = new Set<string>();
50
+ let refreshedPages = 0;
51
+ for (let index = 0; index < pageCount; index++) {
52
+ const page = await host.load(itemId, cursor);
53
+ if (!alive || ticket !== epoch) return;
54
+ if (page.availability !== 'available') {
55
+ result = page;
56
+ break;
57
+ }
58
+ if (
59
+ result &&
60
+ page.entry.item.analysisRevision !== result.entry.item.analysisRevision
61
+ )
62
+ throw new Error('Review changed during pagination');
63
+ result ??= page;
64
+ for (const action of page.reviews.actions) {
65
+ if (!actionIds.has(action.review.actionId)) {
66
+ actionIds.add(action.review.actionId);
67
+ actions.push(action);
68
+ }
69
+ }
70
+ refreshedPages++;
71
+ cursor = page.reviews.nextCursor;
72
+ if (!cursor) break;
73
+ if (cursors.has(cursor)) throw new Error('Repeated review cursor');
74
+ cursors.add(cursor);
75
+ }
76
+ if (!result) throw new Error('Missing review page');
77
+ if (result.availability === 'available') {
78
+ result = {
79
+ ...result,
80
+ reviews: { ...result.reviews, actions, nextCursor: cursor },
81
+ };
82
+ loadedPages = refreshedPages;
83
+ }
84
+ // A host denial must not leave any original, candidate, or argument in the DOM.
85
+ view =
86
+ result.availability === 'available'
87
+ ? result
88
+ : {
89
+ entry: result.entry,
90
+ evidence: [],
91
+ reviews: { actions: [] },
92
+ availability: result.availability,
93
+ };
94
+ splits = Object.fromEntries(
95
+ (view.generation?.splits ?? []).map((split) => [
96
+ split.evidenceId,
97
+ JSON.stringify(split.groups),
98
+ ]),
99
+ );
100
+ } catch {
101
+ if (alive && ticket === epoch) deny();
102
+ } finally {
103
+ if (alive && ticket === epoch) pending = false;
104
+ }
105
+ }
106
+ async function run(operation: () => Promise<unknown>) {
107
+ if (pending) return;
108
+ const ticket = ++epoch;
109
+ pending = true;
110
+ error = false;
111
+ view = undefined;
112
+ splits = {};
113
+ try {
114
+ await operation();
115
+ if (alive && ticket === epoch) await load();
116
+ } catch {
117
+ if (alive && ticket === epoch) deny();
118
+ }
119
+ }
120
+ function preview(index: number) {
121
+ const analysis = view?.analysis;
122
+ const suggestion = view?.generation?.suggestions[index];
123
+ if (!analysis || suggestion?.disposition !== 'ready_for_review') return;
124
+ const input = {
125
+ itemId,
126
+ attemptId: analysis.attemptId,
127
+ index,
128
+ requestId: crypto.randomUUID(),
129
+ };
130
+ void run(() => host.preview(input));
131
+ }
132
+ function split(evidenceId: string) {
133
+ const analysis = view?.analysis;
134
+ if (!view?.generation || !analysis) return;
135
+ let groups: number[][];
136
+ try {
137
+ groups = pageGroups(splits[evidenceId]);
138
+ } catch {
139
+ invalid = true;
140
+ return;
141
+ }
142
+ const input = {
143
+ itemId,
144
+ attemptId: analysis.attemptId,
145
+ expectedRevision: analysis.revision,
146
+ evidenceId,
147
+ groups,
148
+ requestId: crypto.randomUUID(),
149
+ };
150
+ void run(() => host.split(input));
151
+ }
152
+ function locationLabel(location: EvidenceLocation) {
153
+ switch (location.kind) {
154
+ case 'page':
155
+ return t(M['ingestion.page'], { page: location.page });
156
+ case 'pages':
157
+ return t(M['ingestion.pages'], {
158
+ start: location.startPage,
159
+ end: location.endPage,
160
+ });
161
+ case 'time':
162
+ return t(M['ingestion.time'], {
163
+ start: location.startMs / 1000,
164
+ end: location.endMs / 1000,
165
+ });
166
+ default:
167
+ return t(M['ingestion.source']);
168
+ }
169
+ }
170
+ function locationUrl(evidenceId: string, location: EvidenceLocation) {
171
+ const evidence = view?.evidence.find(
172
+ (entry) => entry.evidence.id === evidenceId,
173
+ );
174
+ const url = evidenceUrl(evidence?.viewUrl);
175
+ if (!url) return undefined;
176
+ const base = url.split('#')[0];
177
+ if (
178
+ location.kind === 'page' &&
179
+ evidence?.evidence.mediaType === 'application/pdf'
180
+ )
181
+ return `${base}#page=${location.page}`;
182
+ if (
183
+ location.kind === 'pages' &&
184
+ evidence?.evidence.mediaType === 'application/pdf'
185
+ )
186
+ return `${base}#page=${location.startPage}`;
187
+ if (
188
+ location.kind === 'time' &&
189
+ evidence?.evidence.mediaType.startsWith('audio/')
190
+ )
191
+ return `${base}#t=${location.startMs / 1000},${location.endMs / 1000}`;
192
+ return url;
193
+ }
194
+ onMount(() => {
195
+ void load();
196
+ });
197
+ </script>
198
+ <section data-testid="intake-review" aria-busy={pending}>
199
+ <header><h1>{t(M['ingestion.review'])}</h1><Button onclick={() => load()} disabled={pending}>{t(M['ingestion.refresh'])}</Button></header>
200
+ {#if pending}<p role="status">{t(M['ingestion.loading'])}</p>{/if}
201
+ {#if error}<p role="alert">{t(M['ingestion.error'])}</p>{/if}
202
+ {#if view}
203
+ {#if view.availability !== 'available'}<p role="alert">{t(M[`ingestion.${view.availability}`])}</p>
204
+ {:else}
205
+ {@const extracted = view.analysis ? extractedContent(view.analysis.result.output) : []}
206
+ <h2>{view.entry.label}</h2><p>{t(M[`ingestion.${view.entry.state}`])}</p>
207
+ <div class="review-grid"><EvidenceViewer evidence={view.evidence} /><div class="details">
208
+ {#if extracted.length}
209
+ <h2>{t(M['ingestion.extracted'])}</h2>
210
+ {#each extracted as segment}
211
+ <article><pre>{segment.text}</pre>
212
+ {#if segment.confidence !== undefined}<p>{t(M['ingestion.confidence'])}: {segment.confidence}</p>{:else}<p>{t(M['ingestion.noConfidence'])}</p>{/if}
213
+ {#if segment.location}{@const url = locationUrl(segment.evidenceId,segment.location)}{#if url}<Button href={url} target="_blank" rel="noopener noreferrer">{locationLabel(segment.location)}</Button>{:else}<p>{locationLabel(segment.location)}</p>{/if}{/if}
214
+ </article>
215
+ {/each}
216
+ {/if}
217
+ {#if view.generation}
218
+ {@const generation = view.generation}
219
+ <h2>{t(M['ingestion.proposals'])}</h2><p>{generation.outcome}</p>
220
+ {#if typeof view.analysis?.result.confidence === 'number' && Number.isFinite(view.analysis.result.confidence)}<p>{t(M['ingestion.confidence'])}: {view.analysis.result.confidence}</p>{:else}<p>{t(M['ingestion.noConfidence'])}</p>{/if}
221
+ {#each generation.warnings as warning}<p>{warning}</p>{/each}
222
+ {#each generation.suggestions as suggestion,index}
223
+ <article><h3>{suggestion.handlerId}</h3><p>{suggestion.explanation}</p><pre>{JSON.stringify(suggestion.args,null,2)}</pre>
224
+ {#if suggestion.alternatives.length}<h4>{t(M['ingestion.alternatives'])}</h4><ul>{#each suggestion.alternatives as alternative}<li>{alternative}</li>{/each}</ul>{/if}
225
+ {#if suggestion.missingFields.length}<h4>{t(M['ingestion.missing'])}</h4><ul>{#each suggestion.missingFields as missing}<li>{missing}</li>{/each}</ul>{/if}
226
+ <ul>{#each suggestion.evidence as reference}{@const url = locationUrl(reference.evidenceId,reference.location)}<li>{#if url}<Button href={url} target="_blank" rel="noopener noreferrer">{locationLabel(reference.location)}</Button>{:else}{locationLabel(reference.location)}{/if}</li>{/each}</ul>
227
+ <Button disabled={suggestion.disposition !== 'ready_for_review' || !view.analysis} onclick={() => preview(index)}>{t(M['ingestion.preview'])}</Button>
228
+ </article>
229
+ {/each}
230
+ {#each generation.splits as proposed (proposed.evidenceId)}<div><Textarea aria-label={`${t(M['ingestion.split'])}: ${proposed.evidenceId}`} bind:value={splits[proposed.evidenceId]} /><Button disabled={!view.analysis} onclick={() => split(proposed.evidenceId)}>{t(M['ingestion.saveSplit'])}</Button></div>{/each}
231
+ {#if invalid}<p role="alert">{t(M['ingestion.invalid'])}</p>{/if}
232
+ {/if}
233
+ {#if !view.reviews.actions.length}<p>{t(M['ingestion.noReviews'])}</p>{/if}
234
+ {#each view.reviews.actions as action (`${action.review.actionId}:${action.review.revision}:${action.review.reviewVersion}`)}
235
+ <ActionReview {host} {itemId} {action} {run} {deny} currentAttemptId={action.stalePlan && view.reviews.actions.find((entry) => entry.stalePlan?.id === action.stalePlan?.id) === action ? action.attemptId : undefined} catalog={view.generation?.offered.find((entry) => entry.handler.id === action.handlerId && entry.handler.version === action.handlerVersion)?.handler} />
236
+ {/each}
237
+ {#if view.reviews.nextCursor}<Button onclick={() => load(loadedPages + 1)}>{t(M['ingestion.more'])}</Button>{/if}
238
+ </div></div>
239
+ {/if}{/if}
240
+ </section>
241
+ <style>
242
+ section{overflow-wrap:anywhere;max-width:90rem;margin-inline:auto;padding:1rem;color:var(--smrt-color-on-surface,#222)}header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.review-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:1.5rem}.details,article{min-width:0}pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:24rem;overflow:auto}article{padding-block:1rem;border-block-end:1px solid var(--smrt-color-outline-variant,#ccc)}@media(max-width:48rem){.review-grid{grid-template-columns:minmax(0,1fr)}header{flex-wrap:wrap}}
243
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { IntakeReviewHost } from '../../review-dto.js';
2
+ export interface Props {
3
+ host: IntakeReviewHost;
4
+ itemId: string;
5
+ }
6
+ declare const ReviewView: import("svelte").Component<Props, {}, "">;
7
+ type ReviewView = ReturnType<typeof ReviewView>;
8
+ export default ReviewView;
9
+ //# sourceMappingURL=ReviewView.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ReviewView.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,qBAAqB,CAAC;AAM5E,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAkPD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,72 @@
1
+ export declare const M: {
2
+ readonly 'ingestion.planArgs': "ingestion.planArgs";
3
+ readonly 'ingestion.editPlan': "ingestion.editPlan";
4
+ readonly 'ingestion.inbox': "ingestion.inbox";
5
+ readonly 'ingestion.review': "ingestion.review";
6
+ readonly 'ingestion.loading': "ingestion.loading";
7
+ readonly 'ingestion.error': "ingestion.error";
8
+ readonly 'ingestion.refresh': "ingestion.refresh";
9
+ readonly 'ingestion.empty': "ingestion.empty";
10
+ readonly 'ingestion.all': "ingestion.all";
11
+ readonly 'ingestion.filter': "ingestion.filter";
12
+ readonly 'ingestion.assignee': "ingestion.assignee";
13
+ readonly 'ingestion.assign': "ingestion.assign";
14
+ readonly 'ingestion.unassigned': "ingestion.unassigned";
15
+ readonly 'ingestion.upload': "ingestion.upload";
16
+ readonly 'ingestion.files': "ingestion.files";
17
+ readonly 'ingestion.filesHelp': "ingestion.filesHelp";
18
+ readonly 'ingestion.more': "ingestion.more";
19
+ readonly 'ingestion.open': "ingestion.open";
20
+ readonly 'ingestion.processing': "ingestion.processing";
21
+ readonly 'ingestion.unresolved': "ingestion.unresolved";
22
+ readonly 'ingestion.waiting': "ingestion.waiting";
23
+ readonly 'ingestion.failed': "ingestion.failed";
24
+ readonly 'ingestion.deferred': "ingestion.deferred";
25
+ readonly 'ingestion.partially_completed': "ingestion.partially_completed";
26
+ readonly 'ingestion.completed': "ingestion.completed";
27
+ readonly 'ingestion.evidence': "ingestion.evidence";
28
+ readonly 'ingestion.original': "ingestion.original";
29
+ readonly 'ingestion.noOriginal': "ingestion.noOriginal";
30
+ readonly 'ingestion.extracted': "ingestion.extracted";
31
+ readonly 'ingestion.confidence': "ingestion.confidence";
32
+ readonly 'ingestion.noConfidence': "ingestion.noConfidence";
33
+ readonly 'ingestion.proposals': "ingestion.proposals";
34
+ readonly 'ingestion.preview': "ingestion.preview";
35
+ readonly 'ingestion.args': "ingestion.args";
36
+ readonly 'ingestion.effects': "ingestion.effects";
37
+ readonly 'ingestion.approve': "ingestion.approve";
38
+ readonly 'ingestion.reject': "ingestion.reject";
39
+ readonly 'ingestion.defer': "ingestion.defer";
40
+ readonly 'ingestion.correct': "ingestion.correct";
41
+ readonly 'ingestion.apply': "ingestion.apply";
42
+ readonly 'ingestion.invalid': "ingestion.invalid";
43
+ readonly 'ingestion.alternatives': "ingestion.alternatives";
44
+ readonly 'ingestion.missing': "ingestion.missing";
45
+ readonly 'ingestion.candidates': "ingestion.candidates";
46
+ readonly 'ingestion.search': "ingestion.search";
47
+ readonly 'ingestion.query': "ingestion.query";
48
+ readonly 'ingestion.field': "ingestion.field";
49
+ readonly 'ingestion.choose': "ingestion.choose";
50
+ readonly 'ingestion.split': "ingestion.split";
51
+ readonly 'ingestion.saveSplit': "ingestion.saveSplit";
52
+ readonly 'ingestion.result': "ingestion.result";
53
+ readonly 'ingestion.stale': "ingestion.stale";
54
+ readonly 'ingestion.revoked': "ingestion.revoked";
55
+ readonly 'ingestion.expired': "ingestion.expired";
56
+ readonly 'ingestion.unavailable': "ingestion.unavailable";
57
+ readonly 'ingestion.feedback': "ingestion.feedback";
58
+ readonly 'ingestion.correctJudgment': "ingestion.correctJudgment";
59
+ readonly 'ingestion.incorrectJudgment': "ingestion.incorrectJudgment";
60
+ readonly 'ingestion.comment': "ingestion.comment";
61
+ readonly 'ingestion.page': "ingestion.page";
62
+ readonly 'ingestion.pages': "ingestion.pages";
63
+ readonly 'ingestion.time': "ingestion.time";
64
+ readonly 'ingestion.source': "ingestion.source";
65
+ readonly 'ingestion.truncated': "ingestion.truncated";
66
+ readonly 'ingestion.reviewAgain': "ingestion.reviewAgain";
67
+ readonly 'ingestion.noReviews': "ingestion.noReviews";
68
+ readonly 'ingestion.state': "ingestion.state";
69
+ readonly 'ingestion.reason': "ingestion.reason";
70
+ readonly 'ingestion.feedbackSent': "ingestion.feedbackSent";
71
+ };
72
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuEZ,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { defineMessages } from '@happyvertical/smrt-ui/i18n';
2
+ export const M = defineMessages({
3
+ 'ingestion.planArgs': 'Plan arguments (JSON)',
4
+ 'ingestion.editPlan': 'Edit the plan and review its new steps',
5
+ 'ingestion.inbox': 'Intake inbox',
6
+ 'ingestion.review': 'Review intake',
7
+ 'ingestion.loading': 'Loading…',
8
+ 'ingestion.error': 'This view is unavailable. Refresh to check current access and revision.',
9
+ 'ingestion.refresh': 'Refresh',
10
+ 'ingestion.empty': 'No intake items.',
11
+ 'ingestion.all': 'All states',
12
+ 'ingestion.filter': 'State',
13
+ 'ingestion.assignee': 'Assignee ID',
14
+ 'ingestion.assign': 'Save assignment',
15
+ 'ingestion.unassigned': 'Unassigned',
16
+ 'ingestion.upload': 'Upload',
17
+ 'ingestion.files': 'Choose intake files',
18
+ 'ingestion.filesHelp': 'Select documents, images, email, or audio.',
19
+ 'ingestion.more': 'Load more',
20
+ 'ingestion.open': 'Review',
21
+ 'ingestion.processing': 'Processing',
22
+ 'ingestion.unresolved': 'Unresolved',
23
+ 'ingestion.waiting': 'Waiting',
24
+ 'ingestion.failed': 'Failed',
25
+ 'ingestion.deferred': 'Deferred',
26
+ 'ingestion.partially_completed': 'Partially completed',
27
+ 'ingestion.completed': 'Completed',
28
+ 'ingestion.evidence': 'Original evidence',
29
+ 'ingestion.original': 'Open original',
30
+ 'ingestion.noOriginal': 'Original unavailable',
31
+ 'ingestion.extracted': 'Extracted information',
32
+ 'ingestion.confidence': 'Reported confidence',
33
+ 'ingestion.noConfidence': 'Confidence unavailable',
34
+ 'ingestion.proposals': 'Proposed actions',
35
+ 'ingestion.preview': 'Prepare review',
36
+ 'ingestion.args': 'Exact arguments (JSON)',
37
+ 'ingestion.effects': 'Proposed effects',
38
+ 'ingestion.approve': 'Approve',
39
+ 'ingestion.reject': 'Reject',
40
+ 'ingestion.defer': 'Defer',
41
+ 'ingestion.correct': 'Save edits for new review',
42
+ 'ingestion.apply': 'Apply approved action',
43
+ 'ingestion.invalid': 'Enter a valid JSON object or page groups.',
44
+ 'ingestion.alternatives': 'Alternatives',
45
+ 'ingestion.missing': 'Missing information',
46
+ 'ingestion.candidates': 'Authorized destinations',
47
+ 'ingestion.search': 'Search destinations',
48
+ 'ingestion.query': 'Destination search',
49
+ 'ingestion.field': 'Argument field',
50
+ 'ingestion.choose': 'Choose destination',
51
+ 'ingestion.split': 'Logical page groups (JSON)',
52
+ 'ingestion.saveSplit': 'Save split and reprocess',
53
+ 'ingestion.result': 'Result',
54
+ 'ingestion.stale': 'Stale',
55
+ 'ingestion.revoked': 'Access revoked',
56
+ 'ingestion.expired': 'Expired',
57
+ 'ingestion.unavailable': 'Unavailable',
58
+ 'ingestion.feedback': 'Explicit correctness feedback',
59
+ 'ingestion.correctJudgment': 'Correct',
60
+ 'ingestion.incorrectJudgment': 'Incorrect',
61
+ 'ingestion.comment': 'Feedback comment',
62
+ 'ingestion.page': 'Page {page}',
63
+ 'ingestion.pages': 'Pages {start}–{end}',
64
+ 'ingestion.time': '{start}–{end} seconds',
65
+ 'ingestion.source': 'Source location',
66
+ 'ingestion.truncated': 'More matches exist; refine the search.',
67
+ 'ingestion.reviewAgain': 'Edits require a new review before approval.',
68
+ 'ingestion.noReviews': 'No saved reviews.',
69
+ 'ingestion.state': 'State',
70
+ 'ingestion.reason': 'Reason',
71
+ 'ingestion.feedbackSent': 'Feedback sent.',
72
+ });
@@ -0,0 +1,5 @@
1
+ export type { Assignment, EvidenceView, InboxEntry, InboxState, IntakeReviewHost, ItemReviewView, LogicalSplitInput, ReviewAction, ReviewPage, } from '../review-dto.js';
2
+ export { default as EvidenceViewer } from './components/EvidenceViewer.svelte';
3
+ export { default as IntakeInbox } from './components/IntakeInbox.svelte';
4
+ export { default as IntakeReview } from './components/IntakeReview.svelte';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,YAAY,EACZ,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC"}