@happyvertical/smrt-ingestion 0.57.45 → 0.57.47
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.
- package/FEEDBACK.md +30 -0
- package/PROPOSALS.md +29 -0
- package/REVIEW.md +79 -0
- package/agents/execution.md +3 -1
- package/dist/chunks/{execution-DKWz2cz0.js → execution-Clcmi0sj.js} +2 -2
- package/dist/chunks/{execution-DKWz2cz0.js.map → execution-Clcmi0sj.js.map} +1 -1
- package/dist/chunks/{feedback-DHcSKgVU.js → feedback-hDSMYjiG.js} +94 -34
- package/dist/chunks/feedback-hDSMYjiG.js.map +1 -0
- package/dist/chunks/{proposal-contracts-CPH3ckgH.js → proposal-contracts-DREPLG56.js} +75 -2
- package/dist/chunks/proposal-contracts-DREPLG56.js.map +1 -0
- package/dist/chunks/{reviewer-instruction-Deoe4NPv.js → reviewer-instruction-BeOYVCVk.js} +2 -2
- package/dist/chunks/{reviewer-instruction-Deoe4NPv.js.map → reviewer-instruction-BeOYVCVk.js.map} +1 -1
- package/dist/execution-contracts.d.ts +7 -0
- package/dist/execution-contracts.d.ts.map +1 -1
- package/dist/execution.js +1 -1
- package/dist/feedback-contracts.d.ts +32 -3
- package/dist/feedback-contracts.d.ts.map +1 -1
- package/dist/feedback.d.ts +28 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/generation-retry.d.ts +45 -0
- package/dist/generation-retry.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/models.js +1 -1
- package/dist/models.js.map +1 -1
- package/dist/proposal-contracts.d.ts +15 -0
- package/dist/proposal-contracts.d.ts.map +1 -1
- package/dist/proposal-dto.d.ts +6 -0
- package/dist/proposal-dto.d.ts.map +1 -1
- package/dist/proposals.d.ts +7 -0
- package/dist/proposals.d.ts.map +1 -1
- package/dist/proposals.js +61 -18
- package/dist/proposals.js.map +1 -1
- package/dist/review-dto.d.ts +56 -0
- package/dist/review-dto.d.ts.map +1 -1
- package/dist/review-host.d.ts +14 -1
- package/dist/review-host.d.ts.map +1 -1
- package/dist/server.d.ts +27 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +127 -5
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +16 -16
- package/dist/svelte/argument-fields.d.ts +48 -0
- package/dist/svelte/argument-fields.d.ts.map +1 -0
- package/dist/svelte/argument-fields.js +96 -0
- package/dist/svelte/components/ActionReview.svelte +81 -11
- package/dist/svelte/components/ActionReview.svelte.d.ts +30 -1
- package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ArgumentFields.svelte +176 -0
- package/dist/svelte/components/ArgumentFields.svelte.d.ts +27 -0
- package/dist/svelte/components/ArgumentFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InboxView.svelte +137 -17
- package/dist/svelte/components/InboxView.svelte.d.ts +17 -0
- package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/IntakeInbox.svelte +26 -2
- package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -1
- package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -1
- package/dist/svelte/components/IntakeWorkspace.svelte +23 -1
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +13 -1
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ReviewView.svelte +88 -10
- package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TriageView.svelte +43 -3
- package/dist/svelte/components/TriageView.svelte.d.ts +10 -1
- package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -1
- package/dist/svelte/i18n.d.ts +33 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +36 -0
- package/dist/svelte/index.d.ts +2 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/upload-refusal.d.ts +25 -0
- package/dist/svelte/upload-refusal.d.ts.map +1 -0
- package/dist/svelte/upload-refusal.js +40 -0
- package/dist/svelte/view-utils.d.ts +8 -0
- package/dist/svelte/view-utils.d.ts.map +1 -1
- package/dist/svelte/view-utils.js +36 -0
- package/package.json +17 -17
- package/dist/chunks/feedback-DHcSKgVU.js.map +0 -1
- package/dist/chunks/proposal-contracts-CPH3ckgH.js.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ProposalCatalogEntry } from '../proposal-dto.js';
|
|
2
|
+
/** One top-level handler argument the review screen can edit as a field. */
|
|
3
|
+
export type ArgumentField = {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
required: boolean;
|
|
7
|
+
kind: 'text' | 'date' | 'date-time';
|
|
8
|
+
maxLength?: number;
|
|
9
|
+
} | {
|
|
10
|
+
name: string;
|
|
11
|
+
title: string;
|
|
12
|
+
required: boolean;
|
|
13
|
+
kind: 'number' | 'integer';
|
|
14
|
+
minimum?: number;
|
|
15
|
+
maximum?: number;
|
|
16
|
+
} | {
|
|
17
|
+
name: string;
|
|
18
|
+
title: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
kind: 'boolean';
|
|
21
|
+
} | {
|
|
22
|
+
name: string;
|
|
23
|
+
title: string;
|
|
24
|
+
required: boolean;
|
|
25
|
+
kind: 'enum';
|
|
26
|
+
options: Array<string | number>;
|
|
27
|
+
} | {
|
|
28
|
+
name: string;
|
|
29
|
+
title: string;
|
|
30
|
+
required: boolean;
|
|
31
|
+
kind: 'candidate';
|
|
32
|
+
model: string;
|
|
33
|
+
} | {
|
|
34
|
+
name: string;
|
|
35
|
+
title: string;
|
|
36
|
+
required: boolean;
|
|
37
|
+
kind: 'evidence';
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Top-level arguments drawn as fields (#4503): strings (plain, `date`,
|
|
41
|
+
* `date-time`), numbers, integers, booleans, enums of strings or numbers, and
|
|
42
|
+
* declared candidate/evidence references. Anything else (objects, arrays,
|
|
43
|
+
* unions) is edited only as JSON under "Advanced".
|
|
44
|
+
*/
|
|
45
|
+
export declare function argumentFields(schema: Record<string, unknown> | undefined, references?: ProposalCatalogEntry['references']): ArgumentField[];
|
|
46
|
+
/** `YYYY-MM-DDTHH:mm` in local time for a `datetime-local` input; '' when unparsable. */
|
|
47
|
+
export declare function localDateTime(value: unknown): string;
|
|
48
|
+
//# sourceMappingURL=argument-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"argument-fields.d.ts","sourceRoot":"","sources":["../../src/svelte/argument-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACnE;IACE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CACjC,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAkBzE;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,UAAU,GAAE,oBAAoB,CAAC,YAAY,CAAM,GAClD,aAAa,EAAE,CAsEjB;AACD,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMpD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const record = (value) => value && typeof value === 'object' && !Array.isArray(value)
|
|
2
|
+
? value
|
|
3
|
+
: {};
|
|
4
|
+
/** The single non-null JSON type a property declares, if any. */
|
|
5
|
+
function typeOf(schema) {
|
|
6
|
+
const declared = schema.type;
|
|
7
|
+
if (typeof declared === 'string')
|
|
8
|
+
return declared;
|
|
9
|
+
if (Array.isArray(declared)) {
|
|
10
|
+
const types = declared.filter((type) => type !== 'null');
|
|
11
|
+
return types.length === 1 && typeof types[0] === 'string'
|
|
12
|
+
? types[0]
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Top-level arguments drawn as fields (#4503): strings (plain, `date`,
|
|
19
|
+
* `date-time`), numbers, integers, booleans, enums of strings or numbers, and
|
|
20
|
+
* declared candidate/evidence references. Anything else (objects, arrays,
|
|
21
|
+
* unions) is edited only as JSON under "Advanced".
|
|
22
|
+
*/
|
|
23
|
+
export function argumentFields(schema, references = {}) {
|
|
24
|
+
const root = record(schema);
|
|
25
|
+
const properties = record(root.properties);
|
|
26
|
+
const required = new Set(Array.isArray(root.required)
|
|
27
|
+
? root.required.filter((name) => typeof name === 'string')
|
|
28
|
+
: []);
|
|
29
|
+
const fields = [];
|
|
30
|
+
for (const [name, value] of Object.entries(properties)) {
|
|
31
|
+
const property = record(value);
|
|
32
|
+
const base = {
|
|
33
|
+
name,
|
|
34
|
+
title: typeof property.title === 'string' && property.title.trim()
|
|
35
|
+
? property.title.trim()
|
|
36
|
+
: name,
|
|
37
|
+
required: required.has(name),
|
|
38
|
+
};
|
|
39
|
+
const reference = references[name];
|
|
40
|
+
if (reference?.kind === 'candidate') {
|
|
41
|
+
fields.push({ ...base, kind: 'candidate', model: reference.model });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (reference?.kind === 'evidence') {
|
|
45
|
+
fields.push({ ...base, kind: 'evidence' });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const type = typeOf(property);
|
|
49
|
+
if (Array.isArray(property.enum) &&
|
|
50
|
+
property.enum.length &&
|
|
51
|
+
property.enum.every((option) => typeof option === 'string' || typeof option === 'number')) {
|
|
52
|
+
fields.push({
|
|
53
|
+
...base,
|
|
54
|
+
kind: 'enum',
|
|
55
|
+
options: property.enum,
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (type === 'string')
|
|
60
|
+
fields.push({
|
|
61
|
+
...base,
|
|
62
|
+
kind: property.format === 'date'
|
|
63
|
+
? 'date'
|
|
64
|
+
: property.format === 'date-time'
|
|
65
|
+
? 'date-time'
|
|
66
|
+
: 'text',
|
|
67
|
+
...(Number.isSafeInteger(property.maxLength)
|
|
68
|
+
? { maxLength: property.maxLength }
|
|
69
|
+
: {}),
|
|
70
|
+
});
|
|
71
|
+
else if (type === 'number' || type === 'integer')
|
|
72
|
+
fields.push({
|
|
73
|
+
...base,
|
|
74
|
+
kind: type,
|
|
75
|
+
...(typeof property.minimum === 'number'
|
|
76
|
+
? { minimum: property.minimum }
|
|
77
|
+
: {}),
|
|
78
|
+
...(typeof property.maximum === 'number'
|
|
79
|
+
? { maximum: property.maximum }
|
|
80
|
+
: {}),
|
|
81
|
+
});
|
|
82
|
+
else if (type === 'boolean')
|
|
83
|
+
fields.push({ ...base, kind: 'boolean' });
|
|
84
|
+
}
|
|
85
|
+
return fields;
|
|
86
|
+
}
|
|
87
|
+
/** `YYYY-MM-DDTHH:mm` in local time for a `datetime-local` input; '' when unparsable. */
|
|
88
|
+
export function localDateTime(value) {
|
|
89
|
+
if (typeof value !== 'string' || !value)
|
|
90
|
+
return '';
|
|
91
|
+
const date = new Date(value);
|
|
92
|
+
if (Number.isNaN(date.getTime()))
|
|
93
|
+
return '';
|
|
94
|
+
const pad = (part) => String(part).padStart(2, '0');
|
|
95
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
96
|
+
}
|
|
@@ -8,10 +8,19 @@ import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
|
8
8
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
9
9
|
import { onDestroy, untrack } from 'svelte';
|
|
10
10
|
import type { ReviewInput } from '../../execution-dto.js';
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ProposalCandidate,
|
|
13
|
+
ProposalCatalogEntry,
|
|
14
|
+
} from '../../proposal-dto.js';
|
|
12
15
|
import type { IntakeReviewHost, ReviewAction } from '../../review-dto.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
16
|
+
import { argumentFields } from '../argument-fields.js';
|
|
17
|
+
import { M, messageKey } from '../i18n.js';
|
|
18
|
+
import {
|
|
19
|
+
argumentsObject,
|
|
20
|
+
displayEntries,
|
|
21
|
+
missingRequired,
|
|
22
|
+
} from '../view-utils.js';
|
|
23
|
+
import ArgumentFields from './ArgumentFields.svelte';
|
|
15
24
|
import CandidatePicker from './CandidatePicker.svelte';
|
|
16
25
|
export interface Props {
|
|
17
26
|
host: IntakeReviewHost;
|
|
@@ -29,6 +38,26 @@ export interface Props {
|
|
|
29
38
|
deny: () => void;
|
|
30
39
|
/** Whether this editor holds unsent input (arguments, reason, comment). */
|
|
31
40
|
ondirty?: (dirty: boolean) => void;
|
|
41
|
+
/** The handler's display label (#4503); the handler id is drawn when absent. */
|
|
42
|
+
label?: string;
|
|
43
|
+
/**
|
|
44
|
+
* The handler whose `argsSchema` draws the arguments as fields (#4503),
|
|
45
|
+
* the offered or reviewer catalog entry; the raw JSON stays under
|
|
46
|
+
* "Advanced". Without it (or a schema without properties) JSON only.
|
|
47
|
+
*/
|
|
48
|
+
fields?: ProposalCatalogEntry;
|
|
49
|
+
/** Candidates already known for reference labels (the generation's offer). */
|
|
50
|
+
candidates?: readonly ProposalCandidate[];
|
|
51
|
+
/** Originals an evidence reference may name. */
|
|
52
|
+
evidence?: ReadonlyArray<{ id: string; label: string }>;
|
|
53
|
+
/**
|
|
54
|
+
* The judgment this reviewer sent for the action in this view (kept by the
|
|
55
|
+
* parent across the reload that follows), with the receipt id when the
|
|
56
|
+
* host returned one.
|
|
57
|
+
*/
|
|
58
|
+
sent?: { judgment: 'correct' | 'incorrect'; id?: string };
|
|
59
|
+
/** Told about a judgment once the host accepted it. */
|
|
60
|
+
onjudged?: (judged: { judgment: 'correct' | 'incorrect'; id?: string }) => void;
|
|
32
61
|
}
|
|
33
62
|
let {
|
|
34
63
|
host,
|
|
@@ -40,6 +69,12 @@ let {
|
|
|
40
69
|
run,
|
|
41
70
|
deny,
|
|
42
71
|
ondirty,
|
|
72
|
+
label,
|
|
73
|
+
fields,
|
|
74
|
+
candidates,
|
|
75
|
+
evidence,
|
|
76
|
+
sent,
|
|
77
|
+
onjudged,
|
|
43
78
|
}: Props = $props();
|
|
44
79
|
const { t } = useI18n();
|
|
45
80
|
// ReviewView keys this editor by action/revision/reviewVersion; edits are local to that binding.
|
|
@@ -50,6 +85,41 @@ let args = $state(untrack(() => JSON.stringify(action.args, null, 2) ?? '')),
|
|
|
50
85
|
planArgs = $state(
|
|
51
86
|
untrack(() => JSON.stringify(action.plan?.args, null, 2) ?? ''),
|
|
52
87
|
);
|
|
88
|
+
// Fields replace the destination picker when the schema draws any.
|
|
89
|
+
const drawsFields = $derived(
|
|
90
|
+
!!fields && argumentFields(fields.argsSchema, fields.references).length > 0,
|
|
91
|
+
);
|
|
92
|
+
const stateKey = $derived(
|
|
93
|
+
messageKey('ingestion.reviewState.', action.review.state),
|
|
94
|
+
);
|
|
95
|
+
// The handler's preview fields, then non-empty framework metadata.
|
|
96
|
+
const effects = $derived(displayEntries(action.review.display));
|
|
97
|
+
// The current judgment: the loaded one, else the one sent from this view.
|
|
98
|
+
const judged = $derived(action.feedback ?? sent);
|
|
99
|
+
function judge(judgment: 'correct' | 'incorrect') {
|
|
100
|
+
const send = host.feedback;
|
|
101
|
+
if (!send) return;
|
|
102
|
+
const previous = action.feedback?.id ?? sent?.id;
|
|
103
|
+
const input = {
|
|
104
|
+
itemId,
|
|
105
|
+
actionId: action.review.actionId,
|
|
106
|
+
expectedRevision: action.review.revision,
|
|
107
|
+
expectedReviewVersion: action.review.reviewVersion,
|
|
108
|
+
bindingHash: action.review.bindingHash,
|
|
109
|
+
judgment,
|
|
110
|
+
comment,
|
|
111
|
+
requestId: crypto.randomUUID(),
|
|
112
|
+
// A second judgment replaces the reviewer's current one.
|
|
113
|
+
...(previous ? { supersedesId: previous } : {}),
|
|
114
|
+
};
|
|
115
|
+
return run(async () => {
|
|
116
|
+
const receipt = await send(input);
|
|
117
|
+
onjudged?.({
|
|
118
|
+
judgment,
|
|
119
|
+
...(receipt && typeof receipt.id === 'string' ? { id: receipt.id } : {}),
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
53
123
|
let alive = true;
|
|
54
124
|
onDestroy(() => {
|
|
55
125
|
alive = false;
|
|
@@ -189,16 +259,16 @@ function choose(field: string, id: string) {
|
|
|
189
259
|
}
|
|
190
260
|
</script>
|
|
191
261
|
<article data-testid={`action-${action.review.actionId}`}>
|
|
192
|
-
<h3>{action.handlerId ?? action.review.actionId}</h3>
|
|
262
|
+
<h3>{label ?? action.handlerId ?? action.review.actionId}</h3>
|
|
193
263
|
{#if action.origin === 'reviewer-selected'}<p>{t(M['ingestion.reviewerSelected'])}</p>{/if}
|
|
194
|
-
<p>{t(M['ingestion.state'])}: {action.review.state}</p>
|
|
264
|
+
<p data-testid="review-state">{t(M['ingestion.state'])}: {stateKey ? t(M[stateKey]) : action.review.state}</p>
|
|
195
265
|
{#if action.args !== undefined}
|
|
196
|
-
<h4>{t(M['ingestion.effects'])}</h4
|
|
197
|
-
<
|
|
266
|
+
<h4>{t(M['ingestion.effects'])}</h4>{#if effects.length}<dl class="effects" data-testid="effects">{#each effects as [name, value] (name)}<dt>{name}</dt><dd>{value}</dd>{/each}</dl>{/if}
|
|
267
|
+
<ArgumentFields bind:value={args} label={t(M['ingestion.args'])} catalog={fields} {candidates} {evidence} readonly={!editable || !!action.plan} {host} {itemId} {deny} />
|
|
198
268
|
{#if editable}
|
|
199
269
|
{#if missing.length}<h4>{t(M['ingestion.missing'])}</h4><ul>{#each missing as name (name)}<li>{name}</li>{/each}</ul>{/if}
|
|
200
270
|
<Input aria-label={t(M['ingestion.reason'])} bind:value={reason} />
|
|
201
|
-
{#if !action.plan && catalog}<CandidatePicker {host} {itemId} {catalog} {deny} onchoose={choose} />{/if}
|
|
271
|
+
{#if !action.plan && catalog && !drawsFields}<CandidatePicker {host} {itemId} {catalog} {deny} onchoose={choose} />{/if}
|
|
202
272
|
{#if dirty}<p>{t(M['ingestion.reviewAgain'])}</p>{/if}
|
|
203
273
|
{#if invalid}<p role="alert">{t(M['ingestion.invalid'])}</p>{/if}
|
|
204
274
|
<div class="controls">{#if waiting && !resolved}<Button disabled={dirty} onclick={() => decide('approve')}>{t(M['ingestion.approve'])}</Button>{/if}{#if !action.plan && !resolved}<Button disabled={!dirty} onclick={saveEdits}>{t(M['ingestion.correct'])}</Button>{/if}{#if waiting}<Button onclick={() => decide('reject')}>{t(M['ingestion.reject'])}</Button>{#if !resolved}<Button onclick={() => decide('defer')}>{t(M['ingestion.defer'])}</Button>{/if}{/if}</div>
|
|
@@ -213,7 +283,7 @@ function choose(field: string, id: string) {
|
|
|
213
283
|
{/if}
|
|
214
284
|
{#if action.review.state === 'stale' && !action.stalePlan && action.attemptId && host.editAction && !resolved}
|
|
215
285
|
<p>{t(M['ingestion.reviewAgain'])}</p>
|
|
216
|
-
<
|
|
286
|
+
<ArgumentFields bind:value={args} label={t(M['ingestion.args'])} catalog={fields} {candidates} {evidence} {host} {itemId} {deny} />
|
|
217
287
|
{#if invalid}<p role="alert">{t(M['ingestion.invalid'])}</p>{/if}
|
|
218
288
|
<Button onclick={saveEdits}>{t(M['ingestion.correct'])}</Button>
|
|
219
289
|
{/if}
|
|
@@ -224,11 +294,11 @@ function choose(field: string, id: string) {
|
|
|
224
294
|
<Button onclick={editPlan}>{t(M['ingestion.editPlan'])}</Button>
|
|
225
295
|
{/if}
|
|
226
296
|
{#if action.result}<h4>{t(M['ingestion.result'])}</h4><pre>{JSON.stringify(action.result,null,2)}</pre>{/if}
|
|
227
|
-
{#if host.feedback && action.args !== undefined && action.origin !== 'reviewer-selected'}<fieldset><legend>{t(M['ingestion.feedback'])}</legend><Textarea aria-label={t(M['ingestion.comment'])} bind:value={comment} /><Button disabled={dirty}
|
|
297
|
+
{#if host.feedback && action.args !== undefined && action.origin !== 'reviewer-selected'}<fieldset><legend>{t(M['ingestion.feedback'])}</legend><Textarea aria-label={t(M['ingestion.comment'])} bind:value={comment} /><Button disabled={dirty} aria-pressed={judged?.judgment === 'correct'} onclick={() => judge('correct')}>{t(M['ingestion.correctJudgment'])}</Button><Button disabled={dirty} aria-pressed={judged?.judgment === 'incorrect'} onclick={() => judge('incorrect')}>{t(M['ingestion.incorrectJudgment'])}</Button>{#if sent}<p role="status" data-testid="feedback-sent">{t(M['ingestion.feedbackSent'])}</p>{/if}</fieldset>{/if}
|
|
228
298
|
</article>
|
|
229
299
|
<style>
|
|
230
300
|
@layer smrt.reset, smrt.structure, smrt.theme;
|
|
231
301
|
@layer smrt.theme {
|
|
232
|
-
article{min-width:0;overflow-wrap:anywhere;border-block-start:var(--smrt-hairline);padding-block:1rem}.controls{display:flex;flex-wrap:wrap;gap:.5rem;margin-block:.75rem}pre{white-space:pre-wrap;overflow-wrap:anywhere}fieldset{margin-block:1rem;min-width:0}
|
|
302
|
+
article{min-width:0;overflow-wrap:anywhere;border-block-start:var(--smrt-hairline);padding-block:1rem}.controls{display:flex;flex-wrap:wrap;gap:.5rem;margin-block:.75rem}pre{white-space:pre-wrap;overflow-wrap:anywhere}fieldset{margin-block:1rem;min-width:0}.effects{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:.25rem 1rem;margin-block:.5rem}.effects dt{font-weight:600}.effects dd{margin:0}
|
|
233
303
|
}
|
|
234
304
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** A reviewer operation whose result may finish the item for this reviewer. */
|
|
2
2
|
export type ReviewOutcome = 'approve' | 'apply' | 'resolve';
|
|
3
|
-
import type { ProposalCatalogEntry } from '../../proposal-dto.js';
|
|
3
|
+
import type { ProposalCandidate, ProposalCatalogEntry } from '../../proposal-dto.js';
|
|
4
4
|
import type { IntakeReviewHost, ReviewAction } from '../../review-dto.js';
|
|
5
5
|
export interface Props {
|
|
6
6
|
host: IntakeReviewHost;
|
|
@@ -15,6 +15,35 @@ export interface Props {
|
|
|
15
15
|
deny: () => void;
|
|
16
16
|
/** Whether this editor holds unsent input (arguments, reason, comment). */
|
|
17
17
|
ondirty?: (dirty: boolean) => void;
|
|
18
|
+
/** The handler's display label (#4503); the handler id is drawn when absent. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The handler whose `argsSchema` draws the arguments as fields (#4503),
|
|
22
|
+
* the offered or reviewer catalog entry; the raw JSON stays under
|
|
23
|
+
* "Advanced". Without it (or a schema without properties) JSON only.
|
|
24
|
+
*/
|
|
25
|
+
fields?: ProposalCatalogEntry;
|
|
26
|
+
/** Candidates already known for reference labels (the generation's offer). */
|
|
27
|
+
candidates?: readonly ProposalCandidate[];
|
|
28
|
+
/** Originals an evidence reference may name. */
|
|
29
|
+
evidence?: ReadonlyArray<{
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* The judgment this reviewer sent for the action in this view (kept by the
|
|
35
|
+
* parent across the reload that follows), with the receipt id when the
|
|
36
|
+
* host returned one.
|
|
37
|
+
*/
|
|
38
|
+
sent?: {
|
|
39
|
+
judgment: 'correct' | 'incorrect';
|
|
40
|
+
id?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Told about a judgment once the host accepted it. */
|
|
43
|
+
onjudged?: (judged: {
|
|
44
|
+
judgment: 'correct' | 'incorrect';
|
|
45
|
+
id?: string;
|
|
46
|
+
}) => void;
|
|
18
47
|
}
|
|
19
48
|
declare const ActionReview: import("svelte").Component<Props, {}, "">;
|
|
20
49
|
type ActionReview = ReturnType<typeof ActionReview>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionReview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ActionReview.svelte.ts"],"names":[],"mappings":"AAGA,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAQ5D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ActionReview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ActionReview.svelte.ts"],"names":[],"mappings":"AAGA,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAQ5D,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAW1E,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wFAAwF;IACxF,GAAG,EAAE,CACH,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EACjC,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,8EAA8E;IAC9E,UAAU,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,gDAAgD;IAChD,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD;;;;OAIG;IACH,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,SAAS,GAAG,WAAW,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,SAAS,GAAG,WAAW,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjF;AAiQD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
Combobox,
|
|
5
|
+
Input,
|
|
6
|
+
Select,
|
|
7
|
+
Textarea,
|
|
8
|
+
} from '@happyvertical/smrt-ui/forms';
|
|
9
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
10
|
+
import { onDestroy } from 'svelte';
|
|
11
|
+
import type {
|
|
12
|
+
ProposalCandidate,
|
|
13
|
+
ProposalCatalogEntry,
|
|
14
|
+
} from '../../proposal-dto.js';
|
|
15
|
+
import type { IntakeReviewHost } from '../../review-dto.js';
|
|
16
|
+
import {
|
|
17
|
+
type ArgumentField,
|
|
18
|
+
argumentFields,
|
|
19
|
+
localDateTime,
|
|
20
|
+
} from '../argument-fields.js';
|
|
21
|
+
import { M } from '../i18n.js';
|
|
22
|
+
import { argumentsObject } from '../view-utils.js';
|
|
23
|
+
export interface Props {
|
|
24
|
+
/** The arguments draft as JSON text (bindable); the fields edit it in place. */
|
|
25
|
+
value: string;
|
|
26
|
+
/** Accessible name of the JSON textarea. */
|
|
27
|
+
label: string;
|
|
28
|
+
/** The handler whose `argsSchema`/`references` shape the fields; JSON only when absent. */
|
|
29
|
+
catalog?: ProposalCatalogEntry;
|
|
30
|
+
/** Candidates already known (the generation's offer), for reference labels. */
|
|
31
|
+
candidates?: readonly ProposalCandidate[];
|
|
32
|
+
/** Originals an evidence reference may name. */
|
|
33
|
+
evidence?: ReadonlyArray<{ id: string; label: string }>;
|
|
34
|
+
readonly?: boolean;
|
|
35
|
+
rows?: number;
|
|
36
|
+
host: IntakeReviewHost;
|
|
37
|
+
itemId: string;
|
|
38
|
+
/** A failed candidate search fails closed: the owning view discards its payloads. */
|
|
39
|
+
deny: () => void;
|
|
40
|
+
}
|
|
41
|
+
let {
|
|
42
|
+
value = $bindable(),
|
|
43
|
+
label,
|
|
44
|
+
catalog,
|
|
45
|
+
candidates = [],
|
|
46
|
+
evidence = [],
|
|
47
|
+
readonly = false,
|
|
48
|
+
rows = 8,
|
|
49
|
+
host,
|
|
50
|
+
itemId,
|
|
51
|
+
deny,
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
const { t } = useI18n();
|
|
54
|
+
const fields = $derived(
|
|
55
|
+
catalog ? argumentFields(catalog.argsSchema, catalog.references) : [],
|
|
56
|
+
);
|
|
57
|
+
const parsed = $derived.by((): Record<string, unknown> | undefined => {
|
|
58
|
+
try {
|
|
59
|
+
return argumentsObject(value);
|
|
60
|
+
} catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Labels of candidates seen in this editor (searched or offered), by model and id.
|
|
65
|
+
let found = $state<Record<string, ProposalCandidate[]>>({}),
|
|
66
|
+
known = $state<Record<string, string>>({}),
|
|
67
|
+
searching = $state<Record<string, boolean>>({});
|
|
68
|
+
let alive = true;
|
|
69
|
+
// Newest search per field; an older answer is dropped.
|
|
70
|
+
const searches: Record<string, number> = {};
|
|
71
|
+
onDestroy(() => {
|
|
72
|
+
alive = false;
|
|
73
|
+
});
|
|
74
|
+
function candidateLabel(model: string, id: unknown): string | undefined {
|
|
75
|
+
if (typeof id !== 'string' || !id) return undefined;
|
|
76
|
+
return (
|
|
77
|
+
known[`${model}:${id}`] ??
|
|
78
|
+
candidates.find((entry) => entry.model === model && entry.id === id)?.label
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function set(name: string, next: unknown) {
|
|
82
|
+
if (!parsed) return;
|
|
83
|
+
const draft = { ...parsed };
|
|
84
|
+
if (next === undefined) delete draft[name];
|
|
85
|
+
else draft[name] = next;
|
|
86
|
+
value = JSON.stringify(draft, null, 2);
|
|
87
|
+
}
|
|
88
|
+
async function search(
|
|
89
|
+
field: ArgumentField & { kind: 'candidate' },
|
|
90
|
+
query: string,
|
|
91
|
+
) {
|
|
92
|
+
if (!catalog) return;
|
|
93
|
+
const ticket = (searches[field.name] ?? 0) + 1;
|
|
94
|
+
searches[field.name] = ticket;
|
|
95
|
+
searching[field.name] = true;
|
|
96
|
+
try {
|
|
97
|
+
const page = await host.candidates({
|
|
98
|
+
itemId,
|
|
99
|
+
handlerId: catalog.id,
|
|
100
|
+
handlerVersion: catalog.version,
|
|
101
|
+
query,
|
|
102
|
+
});
|
|
103
|
+
if (!alive || ticket !== searches[field.name]) return;
|
|
104
|
+
const matches = page.items.filter((entry) => entry.model === field.model);
|
|
105
|
+
found[field.name] = matches;
|
|
106
|
+
for (const entry of matches)
|
|
107
|
+
known[`${entry.model}:${entry.id}`] = entry.label;
|
|
108
|
+
} catch {
|
|
109
|
+
if (alive) deny();
|
|
110
|
+
} finally {
|
|
111
|
+
if (alive && ticket === searches[field.name]) searching[field.name] = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function choose(field: ArgumentField & { kind: 'candidate' }, id: string) {
|
|
115
|
+
// Only an id from the authorized page (or the current value) is accepted.
|
|
116
|
+
if (!id) return set(field.name, undefined);
|
|
117
|
+
if (
|
|
118
|
+
found[field.name]?.some((entry) => entry.id === id) ||
|
|
119
|
+
candidates.some((entry) => entry.model === field.model && entry.id === id)
|
|
120
|
+
)
|
|
121
|
+
set(field.name, id);
|
|
122
|
+
}
|
|
123
|
+
function number(field: ArgumentField, text: string) {
|
|
124
|
+
if (!text.trim()) return set(field.name, undefined);
|
|
125
|
+
const next = Number(text);
|
|
126
|
+
if (!Number.isFinite(next)) return;
|
|
127
|
+
if (field.kind === 'integer' && !Number.isSafeInteger(next)) return;
|
|
128
|
+
set(field.name, next);
|
|
129
|
+
}
|
|
130
|
+
function text(input: unknown): string {
|
|
131
|
+
return typeof input === 'string' || typeof input === 'number'
|
|
132
|
+
? String(input)
|
|
133
|
+
: '';
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
{#if fields.length}
|
|
137
|
+
<fieldset class="argument-fields" data-testid="argument-fields" disabled={readonly}>
|
|
138
|
+
<legend>{t(M['ingestion.fields'])}</legend>
|
|
139
|
+
{#if !parsed}<p role="status">{t(M['ingestion.fieldsUnavailable'])}</p>{/if}
|
|
140
|
+
{#each fields as field (field.name)}
|
|
141
|
+
{@const current = parsed?.[field.name]}
|
|
142
|
+
{@const name = field.required ? `${field.title} *` : field.title}
|
|
143
|
+
<div class="field" data-field={field.name}>
|
|
144
|
+
{#if field.kind === 'candidate'}
|
|
145
|
+
{@const options = (found[field.name] ?? []).map((entry) => ({ value: entry.id, label: entry.label }))}
|
|
146
|
+
<Combobox label={name} {options} value={text(current)} valueLabel={candidateLabel(field.model, current) ?? text(current)} filter={false} disabled={readonly || !parsed} busy={!!searching[field.name]} status={searching[field.name] ? t(M['ingestion.searching']) : options.length ? undefined : t(M['ingestion.noMatches'])} onquery={(query) => search(field, query)} onvaluechange={(id) => choose(field, id)} />
|
|
147
|
+
{:else if field.kind === 'boolean'}
|
|
148
|
+
<Checkbox label={name} checked={current === true} disabled={readonly || !parsed} onchange={(event) => set(field.name, (event.currentTarget as HTMLInputElement).checked)} />
|
|
149
|
+
{:else if field.kind === 'enum'}
|
|
150
|
+
<Select aria-label={name} value={text(current)} disabled={readonly || !parsed} onchange={(event) => { const chosen = (event.currentTarget as HTMLSelectElement).value; set(field.name, chosen === '' ? undefined : field.options.find((option) => String(option) === chosen)); }}><option value="">{t(M['ingestion.unset'])}</option>{#each field.options as option (option)}<option value={String(option)}>{option}</option>{/each}</Select>
|
|
151
|
+
{:else if field.kind === 'evidence' && evidence.length}
|
|
152
|
+
<Select aria-label={name} value={text(current)} disabled={readonly || !parsed} onchange={(event) => { const chosen = (event.currentTarget as HTMLSelectElement).value; set(field.name, chosen || undefined); }}><option value="">{t(M['ingestion.unset'])}</option>{#each evidence as part (part.id)}<option value={part.id}>{part.label}</option>{/each}</Select>
|
|
153
|
+
{:else if field.kind === 'number' || field.kind === 'integer'}
|
|
154
|
+
<Input type="number" aria-label={name} value={text(current)} step={field.kind === 'integer' ? 1 : 'any'} min={field.minimum} max={field.maximum} disabled={readonly || !parsed} oninput={(event) => number(field, (event.currentTarget as HTMLInputElement).value)} />
|
|
155
|
+
{:else if field.kind === 'date'}
|
|
156
|
+
<Input type="date" aria-label={name} value={text(current)} disabled={readonly || !parsed} oninput={(event) => { const chosen = (event.currentTarget as HTMLInputElement).value; set(field.name, chosen || undefined); }} />
|
|
157
|
+
{:else if field.kind === 'date-time'}
|
|
158
|
+
<Input type="datetime-local" aria-label={name} value={localDateTime(current)} disabled={readonly || !parsed} oninput={(event) => { const chosen = (event.currentTarget as HTMLInputElement).value; const at = chosen ? new Date(chosen) : undefined; if (at && Number.isNaN(at.getTime())) return; set(field.name, at?.toISOString()); }} />
|
|
159
|
+
{:else}
|
|
160
|
+
<Input aria-label={name} value={text(current)} maxlength={field.kind === 'text' ? field.maxLength : undefined} disabled={readonly || !parsed} oninput={(event) => { const chosen = (event.currentTarget as HTMLInputElement).value; set(field.name, chosen === '' ? undefined : chosen); }} />
|
|
161
|
+
{/if}
|
|
162
|
+
</div>
|
|
163
|
+
{/each}
|
|
164
|
+
</fieldset>
|
|
165
|
+
<details class="advanced" data-testid="advanced-arguments"><summary>{t(M['ingestion.advanced'])}</summary>
|
|
166
|
+
<Textarea aria-label={label} bind:value {readonly} {rows} />
|
|
167
|
+
</details>
|
|
168
|
+
{:else}
|
|
169
|
+
<Textarea aria-label={label} bind:value {readonly} {rows} />
|
|
170
|
+
{/if}
|
|
171
|
+
<style>
|
|
172
|
+
@layer smrt.reset, smrt.structure, smrt.theme;
|
|
173
|
+
@layer smrt.theme {
|
|
174
|
+
.argument-fields{display:grid;gap:.75rem;min-width:0;margin-block:.5rem}.field{min-width:0}.advanced{margin-block:.5rem}
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProposalCandidate, ProposalCatalogEntry } from '../../proposal-dto.js';
|
|
2
|
+
import type { IntakeReviewHost } from '../../review-dto.js';
|
|
3
|
+
export interface Props {
|
|
4
|
+
/** The arguments draft as JSON text (bindable); the fields edit it in place. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Accessible name of the JSON textarea. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** The handler whose `argsSchema`/`references` shape the fields; JSON only when absent. */
|
|
9
|
+
catalog?: ProposalCatalogEntry;
|
|
10
|
+
/** Candidates already known (the generation's offer), for reference labels. */
|
|
11
|
+
candidates?: readonly ProposalCandidate[];
|
|
12
|
+
/** Originals an evidence reference may name. */
|
|
13
|
+
evidence?: ReadonlyArray<{
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}>;
|
|
17
|
+
readonly?: boolean;
|
|
18
|
+
rows?: number;
|
|
19
|
+
host: IntakeReviewHost;
|
|
20
|
+
itemId: string;
|
|
21
|
+
/** A failed candidate search fails closed: the owning view discards its payloads. */
|
|
22
|
+
deny: () => void;
|
|
23
|
+
}
|
|
24
|
+
declare const ArgumentFields: import("svelte").Component<Props, {}, "value">;
|
|
25
|
+
type ArgumentFields = ReturnType<typeof ArgumentFields>;
|
|
26
|
+
export default ArgumentFields;
|
|
27
|
+
//# sourceMappingURL=ArgumentFields.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArgumentFields.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ArgumentFields.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,MAAM,WAAW,KAAK;IACpB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,gDAAgD;IAChD,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAiJD,QAAA,MAAM,cAAc,gDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|