@aglyn/plugins-ai 1.0.0-beta.237 → 1.0.0-beta.239
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/package.json +17 -17
- package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
- package/src/lib/components/ai-credits-prompt.component.js +84 -34
- package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
- package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
- package/src/lib/components/ai-job-cancel.component.js +156 -0
- package/src/lib/components/ai-job-cancel.component.js.map +1 -0
- package/src/lib/components/ai-job-links.js +8 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +12 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +141 -72
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-jobs-store.d.ts +12 -0
- package/src/lib/components/ai-jobs-store.js +15 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +22 -2
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-usage-meter-refresh.d.ts +35 -0
- package/src/lib/components/ai-usage-meter-refresh.js +63 -0
- package/src/lib/components/ai-usage-meter-refresh.js.map +1 -0
- package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
- package/src/lib/components/ai-usage-strip.component.js +40 -9
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +12 -16
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +91 -51
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-usage-meter.d.ts +2 -0
- package/src/lib/components/use-ai-usage-meter.js +8 -3
- package/src/lib/components/use-ai-usage-meter.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +3 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +19 -3
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +33 -3
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
- package/src/lib/jobs/ai-job-cancel.js +102 -0
- package/src/lib/jobs/ai-job-cancel.js.map +1 -0
- package/src/lib/jobs/ai-job-component-step.js +4 -1
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-draft-ids.js +7 -2
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +2 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-form-dataset.d.ts +100 -0
- package/src/lib/jobs/ai-job-form-dataset.js +161 -0
- package/src/lib/jobs/ai-job-form-dataset.js.map +1 -0
- package/src/lib/jobs/ai-job-form-step.d.ts +15 -3
- package/src/lib/jobs/ai-job-form-step.js +41 -10
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-layout-language.js +13 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +6 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +45 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +38 -1
- package/src/lib/jobs/ai-job-plan-step.js +90 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
- package/src/lib/jobs/ai-job-site-content.js +66 -7
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +170 -0
- package/src/lib/jobs/ai-job-site-datasets.js +499 -0
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +15 -8
- package/src/lib/jobs/ai-job-site-step.js +152 -30
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +4 -1
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +37 -12
- package/src/lib/jobs/ai-jobs.js +184 -28
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
- package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.d.ts +30 -0
- package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js +637 -0
- package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -0
- package/src/lib/layout-language/ai-layout-compiler.js +397 -9
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +5 -3
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -3
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +17 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +44 -3
- package/src/lib/layout-language/ai-layout-listings.js +118 -50
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
- package/src/lib/layout-language/ai-layout-storefront.js +223 -0
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +11 -1
- package/src/lib/model/ai-build-job.js +35 -6
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +23 -3
- package/src/lib/model/ai-build-plan.js +47 -6
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +2 -2
- package/src/lib/model/ai-build-progress.js +6 -4
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +69 -2
- package/src/lib/model/ai-credit-estimate.js +91 -3
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
- package/src/lib/model/ai-job-cancel-copy.js +53 -0
- package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
- package/src/lib/model/ai-job-failure-copy.js +8 -3
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.js +7 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +38 -4
- package/src/lib/model/ai-site-job.js +50 -14
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +4 -1
- package/src/lib/model/ai-site-kinds.js +34 -6
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +77 -0
- package/src/lib/runtime/ai-dataset-generation.js +127 -0
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +77 -1
- package/src/lib/runtime/ai-doctrine-validators.js +280 -6
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +10 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +2 -0
- package/src/lib/runtime/ai-node-tree.js +16 -0
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +53 -7
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
- package/src/lib/server/ai-jobs-cancel.js +27 -9
- package/src/lib/server/ai-jobs-cancel.js.map +1 -1
- package/src/lib/server/ai-jobs-gate.d.ts +12 -2
- package/src/lib/server/ai-jobs-gate.js +20 -9
- package/src/lib/server/ai-jobs-gate.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +68 -0
- package/src/lib/tools/ai-dataset-tool.js +231 -0
- package/src/lib/tools/ai-dataset-tool.js.map +1 -0
- package/src/lib/usage/ai-usage-meter.d.ts +2 -1
- package/src/lib/usage/ai-usage-meter.js +17 -1
- package/src/lib/usage/ai-usage-meter.js.map +1 -1
- package/src/lib/usage/ai-usage-wire.d.ts +6 -0
- package/src/lib/usage/ai-usage-wire.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +7 -0
- package/src/lib/usage/assist-usage.js +14 -4
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
- package/src/lib/usage/free-ai-credits-left.js +30 -4
- package/src/lib/usage/free-ai-credits-left.js.map +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
|
|
18
|
+
import type { NodesMap } from '@aglyn/aglyn/types/nodes';
|
|
19
|
+
import type { AiSiteInventory } from '../model/ai-site-inventory';
|
|
20
|
+
/**
|
|
21
|
+
* A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's
|
|
22
|
+
* forms can write to datasets — a volunteer sign-up into a Volunteers
|
|
23
|
+
* dataset, an RSVP into an event's attendees, a catering inquiry into
|
|
24
|
+
* Inquiries).
|
|
25
|
+
*
|
|
26
|
+
* The plan says which dataset a form writes to (`writesTo` on its creation);
|
|
27
|
+
* it is never inferred from what a section places. The dataset is built
|
|
28
|
+
* first — a site start's dataset unit, a build's dataset item, or one the
|
|
29
|
+
* site already has — and the form step binds the form it generated the way a
|
|
30
|
+
* person binds one on the Form element: the form node's `datasetId`, and
|
|
31
|
+
* each Form Field's `datasetFieldId` where the dataset has a field of the
|
|
32
|
+
* same name and a type the submitted value can be stored as. A field the
|
|
33
|
+
* dataset lacks is left unbound, as the Form element's mapping leaves one:
|
|
34
|
+
* its value stays in the Inbox copy, and a form never adds a field to a
|
|
35
|
+
* dataset.
|
|
36
|
+
*
|
|
37
|
+
* The gates are the platform's. The binding is made only where the plan
|
|
38
|
+
* includes the data store (`dataStore`), the data plugin's writer is loaded
|
|
39
|
+
* here and reads the dataset, and the dataset is one this job made or one the
|
|
40
|
+
* site's inventory shows. Records, their allowance and storage are held at
|
|
41
|
+
* each submission by the data plugin's own record target. Anywhere else the
|
|
42
|
+
* form is the form it always was, its submissions in the Inbox.
|
|
43
|
+
*/
|
|
44
|
+
/** The unit input a dataset this job made for the form rides in, by id. */
|
|
45
|
+
export declare const AI_FORM_DATASET_MADE_INPUT = "formDatasetMade";
|
|
46
|
+
/** One field of the dataset a form writes to, as its writer reports it: storage type words. */
|
|
47
|
+
export interface AiFormDatasetField {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
type: string;
|
|
51
|
+
}
|
|
52
|
+
/** The dataset a form writes to, with the fields a submission may fill. */
|
|
53
|
+
export interface AiFormDataset {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
fields: AiFormDatasetField[];
|
|
57
|
+
}
|
|
58
|
+
/** A field name as two spellings of it compare: `fullName`, `full_name` and "Full name" are one. */
|
|
59
|
+
export declare function aiFormFieldKey(name: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Whether a submitted value of this form field type can be stored in a
|
|
62
|
+
* dataset field of this storage type. Every value a form submits is text, so
|
|
63
|
+
* a text field takes any of them and a number field takes a rating; any
|
|
64
|
+
* other pairing is left unbound rather than refused at every submission.
|
|
65
|
+
*/
|
|
66
|
+
export declare function aiFormFieldFitsDataset(fieldType: string, datasetType: string): boolean;
|
|
67
|
+
/** What a binding came to: the fields bound, and the fields the dataset has no place for. */
|
|
68
|
+
export interface AiFormDatasetBinding {
|
|
69
|
+
bound: string[];
|
|
70
|
+
unbound: string[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Binds a form design to a dataset, in place: the form node's `datasetId`,
|
|
74
|
+
* and `datasetFieldId` on each Form Field the dataset has a field for, by its
|
|
75
|
+
* `fieldName` or its label, each dataset field taken once. The platform's
|
|
76
|
+
* consent field is never bound. A binding the design already carried is
|
|
77
|
+
* replaced, so what the model drew never chooses where a record lands.
|
|
78
|
+
*/
|
|
79
|
+
export declare function aiBindFormToDataset(nodes: NodesMap, formNodeId: string, dataset: AiFormDataset): AiFormDatasetBinding;
|
|
80
|
+
export interface AiFormDatasetReadDeps {
|
|
81
|
+
writerFor?: typeof pluginResourceDraftWriter;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The dataset a form may write to, or `null` where it may not: the plan
|
|
85
|
+
* includes no data store, the data plugin's writer is not loaded here, the
|
|
86
|
+
* dataset is neither one this job made nor one the site's inventory shows,
|
|
87
|
+
* or it has no field a submission could fill.
|
|
88
|
+
*/
|
|
89
|
+
export declare function aiReadFormDataset(input: {
|
|
90
|
+
hostId: string;
|
|
91
|
+
id: string | null | undefined;
|
|
92
|
+
org: object | null;
|
|
93
|
+
inventory: Pick<AiSiteInventory, 'datasets'> | null;
|
|
94
|
+
/** The dataset this job made for the form, by id. */
|
|
95
|
+
madeId?: unknown;
|
|
96
|
+
}, deps?: AiFormDatasetReadDeps): Promise<AiFormDataset | null>;
|
|
97
|
+
/** What the form's generation is told about the dataset it writes to. */
|
|
98
|
+
export declare function aiFormDatasetPromptLine(dataset: AiFormDataset): string;
|
|
99
|
+
/** What the form's row says about where its submissions are also saved. */
|
|
100
|
+
export declare function aiFormDatasetNote(dataset: AiFormDataset, binding: AiFormDatasetBinding): string;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
18
|
+
import { collectFormFieldNodeIds, FORM_COMPONENT_ID, isMarketingConsentFieldName } from "@aglyn/aglyn/app-utils/forms";
|
|
19
|
+
import { pluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
|
|
20
|
+
import { AI_DATASET_DRAFT_RESOURCE } from "./ai-build-unit-outcome.js";
|
|
21
|
+
/**
|
|
22
|
+
* A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's
|
|
23
|
+
* forms can write to datasets — a volunteer sign-up into a Volunteers
|
|
24
|
+
* dataset, an RSVP into an event's attendees, a catering inquiry into
|
|
25
|
+
* Inquiries).
|
|
26
|
+
*
|
|
27
|
+
* The plan says which dataset a form writes to (`writesTo` on its creation);
|
|
28
|
+
* it is never inferred from what a section places. The dataset is built
|
|
29
|
+
* first — a site start's dataset unit, a build's dataset item, or one the
|
|
30
|
+
* site already has — and the form step binds the form it generated the way a
|
|
31
|
+
* person binds one on the Form element: the form node's `datasetId`, and
|
|
32
|
+
* each Form Field's `datasetFieldId` where the dataset has a field of the
|
|
33
|
+
* same name and a type the submitted value can be stored as. A field the
|
|
34
|
+
* dataset lacks is left unbound, as the Form element's mapping leaves one:
|
|
35
|
+
* its value stays in the Inbox copy, and a form never adds a field to a
|
|
36
|
+
* dataset.
|
|
37
|
+
*
|
|
38
|
+
* The gates are the platform's. The binding is made only where the plan
|
|
39
|
+
* includes the data store (`dataStore`), the data plugin's writer is loaded
|
|
40
|
+
* here and reads the dataset, and the dataset is one this job made or one the
|
|
41
|
+
* site's inventory shows. Records, their allowance and storage are held at
|
|
42
|
+
* each submission by the data plugin's own record target. Anywhere else the
|
|
43
|
+
* form is the form it always was, its submissions in the Inbox.
|
|
44
|
+
*/ /** The unit input a dataset this job made for the form rides in, by id. */ export const AI_FORM_DATASET_MADE_INPUT = 'formDatasetMade';
|
|
45
|
+
/** A field name as two spellings of it compare: `fullName`, `full_name` and "Full name" are one. */ export function aiFormFieldKey(name) {
|
|
46
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
47
|
+
}
|
|
48
|
+
const NUMBER_TYPES = new Set([
|
|
49
|
+
'float',
|
|
50
|
+
'int32',
|
|
51
|
+
'int64',
|
|
52
|
+
'number',
|
|
53
|
+
'integer'
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Whether a submitted value of this form field type can be stored in a
|
|
57
|
+
* dataset field of this storage type. Every value a form submits is text, so
|
|
58
|
+
* a text field takes any of them and a number field takes a rating; any
|
|
59
|
+
* other pairing is left unbound rather than refused at every submission.
|
|
60
|
+
*/ export function aiFormFieldFitsDataset(fieldType, datasetType) {
|
|
61
|
+
if (datasetType === 'text') return true;
|
|
62
|
+
if (NUMBER_TYPES.has(datasetType)) return fieldType === 'rating';
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Binds a form design to a dataset, in place: the form node's `datasetId`,
|
|
67
|
+
* and `datasetFieldId` on each Form Field the dataset has a field for, by its
|
|
68
|
+
* `fieldName` or its label, each dataset field taken once. The platform's
|
|
69
|
+
* consent field is never bound. A binding the design already carried is
|
|
70
|
+
* replaced, so what the model drew never chooses where a record lands.
|
|
71
|
+
*/ export function aiBindFormToDataset(nodes, formNodeId, dataset) {
|
|
72
|
+
var _form_props;
|
|
73
|
+
const result = {
|
|
74
|
+
bound: [],
|
|
75
|
+
unbound: []
|
|
76
|
+
};
|
|
77
|
+
const form = nodes[formNodeId];
|
|
78
|
+
if ((form == null ? void 0 : form.componentId) !== FORM_COMPONENT_ID) return result;
|
|
79
|
+
const props = _extends({}, (_form_props = form.props) != null ? _form_props : {});
|
|
80
|
+
delete props['datasetName'];
|
|
81
|
+
props['datasetId'] = dataset.id;
|
|
82
|
+
form.props = props;
|
|
83
|
+
const taken = new Set();
|
|
84
|
+
for (const id of collectFormFieldNodeIds(nodes, formNodeId)){
|
|
85
|
+
var _field_props, _fieldProps_fieldName, _fieldProps_label, _fieldProps_fieldType, _matching;
|
|
86
|
+
const field = nodes[id];
|
|
87
|
+
if (!field) continue;
|
|
88
|
+
const fieldProps = _extends({}, (_field_props = field.props) != null ? _field_props : {});
|
|
89
|
+
delete fieldProps['datasetFieldId'];
|
|
90
|
+
field.props = fieldProps;
|
|
91
|
+
const fieldName = String((_fieldProps_fieldName = fieldProps['fieldName']) != null ? _fieldProps_fieldName : '').trim();
|
|
92
|
+
if (!fieldName || isMarketingConsentFieldName(fieldName)) continue;
|
|
93
|
+
const label = String((_fieldProps_label = fieldProps['label']) != null ? _fieldProps_label : '').trim();
|
|
94
|
+
const fieldType = String((_fieldProps_fieldType = fieldProps['fieldType']) != null ? _fieldProps_fieldType : 'text');
|
|
95
|
+
// Its name first, then its label: `fullName` is "Full name" before "Your name" is.
|
|
96
|
+
const matching = (key)=>key ? dataset.fields.find((one)=>!taken.has(one.id) && aiFormFieldKey(one.name) === key && aiFormFieldFitsDataset(fieldType, one.type)) : undefined;
|
|
97
|
+
const match = (_matching = matching(aiFormFieldKey(fieldName))) != null ? _matching : matching(aiFormFieldKey(label));
|
|
98
|
+
if (!match) {
|
|
99
|
+
result.unbound.push(label || fieldName);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
taken.add(match.id);
|
|
103
|
+
fieldProps['datasetFieldId'] = match.id;
|
|
104
|
+
result.bound.push(fieldName);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
109
|
+
/**
|
|
110
|
+
* The dataset a form may write to, or `null` where it may not: the plan
|
|
111
|
+
* includes no data store, the data plugin's writer is not loaded here, the
|
|
112
|
+
* dataset is neither one this job made nor one the site's inventory shows,
|
|
113
|
+
* or it has no field a submission could fill.
|
|
114
|
+
*/ export async function aiReadFormDataset(input, deps = {}) {
|
|
115
|
+
var _ref, _deps_writerFor, _record_facts;
|
|
116
|
+
var _input_inventory;
|
|
117
|
+
const id = str(input.id);
|
|
118
|
+
if (!id) return null;
|
|
119
|
+
if (!checkEntitlement(input.org, 'dataStore')) return null;
|
|
120
|
+
const known = id === str(input.madeId) || ((_ref = (_input_inventory = input.inventory) == null ? void 0 : _input_inventory.datasets) != null ? _ref : []).some((row)=>row.id === id);
|
|
121
|
+
if (!known) return null;
|
|
122
|
+
const keeper = ((_deps_writerFor = deps.writerFor) != null ? _deps_writerFor : pluginResourceDraftWriter)(AI_DATASET_DRAFT_RESOURCE);
|
|
123
|
+
if (!keeper) return null;
|
|
124
|
+
const record = await keeper.writer.read({
|
|
125
|
+
hostId: input.hostId,
|
|
126
|
+
id
|
|
127
|
+
}).catch((error)=>{
|
|
128
|
+
console.error('ai form: the dataset it writes to could not be read', {
|
|
129
|
+
hostId: input.hostId,
|
|
130
|
+
id,
|
|
131
|
+
error
|
|
132
|
+
});
|
|
133
|
+
return null;
|
|
134
|
+
});
|
|
135
|
+
if (!record) return null;
|
|
136
|
+
const facts = (_record_facts = record.facts) != null ? _record_facts : {};
|
|
137
|
+
const address = str(facts.addressField);
|
|
138
|
+
const fields = (Array.isArray(facts.fields) ? facts.fields : []).map((raw)=>raw && typeof raw === 'object' ? raw : {}).map((raw)=>({
|
|
139
|
+
id: str(raw['id']),
|
|
140
|
+
name: str(raw['name']),
|
|
141
|
+
type: str(raw['type']) || 'text'
|
|
142
|
+
})).filter((field)=>field.id && field.name && field.id !== address);
|
|
143
|
+
if (!fields.length) return null;
|
|
144
|
+
return {
|
|
145
|
+
id,
|
|
146
|
+
name: str(record.name) || 'dataset',
|
|
147
|
+
fields
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** What the form's generation is told about the dataset it writes to. */ export function aiFormDatasetPromptLine(dataset) {
|
|
151
|
+
const fields = dataset.fields.map((field)=>`${field.name} (${NUMBER_TYPES.has(field.type) ? 'a number' : 'text'})`);
|
|
152
|
+
return `Each submission is also saved as a record of the dataset “${dataset.name}”, whose fields are: ${fields.join(', ')}. Draw a field for each one a visitor can answer, labeled with the field's name.`;
|
|
153
|
+
}
|
|
154
|
+
/** What the form's row says about where its submissions are also saved. */ export function aiFormDatasetNote(dataset, binding) {
|
|
155
|
+
const kept = `Each submission is also saved as a record of the “${dataset.name}” dataset, in Data.`;
|
|
156
|
+
if (!binding.unbound.length) return kept;
|
|
157
|
+
const one = binding.unbound.length === 1;
|
|
158
|
+
return `${kept} The dataset has no field for ${binding.unbound.map((name)=>`“${name}”`).join(', ')}, so ${one ? 'that answer stays' : 'those answers stay'} in the Inbox only.`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//# sourceMappingURL=ai-job-form-dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-form-dataset.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n collectFormFieldNodeIds,\n FORM_COMPONENT_ID,\n isMarketingConsentFieldName,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_DATASET_DRAFT_RESOURCE } from './ai-build-unit-outcome'\n\n/**\n * A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's\n * forms can write to datasets — a volunteer sign-up into a Volunteers\n * dataset, an RSVP into an event's attendees, a catering inquiry into\n * Inquiries).\n *\n * The plan says which dataset a form writes to (`writesTo` on its creation);\n * it is never inferred from what a section places. The dataset is built\n * first — a site start's dataset unit, a build's dataset item, or one the\n * site already has — and the form step binds the form it generated the way a\n * person binds one on the Form element: the form node's `datasetId`, and\n * each Form Field's `datasetFieldId` where the dataset has a field of the\n * same name and a type the submitted value can be stored as. A field the\n * dataset lacks is left unbound, as the Form element's mapping leaves one:\n * its value stays in the Inbox copy, and a form never adds a field to a\n * dataset.\n *\n * The gates are the platform's. The binding is made only where the plan\n * includes the data store (`dataStore`), the data plugin's writer is loaded\n * here and reads the dataset, and the dataset is one this job made or one the\n * site's inventory shows. Records, their allowance and storage are held at\n * each submission by the data plugin's own record target. Anywhere else the\n * form is the form it always was, its submissions in the Inbox.\n */\n\n/** The unit input a dataset this job made for the form rides in, by id. */\nexport const AI_FORM_DATASET_MADE_INPUT = 'formDatasetMade'\n\n/** One field of the dataset a form writes to, as its writer reports it: storage type words. */\nexport interface AiFormDatasetField {\n id: string\n name: string\n type: string\n}\n\n/** The dataset a form writes to, with the fields a submission may fill. */\nexport interface AiFormDataset {\n id: string\n name: string\n fields: AiFormDatasetField[]\n}\n\n/** A field name as two spellings of it compare: `fullName`, `full_name` and \"Full name\" are one. */\nexport function aiFormFieldKey(name: string): string {\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '')\n}\n\nconst NUMBER_TYPES = new Set(['float', 'int32', 'int64', 'number', 'integer'])\n\n/**\n * Whether a submitted value of this form field type can be stored in a\n * dataset field of this storage type. Every value a form submits is text, so\n * a text field takes any of them and a number field takes a rating; any\n * other pairing is left unbound rather than refused at every submission.\n */\nexport function aiFormFieldFitsDataset(fieldType: string, datasetType: string): boolean {\n if (datasetType === 'text') return true\n if (NUMBER_TYPES.has(datasetType)) return fieldType === 'rating'\n return false\n}\n\n/** What a binding came to: the fields bound, and the fields the dataset has no place for. */\nexport interface AiFormDatasetBinding {\n bound: string[]\n unbound: string[]\n}\n\n/**\n * Binds a form design to a dataset, in place: the form node's `datasetId`,\n * and `datasetFieldId` on each Form Field the dataset has a field for, by its\n * `fieldName` or its label, each dataset field taken once. The platform's\n * consent field is never bound. A binding the design already carried is\n * replaced, so what the model drew never chooses where a record lands.\n */\nexport function aiBindFormToDataset(\n nodes: NodesMap,\n formNodeId: string,\n dataset: AiFormDataset,\n): AiFormDatasetBinding {\n const result: AiFormDatasetBinding = { bound: [], unbound: [] }\n const form = nodes[formNodeId]\n if (form?.componentId !== FORM_COMPONENT_ID) return result\n const props = { ...((form.props ?? {}) as Record<string, unknown>) }\n delete props['datasetName']\n props['datasetId'] = dataset.id\n form.props = props as never\n const taken = new Set<string>()\n for (const id of collectFormFieldNodeIds(nodes as never, formNodeId)) {\n const field = nodes[id]\n if (!field) continue\n const fieldProps = { ...((field.props ?? {}) as Record<string, unknown>) }\n delete fieldProps['datasetFieldId']\n field.props = fieldProps as never\n const fieldName = String(fieldProps['fieldName'] ?? '').trim()\n if (!fieldName || isMarketingConsentFieldName(fieldName)) continue\n const label = String(fieldProps['label'] ?? '').trim()\n const fieldType = String(fieldProps['fieldType'] ?? 'text')\n // Its name first, then its label: `fullName` is \"Full name\" before \"Your name\" is.\n const matching = (key: string) =>\n key\n ? dataset.fields.find((one) => !taken.has(one.id) && aiFormFieldKey(one.name) === key && aiFormFieldFitsDataset(fieldType, one.type))\n : undefined\n const match = matching(aiFormFieldKey(fieldName)) ?? matching(aiFormFieldKey(label))\n if (!match) {\n result.unbound.push(label || fieldName)\n continue\n }\n taken.add(match.id)\n fieldProps['datasetFieldId'] = match.id\n result.bound.push(fieldName)\n }\n return result\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nexport interface AiFormDatasetReadDeps {\n writerFor?: typeof pluginResourceDraftWriter\n}\n\n/**\n * The dataset a form may write to, or `null` where it may not: the plan\n * includes no data store, the data plugin's writer is not loaded here, the\n * dataset is neither one this job made nor one the site's inventory shows,\n * or it has no field a submission could fill.\n */\nexport async function aiReadFormDataset(\n input: {\n hostId: string\n id: string | null | undefined\n org: object | null\n inventory: Pick<AiSiteInventory, 'datasets'> | null\n /** The dataset this job made for the form, by id. */\n madeId?: unknown\n },\n deps: AiFormDatasetReadDeps = {},\n): Promise<AiFormDataset | null> {\n const id = str(input.id)\n if (!id) return null\n if (!checkEntitlement(input.org as never, 'dataStore')) return null\n const known = id === str(input.madeId) || (input.inventory?.datasets ?? []).some((row) => row.id === id)\n if (!known) return null\n const keeper = (deps.writerFor ?? pluginResourceDraftWriter)(AI_DATASET_DRAFT_RESOURCE)\n if (!keeper) return null\n const record = await keeper.writer.read({ hostId: input.hostId, id }).catch((error: unknown) => {\n console.error('ai form: the dataset it writes to could not be read', { hostId: input.hostId, id, error })\n return null\n })\n if (!record) return null\n const facts = (record.facts ?? {}) as { fields?: unknown; addressField?: unknown }\n const address = str(facts.addressField)\n const fields = (Array.isArray(facts.fields) ? (facts.fields as unknown[]) : [])\n .map((raw) => (raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {}))\n .map((raw) => ({ id: str(raw['id']), name: str(raw['name']), type: str(raw['type']) || 'text' }))\n .filter((field) => field.id && field.name && field.id !== address)\n if (!fields.length) return null\n return { id, name: str(record.name) || 'dataset', fields }\n}\n\n/** What the form's generation is told about the dataset it writes to. */\nexport function aiFormDatasetPromptLine(dataset: AiFormDataset): string {\n const fields = dataset.fields.map((field) => `${field.name} (${NUMBER_TYPES.has(field.type) ? 'a number' : 'text'})`)\n return `Each submission is also saved as a record of the dataset “${dataset.name}”, whose fields are: ${fields.join(', ')}. Draw a field for each one a visitor can answer, labeled with the field's name.`\n}\n\n/** What the form's row says about where its submissions are also saved. */\nexport function aiFormDatasetNote(dataset: AiFormDataset, binding: AiFormDatasetBinding): string {\n const kept = `Each submission is also saved as a record of the “${dataset.name}” dataset, in Data.`\n if (!binding.unbound.length) return kept\n const one = binding.unbound.length === 1\n return `${kept} The dataset has no field for ${binding.unbound.map((name) => `“${name}”`).join(', ')}, so ${\n one ? 'that answer stays' : 'those answers stay'\n } in the Inbox only.`\n}\n"],"names":["checkEntitlement","collectFormFieldNodeIds","FORM_COMPONENT_ID","isMarketingConsentFieldName","pluginResourceDraftWriter","AI_DATASET_DRAFT_RESOURCE","AI_FORM_DATASET_MADE_INPUT","aiFormFieldKey","name","replace","toLowerCase","NUMBER_TYPES","Set","aiFormFieldFitsDataset","fieldType","datasetType","has","aiBindFormToDataset","nodes","formNodeId","dataset","form","result","bound","unbound","componentId","props","id","taken","field","fieldProps","matching","fieldName","String","trim","label","key","fields","find","one","type","undefined","match","push","add","str","value","aiReadFormDataset","input","deps","record","org","known","madeId","inventory","datasets","some","row","keeper","writerFor","writer","read","hostId","catch","error","console","facts","address","addressField","Array","isArray","map","raw","filter","length","aiFormDatasetPromptLine","join","aiFormDatasetNote","binding","kept"],"mappings":"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"}
|
|
@@ -24,6 +24,7 @@ import type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
|
24
24
|
import type { AiSystemBlock, AiTool } from '../runtime/ai-runtime';
|
|
25
25
|
import { readSiteInventory } from '../runtime/site-inventory';
|
|
26
26
|
import { type AiJobAdmission } from './ai-job-admission';
|
|
27
|
+
import { type AiFormDataset, type AiFormDatasetBinding, type AiFormDatasetReadDeps } from './ai-job-form-dataset';
|
|
27
28
|
import type { AiJobStepRunner } from './ai-job-text-step';
|
|
28
29
|
/**
|
|
29
30
|
* The form step (AGL-2913): the generation step of a `form` job, run once a
|
|
@@ -140,18 +141,26 @@ export interface AiFormDraft {
|
|
|
140
141
|
consentFieldName: string | null;
|
|
141
142
|
routing: FormRouting | null;
|
|
142
143
|
answer: AiFormAnswer;
|
|
144
|
+
/** The dataset its submissions are also written to, and what was bound (AGL-3616). */
|
|
145
|
+
dataset?: {
|
|
146
|
+
dataset: AiFormDataset;
|
|
147
|
+
binding: AiFormDatasetBinding;
|
|
148
|
+
};
|
|
143
149
|
}
|
|
144
150
|
/**
|
|
145
151
|
* The draft a validated design and its answer make: the form node bound to
|
|
146
|
-
* `formId` and captioned `name`,
|
|
147
|
-
*
|
|
148
|
-
*
|
|
152
|
+
* `formId` and captioned `name`, the platform's consent field when the
|
|
153
|
+
* fields can yield an email address, the canvas root above, and the
|
|
154
|
+
* declaration read off the result. A dataset binding is made only where the
|
|
155
|
+
* plan said the form writes to one this step may bind (`identity.dataset`,
|
|
156
|
+
* AGL-3616); any the model drew is dropped. Pure; the input tree is not
|
|
149
157
|
* changed.
|
|
150
158
|
*/
|
|
151
159
|
export declare function aiFormDraft(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>, answer: Record<string, unknown>, identity: {
|
|
152
160
|
formId: string;
|
|
153
161
|
name: string;
|
|
154
162
|
decisions?: AiFormDecisions;
|
|
163
|
+
dataset?: AiFormDataset | null;
|
|
155
164
|
}): AiFormDraft;
|
|
156
165
|
/**
|
|
157
166
|
* What a draft would break once published, as building-rule violations
|
|
@@ -170,6 +179,7 @@ export declare function aiFormDraftCheck(identity: {
|
|
|
170
179
|
formId: string;
|
|
171
180
|
name: string;
|
|
172
181
|
decisions?: AiFormDecisions;
|
|
182
|
+
dataset?: AiFormDataset | null;
|
|
173
183
|
}): {
|
|
174
184
|
extend: (tree: AiValidatedTree, answer: Record<string, unknown>) => AiDoctrineViolation[];
|
|
175
185
|
draftFor: (tree: AiValidatedTree) => AiFormDraft | undefined;
|
|
@@ -280,6 +290,8 @@ export interface AiJobFormStepDeps {
|
|
|
280
290
|
readInventory?: typeof readSiteInventory;
|
|
281
291
|
/** The platform's duplicate module, for a plan that starts from a copy. */
|
|
282
292
|
duplicate?: typeof duplicateResource;
|
|
293
|
+
/** The data plugin's writer lookup the form's dataset is read through (AGL-3616); specs hand in a fake. */
|
|
294
|
+
datasetWriterFor?: AiFormDatasetReadDeps['writerFor'];
|
|
283
295
|
}
|
|
284
296
|
export declare function createAiJobFormStep(deps?: AiJobFormStepDeps): AiJobStepRunner;
|
|
285
297
|
export declare const runAiJobFormStep: AiJobStepRunner;
|
|
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
17
|
+
*/ import { throwIfAiJobCanceled } from "./ai-job-cancel.js";
|
|
18
|
+
import { checkFormContract, formFieldsCanYieldAnEmail } from "@aglyn/aglyn/app-utils/form-contract";
|
|
18
19
|
import { collectFormFieldNodeIds, FORM_COMPONENT_ID, FORM_FIELD_COMPONENT_ID, FORM_ID_PROP, formFieldDeclsFromNodes, formNodeIdIn, isMarketingConsentFieldName, MARKETING_CONSENT_FORM_FIELD } from "@aglyn/aglyn/app-utils/forms";
|
|
19
20
|
import { decodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
20
21
|
import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
|
|
@@ -27,6 +28,7 @@ import { aiDoctrineTreeTool, runValidatedGeneration } from "../runtime/ai-doctri
|
|
|
27
28
|
import { AI_PALETTE } from "../runtime/ai-palette.generated.js";
|
|
28
29
|
import { readSiteInventory } from "../runtime/site-inventory.js";
|
|
29
30
|
import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
31
|
+
import { AI_FORM_DATASET_MADE_INPUT, aiBindFormToDataset, aiFormDatasetNote, aiFormDatasetPromptLine, aiReadFormDataset } from "./ai-job-form-dataset.js";
|
|
30
32
|
import { aiJobPlanCreationsRefusal } from "./ai-job-plan-creations.js";
|
|
31
33
|
import { aiJobDraftId } from "./ai-job-draft-ids.js";
|
|
32
34
|
import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, writeAiDraft } from "./ai-job-drafts.js";
|
|
@@ -208,9 +210,11 @@ function oneLine(value, max) {
|
|
|
208
210
|
}
|
|
209
211
|
/**
|
|
210
212
|
* The draft a validated design and its answer make: the form node bound to
|
|
211
|
-
* `formId` and captioned `name`,
|
|
212
|
-
*
|
|
213
|
-
*
|
|
213
|
+
* `formId` and captioned `name`, the platform's consent field when the
|
|
214
|
+
* fields can yield an email address, the canvas root above, and the
|
|
215
|
+
* declaration read off the result. A dataset binding is made only where the
|
|
216
|
+
* plan said the form writes to one this step may bind (`identity.dataset`,
|
|
217
|
+
* AGL-3616); any the model drew is dropped. Pure; the input tree is not
|
|
214
218
|
* changed.
|
|
215
219
|
*/ export function aiFormDraft(tree, answer, identity) {
|
|
216
220
|
var _identity_decisions;
|
|
@@ -232,8 +236,8 @@ function oneLine(value, max) {
|
|
|
232
236
|
let consentFieldName = null;
|
|
233
237
|
if ((form == null ? void 0 : form.componentId) === FORM_COMPONENT_ID) {
|
|
234
238
|
const props = form.props;
|
|
235
|
-
// A dataset binding decides where every submission is also written
|
|
236
|
-
//
|
|
239
|
+
// A dataset binding decides where every submission is also written: the
|
|
240
|
+
// plan's to say (AGL-3616), bound below, never the model's to draw.
|
|
237
241
|
delete props['datasetId'];
|
|
238
242
|
delete props['datasetName'];
|
|
239
243
|
props[FORM_ID_PROP] = identity.formId;
|
|
@@ -268,6 +272,12 @@ function oneLine(value, max) {
|
|
|
268
272
|
}
|
|
269
273
|
form.parentId = CANVAS_ROOT_ELEMENT_ID;
|
|
270
274
|
}
|
|
275
|
+
// The dataset the plan says the form writes to (AGL-3616), bound as the
|
|
276
|
+
// Form element binds one.
|
|
277
|
+
const dataset = identity.dataset && (form == null ? void 0 : form.componentId) === FORM_COMPONENT_ID ? {
|
|
278
|
+
dataset: identity.dataset,
|
|
279
|
+
binding: aiBindFormToDataset(nodes, formNodeId, identity.dataset)
|
|
280
|
+
} : undefined;
|
|
271
281
|
nodes[CANVAS_ROOT_ELEMENT_ID] = {
|
|
272
282
|
$id: CANVAS_ROOT_ELEMENT_ID,
|
|
273
283
|
componentId: 'div',
|
|
@@ -275,7 +285,7 @@ function oneLine(value, max) {
|
|
|
275
285
|
formNodeId
|
|
276
286
|
]
|
|
277
287
|
};
|
|
278
|
-
return {
|
|
288
|
+
return _extends({
|
|
279
289
|
nodes,
|
|
280
290
|
rootId: CANVAS_ROOT_ELEMENT_ID,
|
|
281
291
|
formNodeId,
|
|
@@ -285,7 +295,9 @@ function oneLine(value, max) {
|
|
|
285
295
|
lead: true
|
|
286
296
|
} : null,
|
|
287
297
|
answer: parsed
|
|
288
|
-
}
|
|
298
|
+
}, dataset ? {
|
|
299
|
+
dataset
|
|
300
|
+
} : {});
|
|
289
301
|
}
|
|
290
302
|
/**
|
|
291
303
|
* What a draft would break once published, as building-rule violations
|
|
@@ -535,6 +547,7 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
535
547
|
const duplicate = (_deps_duplicate = deps.duplicate) != null ? _deps_duplicate : duplicateResource;
|
|
536
548
|
return async ({ job, now, signal, firestore, modelFor })=>{
|
|
537
549
|
var _ref, _orgSnapshot_data;
|
|
550
|
+
var _job_inputs;
|
|
538
551
|
const hostId = job.hostId;
|
|
539
552
|
if (!hostId) throw new Error('a form job names no site, and its admission refuses one');
|
|
540
553
|
const output = (draft, extra = {})=>_extends({
|
|
@@ -661,10 +674,22 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
661
674
|
submissions: aiSiteSubmissions(job.inputs)
|
|
662
675
|
};
|
|
663
676
|
const { audience } = aiSiteWords(job.inputs);
|
|
677
|
+
// The dataset the plan says the form writes to (AGL-3616), where this
|
|
678
|
+
// workspace may bind one; otherwise the form is the form it always was.
|
|
679
|
+
const dataset = await aiReadFormDataset({
|
|
680
|
+
hostId,
|
|
681
|
+
id: creation == null ? void 0 : creation.writesTo,
|
|
682
|
+
org,
|
|
683
|
+
inventory,
|
|
684
|
+
madeId: (_job_inputs = job.inputs) == null ? void 0 : _job_inputs[AI_FORM_DATASET_MADE_INPUT]
|
|
685
|
+
}, deps.datasetWriterFor ? {
|
|
686
|
+
writerFor: deps.datasetWriterFor
|
|
687
|
+
} : {});
|
|
664
688
|
const check = aiFormDraftCheck({
|
|
665
689
|
formId: draftId,
|
|
666
690
|
name,
|
|
667
|
-
decisions
|
|
691
|
+
decisions,
|
|
692
|
+
dataset
|
|
668
693
|
});
|
|
669
694
|
const result = await runValidatedGeneration('form', _extends({
|
|
670
695
|
step: 'job.form',
|
|
@@ -679,7 +704,10 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
679
704
|
audience,
|
|
680
705
|
submissions: decisions.submissions
|
|
681
706
|
}),
|
|
682
|
-
...sourceLines
|
|
707
|
+
...sourceLines,
|
|
708
|
+
...dataset ? [
|
|
709
|
+
aiFormDatasetPromptLine(dataset)
|
|
710
|
+
] : []
|
|
683
711
|
].join('\n')
|
|
684
712
|
}
|
|
685
713
|
],
|
|
@@ -699,6 +727,8 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
699
727
|
});
|
|
700
728
|
const draft = check.draftFor(result.value);
|
|
701
729
|
if (!draft) throw new Error('a form the doctrine admitted has no draft from its check');
|
|
730
|
+
// Never written once the job is canceled (AGL-3616).
|
|
731
|
+
throwIfAiJobCanceled(signal);
|
|
702
732
|
const write = await writeAiDraft(firestore, {
|
|
703
733
|
kind: 'form',
|
|
704
734
|
hostId,
|
|
@@ -725,6 +755,7 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
725
755
|
// What the person decides next, then the facts the brief did not give (AGL-3056).
|
|
726
756
|
const note = [
|
|
727
757
|
aiFormOutputNote(draft.answer),
|
|
758
|
+
draft.dataset ? aiFormDatasetNote(draft.dataset.dataset, draft.dataset.binding) : null,
|
|
728
759
|
aiBracketedFactsNote({
|
|
729
760
|
tree: {
|
|
730
761
|
rootId: result.value.rootId,
|