@aglyn/plugins-ai 1.0.0-beta.250 → 1.0.0-beta.253
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-brief-dialog.component.js +15 -1
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +1 -1
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +20 -2
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-store.d.ts +11 -0
- package/src/lib/components/ai-jobs-store.js +22 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -1
- package/src/lib/jobs/ai-job-crm-step.js +7 -4
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
- package/src/lib/jobs/ai-job-drafts.js +46 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +6 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-dataset.d.ts +6 -1
- package/src/lib/jobs/ai-job-form-dataset.js +7 -2
- package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
- package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
- package/src/lib/jobs/ai-job-form-reuse.js +136 -0
- package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
- package/src/lib/jobs/ai-job-form-step.js +63 -11
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +6 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +10 -5
- package/src/lib/jobs/ai-job-page-language.js +10 -5
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +32 -1
- package/src/lib/jobs/ai-job-plan-step.js +53 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +5 -3
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
- package/src/lib/jobs/ai-job-site-content.js +7 -5
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
- package/src/lib/jobs/ai-job-site-datasets.js +2 -2
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +2 -1
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +6 -3
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
- package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
- package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
- package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.js +5 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.js +5 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +7 -3
- package/src/lib/model/ai-build-plan.js +11 -1
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +39 -19
- package/src/lib/model/ai-credit-estimate.js +43 -19
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +30 -0
- package/src/lib/model/ai-job-activity.js +35 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +5 -3
- package/src/lib/model/ai-media-credits.js +7 -5
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-site-job.js +6 -3
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +11 -2
- package/src/lib/model/ai-site-start-seo.js +29 -15
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.d.ts +5 -0
- package/src/lib/model/ai-site-store-pages.js +2 -0
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/plugin-config.js +2 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
- package/src/lib/providers/anthropic.js +29 -7
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +24 -17
- package/src/lib/providers/catalog.js +63 -30
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +19 -1
- package/src/lib/providers/contract.js +10 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/model-choice.d.ts +1 -1
- package/src/lib/providers/model-choice.js +39 -30
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.d.ts +20 -1
- package/src/lib/providers/routing.js +71 -12
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dev-env.d.ts +58 -1
- package/src/lib/runtime/ai-dev-env.js +86 -6
- package/src/lib/runtime/ai-dev-env.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +4 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +13 -3
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +43 -1
- package/src/lib/runtime/ai-eval-live.js +79 -9
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-live-batch.d.ts +20 -4
- package/src/lib/runtime/ai-live-batch.js +57 -10
- package/src/lib/runtime/ai-live-batch.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +4 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +21 -4
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/seo-fields.js +4 -3
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/server/ai-assist.js +3 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +5 -1
- package/src/lib/server/ai-media-image.js +6 -2
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-svg.js +4 -0
- package/src/lib/server/ai-media-svg.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.js +2 -5
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
|
@@ -42,8 +42,13 @@ import { AI_DATASET_DRAFT_RESOURCE } from "./ai-build-unit-outcome.js";
|
|
|
42
42
|
* each submission by the data plugin's own record target. Anywhere else the
|
|
43
43
|
* form is the form it always was, its submissions in the Inbox.
|
|
44
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
|
-
/**
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* A field name as two spellings of it compare: `fullName`, `full_name` and
|
|
47
|
+
* "Full name" are one, and so are `telefono` and "Teléfono" (AGL-3791). Its
|
|
48
|
+
* letters are any script's, so "Имя" and "Телефон" are two fields rather
|
|
49
|
+
* than two empty keys that match nothing.
|
|
50
|
+
*/ export function aiFormFieldKey(name) {
|
|
51
|
+
return name.normalize('NFD').replace(RegExp("[\\u0300-\\u036F\\u0483-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1-\\u05C2\\u05C4-\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7-\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u07FD\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0859-\\u085B\\u0897-\\u089F\\u08CA-\\u08E1\\u08E3-\\u0903\\u093A-\\u093C\\u093E-\\u094F\\u0951-\\u0957\\u0962-\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7-\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2-\\u09E3\\u09FE\\u0A01-\\u0A03\\u0A3C\\u0A3E-\\u0A42\\u0A47-\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70-\\u0A71\\u0A75\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AE2-\\u0AE3\\u0AFA-\\u0AFF\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B44\\u0B47-\\u0B48\\u0B4B-\\u0B4D\\u0B55-\\u0B57\\u0B62-\\u0B63\\u0B82\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C00-\\u0C04\\u0C3C\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55-\\u0C56\\u0C62-\\u0C63\\u0C81-\\u0C83\\u0CBC\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5-\\u0CD6\\u0CE2-\\u0CE3\\u0CF3\\u0D00-\\u0D03\\u0D3B-\\u0D3C\\u0D3E-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0D62-\\u0D63\\u0D81-\\u0D83\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DF2-\\u0DF3\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EBC\\u0EC8-\\u0ECE\\u0F18-\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E-\\u0F3F\\u0F71-\\u0F84\\u0F86-\\u0F87\\u0F8D-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102B-\\u103E\\u1056-\\u1059\\u105E-\\u1060\\u1062-\\u1064\\u1067-\\u106D\\u1071-\\u1074\\u1082-\\u108D\\u108F\\u109A-\\u109D\\u135D-\\u135F\\u1712-\\u1715\\u1732-\\u1734\\u1752-\\u1753\\u1772-\\u1773\\u17B4-\\u17D3\\u17DD\\u180B-\\u180D\\u180F\\u1885-\\u1886\\u18A9\\u1920-\\u192B\\u1930-\\u193B\\u1A17-\\u1A1B\\u1A55-\\u1A5E\\u1A60-\\u1A7C\\u1A7F\\u1AB0-\\u1ADD\\u1AE0-\\u1AEB\\u1B00-\\u1B04\\u1B34-\\u1B44\\u1B6B-\\u1B73\\u1B80-\\u1B82\\u1BA1-\\u1BAD\\u1BE6-\\u1BF3\\u1C24-\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE8\\u1CED\\u1CF4\\u1CF7-\\u1CF9\\u1DC0-\\u1DFF\\u20D0-\\u20F0\\u2CEF-\\u2CF1\\u2D7F\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099-\\u309A\\uA66F-\\uA672\\uA674-\\uA67D\\uA69E-\\uA69F\\uA6F0-\\uA6F1\\uA802\\uA806\\uA80B\\uA823-\\uA827\\uA82C\\uA880-\\uA881\\uA8B4-\\uA8C5\\uA8E0-\\uA8F1\\uA8FF\\uA926-\\uA92D\\uA947-\\uA953\\uA980-\\uA983\\uA9B3-\\uA9C0\\uA9E5\\uAA29-\\uAA36\\uAA43\\uAA4C-\\uAA4D\\uAA7B-\\uAA7D\\uAAB0\\uAAB2-\\uAAB4\\uAAB7-\\uAAB8\\uAABE-\\uAABF\\uAAC1\\uAAEB-\\uAAEF\\uAAF5-\\uAAF6\\uABE3-\\uABEA\\uABEC-\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE2F\\u{101FD}\\u{102E0}\\u{10376}-\\u{1037A}\\u{10A01}-\\u{10A03}\\u{10A05}-\\u{10A06}\\u{10A0C}-\\u{10A0F}\\u{10A38}-\\u{10A3A}\\u{10A3F}\\u{10AE5}-\\u{10AE6}\\u{10D24}-\\u{10D27}\\u{10D69}-\\u{10D6D}\\u{10EAB}-\\u{10EAC}\\u{10EFA}-\\u{10EFF}\\u{10F46}-\\u{10F50}\\u{10F82}-\\u{10F85}\\u{11000}-\\u{11002}\\u{11038}-\\u{11046}\\u{11070}\\u{11073}-\\u{11074}\\u{1107F}-\\u{11082}\\u{110B0}-\\u{110BA}\\u{110C2}\\u{11100}-\\u{11102}\\u{11127}-\\u{11134}\\u{11145}-\\u{11146}\\u{11173}\\u{11180}-\\u{11182}\\u{111B3}-\\u{111C0}\\u{111C9}-\\u{111CC}\\u{111CE}-\\u{111CF}\\u{1122C}-\\u{11237}\\u{1123E}\\u{11241}\\u{112DF}-\\u{112EA}\\u{11300}-\\u{11303}\\u{1133B}-\\u{1133C}\\u{1133E}-\\u{11344}\\u{11347}-\\u{11348}\\u{1134B}-\\u{1134D}\\u{11357}\\u{11362}-\\u{11363}\\u{11366}-\\u{1136C}\\u{11370}-\\u{11374}\\u{113B8}-\\u{113C0}\\u{113C2}\\u{113C5}\\u{113C7}-\\u{113CA}\\u{113CC}-\\u{113D0}\\u{113D2}\\u{113E1}-\\u{113E2}\\u{11435}-\\u{11446}\\u{1145E}\\u{114B0}-\\u{114C3}\\u{115AF}-\\u{115B5}\\u{115B8}-\\u{115C0}\\u{115DC}-\\u{115DD}\\u{11630}-\\u{11640}\\u{116AB}-\\u{116B7}\\u{1171D}-\\u{1172B}\\u{1182C}-\\u{1183A}\\u{11930}-\\u{11935}\\u{11937}-\\u{11938}\\u{1193B}-\\u{1193E}\\u{11940}\\u{11942}-\\u{11943}\\u{119D1}-\\u{119D7}\\u{119DA}-\\u{119E0}\\u{119E4}\\u{11A01}-\\u{11A0A}\\u{11A33}-\\u{11A39}\\u{11A3B}-\\u{11A3E}\\u{11A47}\\u{11A51}-\\u{11A5B}\\u{11A8A}-\\u{11A99}\\u{11B60}-\\u{11B67}\\u{11C2F}-\\u{11C36}\\u{11C38}-\\u{11C3F}\\u{11C92}-\\u{11CA7}\\u{11CA9}-\\u{11CB6}\\u{11D31}-\\u{11D36}\\u{11D3A}\\u{11D3C}-\\u{11D3D}\\u{11D3F}-\\u{11D45}\\u{11D47}\\u{11D8A}-\\u{11D8E}\\u{11D90}-\\u{11D91}\\u{11D93}-\\u{11D97}\\u{11EF3}-\\u{11EF6}\\u{11F00}-\\u{11F01}\\u{11F03}\\u{11F34}-\\u{11F3A}\\u{11F3E}-\\u{11F42}\\u{11F5A}\\u{13440}\\u{13447}-\\u{13455}\\u{1611E}-\\u{1612F}\\u{16AF0}-\\u{16AF4}\\u{16B30}-\\u{16B36}\\u{16F4F}\\u{16F51}-\\u{16F87}\\u{16F8F}-\\u{16F92}\\u{16FE4}\\u{16FF0}-\\u{16FF1}\\u{1BC9D}-\\u{1BC9E}\\u{1CF00}-\\u{1CF2D}\\u{1CF30}-\\u{1CF46}\\u{1D165}-\\u{1D169}\\u{1D16D}-\\u{1D172}\\u{1D17B}-\\u{1D182}\\u{1D185}-\\u{1D18B}\\u{1D1AA}-\\u{1D1AD}\\u{1D242}-\\u{1D244}\\u{1DA00}-\\u{1DA36}\\u{1DA3B}-\\u{1DA6C}\\u{1DA75}\\u{1DA84}\\u{1DA9B}-\\u{1DA9F}\\u{1DAA1}-\\u{1DAAF}\\u{1E000}-\\u{1E006}\\u{1E008}-\\u{1E018}\\u{1E01B}-\\u{1E021}\\u{1E023}-\\u{1E024}\\u{1E026}-\\u{1E02A}\\u{1E08F}\\u{1E130}-\\u{1E136}\\u{1E2AE}\\u{1E2EC}-\\u{1E2EF}\\u{1E4EC}-\\u{1E4EF}\\u{1E5EE}-\\u{1E5EF}\\u{1E6E3}\\u{1E6E6}\\u{1E6EE}-\\u{1E6EF}\\u{1E6F5}\\u{1E8D0}-\\u{1E8D6}\\u{1E944}-\\u{1E94A}\\u{E0100}-\\u{E01EF}]+", "gu"), '').toLowerCase().replace(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+", "gu"), '');
|
|
47
52
|
}
|
|
48
53
|
const NUMBER_TYPES = new Set([
|
|
49
54
|
'float',
|
|
@@ -1 +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 formFieldDeclsFromNodes,\n formInputName,\n formInputType,\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 answer element'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 answer element (an Input, Select, Radio Group,\n * Checkbox or Rating) the dataset has a field for, by the name it submits\n * under or its label — the Form Label or Choice Label beside it — each\n * dataset field taken once. Checkboxes sharing a name are one answer and are\n * bound together. The platform's consent field is never bound. A binding the\n * design already carried is replaced, so what the model drew never chooses\n * 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 labels = new Map(\n formFieldDeclsFromNodes(nodes as never, formNodeId).map((decl) => [decl.fieldName, decl.label ?? ''] as const),\n )\n const taken = new Set<string>()\n /** The dataset field each answer's name was bound to, or `null` where none was. */\n const byName = new Map<string, string | null>()\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 = formInputName(field as never)\n if (!fieldName || isMarketingConsentFieldName(fieldName)) continue\n if (byName.has(fieldName)) {\n const already = byName.get(fieldName)\n if (already) fieldProps['datasetFieldId'] = already\n continue\n }\n const label = labels.get(fieldName) ?? ''\n const fieldType = formInputType(field as never)\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 byName.set(fieldName, null)\n result.unbound.push(label || fieldName)\n continue\n }\n taken.add(match.id)\n byName.set(fieldName, 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","formFieldDeclsFromNodes","formInputName","formInputType","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","labels","Map","map","decl","fieldName","label","taken","byName","field","matching","fieldProps","already","get","key","fields","find","one","type","undefined","match","set","push","add","str","value","trim","aiReadFormDataset","input","deps","record","org","known","madeId","inventory","datasets","some","row","keeper","writerFor","writer","read","hostId","catch","error","console","facts","address","addressField","Array","isArray","raw","filter","length","aiFormDatasetPromptLine","join","aiFormDatasetNote","binding","kept"],"mappings":"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"}
|
|
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 formFieldDeclsFromNodes,\n formInputName,\n formInputType,\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 answer element'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/**\n * A field name as two spellings of it compare: `fullName`, `full_name` and\n * \"Full name\" are one, and so are `telefono` and \"Teléfono\" (AGL-3791). Its\n * letters are any script's, so \"Имя\" and \"Телефон\" are two fields rather\n * than two empty keys that match nothing.\n */\nexport function aiFormFieldKey(name: string): string {\n return name\n .normalize('NFD')\n .replace(/\\p{M}+/gu, '')\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, '')\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 answer element (an Input, Select, Radio Group,\n * Checkbox or Rating) the dataset has a field for, by the name it submits\n * under or its label — the Form Label or Choice Label beside it — each\n * dataset field taken once. Checkboxes sharing a name are one answer and are\n * bound together. The platform's consent field is never bound. A binding the\n * design already carried is replaced, so what the model drew never chooses\n * 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 labels = new Map(\n formFieldDeclsFromNodes(nodes as never, formNodeId).map((decl) => [decl.fieldName, decl.label ?? ''] as const),\n )\n const taken = new Set<string>()\n /** The dataset field each answer's name was bound to, or `null` where none was. */\n const byName = new Map<string, string | null>()\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 = formInputName(field as never)\n if (!fieldName || isMarketingConsentFieldName(fieldName)) continue\n if (byName.has(fieldName)) {\n const already = byName.get(fieldName)\n if (already) fieldProps['datasetFieldId'] = already\n continue\n }\n const label = labels.get(fieldName) ?? ''\n const fieldType = formInputType(field as never)\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 byName.set(fieldName, null)\n result.unbound.push(label || fieldName)\n continue\n }\n taken.add(match.id)\n byName.set(fieldName, 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","formFieldDeclsFromNodes","formInputName","formInputType","isMarketingConsentFieldName","pluginResourceDraftWriter","AI_DATASET_DRAFT_RESOURCE","AI_FORM_DATASET_MADE_INPUT","aiFormFieldKey","name","normalize","replace","toLowerCase","NUMBER_TYPES","Set","aiFormFieldFitsDataset","fieldType","datasetType","has","aiBindFormToDataset","nodes","formNodeId","dataset","form","result","bound","unbound","componentId","props","id","labels","Map","map","decl","fieldName","label","taken","byName","field","matching","fieldProps","already","get","key","fields","find","one","type","undefined","match","set","push","add","str","value","trim","aiReadFormDataset","input","deps","record","org","known","madeId","inventory","datasets","some","row","keeper","writerFor","writer","read","hostId","catch","error","console","facts","address","addressField","Array","isArray","raw","filter","length","aiFormDatasetPromptLine","join","aiFormDatasetNote","binding","kept"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 type { AiJob } from '../model/ai-jobs.types';
|
|
18
|
+
import { type AiDraftRecord } from './ai-job-drafts';
|
|
19
|
+
/**
|
|
20
|
+
* A FORM ANOTHER BUILD JUST MADE, PLACED INSTEAD OF REFUSED (AGL-3765).
|
|
21
|
+
*
|
|
22
|
+
* Two page jobs started from one brief on a Free site, minutes apart, were
|
|
23
|
+
* each planned while the site had no form, and each planned a new one. The
|
|
24
|
+
* first to reach its form step made the site's one Free form; the second
|
|
25
|
+
* reached its own ten seconds later, was refused at the band, and stopped
|
|
26
|
+
* without the page it was asked for. A third job, planned after the form
|
|
27
|
+
* existed, placed it by id and was built.
|
|
28
|
+
*
|
|
29
|
+
* So a form UNIT — the form a page, a guided start or a build makes before
|
|
30
|
+
* the page that places it — that the band refuses looks for a form another AI
|
|
31
|
+
* job made on the same site, and reports that form as what the unit built.
|
|
32
|
+
* The page then binds it by id, exactly as it binds a form the site always
|
|
33
|
+
* had. Another job's form qualifies when the site holds it now, in use, and it
|
|
34
|
+
* was made in the last {@link AI_FORM_REUSE_WINDOW_MS}, or by a job of the
|
|
35
|
+
* same brief. Which job made it is read from what the jobs record: the
|
|
36
|
+
* form's id is the id a job's plan minted for its form creation before the
|
|
37
|
+
* write (`AiBuildPlanCreate.id`), and the output the job recorded after it.
|
|
38
|
+
*
|
|
39
|
+
* A job the member asked for a form by itself never borrows one: it was asked
|
|
40
|
+
* for a new form, not for a page that needs one.
|
|
41
|
+
*/
|
|
42
|
+
/** How recently another job must have made a form for a refused unit to place it. */
|
|
43
|
+
export declare const AI_FORM_REUSE_WINDOW_MS: number;
|
|
44
|
+
/** What the unit's output says when it placed another build's form. */
|
|
45
|
+
export declare const AI_FORM_REUSED_NOTE = "Reused the form another build just made.";
|
|
46
|
+
/** How many of the site's newest jobs are read for the form's maker. */
|
|
47
|
+
export declare const AI_FORM_REUSE_JOBS_READ = 20;
|
|
48
|
+
/** The fields of a job this module reads. */
|
|
49
|
+
export type AiFormReuseJob = Pick<AiJob, '$id' | 'hostId' | 'brief' | 'outputs' | 'plan'>;
|
|
50
|
+
/**
|
|
51
|
+
* The forms other jobs on this site made or were about to make, each with
|
|
52
|
+
* whether its job had the same brief as the job the member started. Read off
|
|
53
|
+
* each job's recorded outputs and its plan's minted form ids; the job the
|
|
54
|
+
* member started is never another job.
|
|
55
|
+
*/
|
|
56
|
+
export declare function aiSiblingJobFormIds(jobs: readonly AiFormReuseJob[], input: {
|
|
57
|
+
hostId: string;
|
|
58
|
+
originJobId: string;
|
|
59
|
+
}): Map<string, {
|
|
60
|
+
sameBrief: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
export interface AiSiblingJobFormDeps {
|
|
63
|
+
/** The site's newest jobs; the machine's list otherwise. */
|
|
64
|
+
listJobs?: (firestore: FirebaseFirestore.Firestore, orgId: string, options: {
|
|
65
|
+
hostId: string;
|
|
66
|
+
limit: number;
|
|
67
|
+
}) => Promise<readonly AiFormReuseJob[]>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The form another AI job made on this site that a refused form unit places
|
|
71
|
+
* instead (AGL-3765), as the draft record the unit reports; `null` when the
|
|
72
|
+
* job is not a unit, or no other job's form qualifies. The newest qualifying
|
|
73
|
+
* form wins.
|
|
74
|
+
*/
|
|
75
|
+
export declare function aiSiblingJobForm(firestore: FirebaseFirestore.Firestore, input: {
|
|
76
|
+
job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'inputs'>;
|
|
77
|
+
now: Date;
|
|
78
|
+
}, deps?: AiSiblingJobFormDeps): Promise<AiDraftRecord | null>;
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
*/ import { isFormArchived } from "@aglyn/aglyn/app-utils/forms";
|
|
17
|
+
import { aiOriginJobId } from "./ai-job-draft-ids.js";
|
|
18
|
+
import { aiDraftInstantMs, aiSiteSubdomain } from "./ai-job-drafts.js";
|
|
19
|
+
import { listAiJobs } from "./ai-jobs.js";
|
|
20
|
+
/**
|
|
21
|
+
* A FORM ANOTHER BUILD JUST MADE, PLACED INSTEAD OF REFUSED (AGL-3765).
|
|
22
|
+
*
|
|
23
|
+
* Two page jobs started from one brief on a Free site, minutes apart, were
|
|
24
|
+
* each planned while the site had no form, and each planned a new one. The
|
|
25
|
+
* first to reach its form step made the site's one Free form; the second
|
|
26
|
+
* reached its own ten seconds later, was refused at the band, and stopped
|
|
27
|
+
* without the page it was asked for. A third job, planned after the form
|
|
28
|
+
* existed, placed it by id and was built.
|
|
29
|
+
*
|
|
30
|
+
* So a form UNIT — the form a page, a guided start or a build makes before
|
|
31
|
+
* the page that places it — that the band refuses looks for a form another AI
|
|
32
|
+
* job made on the same site, and reports that form as what the unit built.
|
|
33
|
+
* The page then binds it by id, exactly as it binds a form the site always
|
|
34
|
+
* had. Another job's form qualifies when the site holds it now, in use, and it
|
|
35
|
+
* was made in the last {@link AI_FORM_REUSE_WINDOW_MS}, or by a job of the
|
|
36
|
+
* same brief. Which job made it is read from what the jobs record: the
|
|
37
|
+
* form's id is the id a job's plan minted for its form creation before the
|
|
38
|
+
* write (`AiBuildPlanCreate.id`), and the output the job recorded after it.
|
|
39
|
+
*
|
|
40
|
+
* A job the member asked for a form by itself never borrows one: it was asked
|
|
41
|
+
* for a new form, not for a page that needs one.
|
|
42
|
+
*/ /** How recently another job must have made a form for a refused unit to place it. */ export const AI_FORM_REUSE_WINDOW_MS = 15 * 60000;
|
|
43
|
+
/** What the unit's output says when it placed another build's form. */ export const AI_FORM_REUSED_NOTE = 'Reused the form another build just made.';
|
|
44
|
+
/** How many of the site's newest jobs are read for the form's maker. */ export const AI_FORM_REUSE_JOBS_READ = 20;
|
|
45
|
+
/** A brief as two jobs of the same brief share it: case and spacing aside. */ function briefKey(brief) {
|
|
46
|
+
return (brief != null ? brief : '').trim().replace(/\s+/g, ' ').toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The forms other jobs on this site made or were about to make, each with
|
|
50
|
+
* whether its job had the same brief as the job the member started. Read off
|
|
51
|
+
* each job's recorded outputs and its plan's minted form ids; the job the
|
|
52
|
+
* member started is never another job.
|
|
53
|
+
*/ export function aiSiblingJobFormIds(jobs, input) {
|
|
54
|
+
const origin = jobs.find((job)=>job.$id === input.originJobId);
|
|
55
|
+
const brief = origin ? briefKey(origin.brief) : '';
|
|
56
|
+
const found = new Map();
|
|
57
|
+
for (const job of jobs){
|
|
58
|
+
var _job_outputs, _ref;
|
|
59
|
+
var _job_plan;
|
|
60
|
+
if (job.$id === input.originJobId || job.hostId !== input.hostId) continue;
|
|
61
|
+
const sameBrief = Boolean(brief) && briefKey(job.brief) === brief;
|
|
62
|
+
const ids = [
|
|
63
|
+
...((_job_outputs = job.outputs) != null ? _job_outputs : []).filter((output)=>{
|
|
64
|
+
var _output_hostId;
|
|
65
|
+
return output.resource === 'form' && ((_output_hostId = output.hostId) != null ? _output_hostId : input.hostId) === input.hostId;
|
|
66
|
+
}).map((output)=>output.id),
|
|
67
|
+
...((_ref = (_job_plan = job.plan) == null ? void 0 : _job_plan.create) != null ? _ref : []).filter((entry)=>entry.kind === 'form').map((entry)=>entry.id)
|
|
68
|
+
];
|
|
69
|
+
for (const id of ids){
|
|
70
|
+
var _ref1;
|
|
71
|
+
var _found_get;
|
|
72
|
+
if (!id) continue;
|
|
73
|
+
found.set(id, {
|
|
74
|
+
sameBrief: sameBrief || ((_ref1 = (_found_get = found.get(id)) == null ? void 0 : _found_get.sameBrief) != null ? _ref1 : false)
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The form another AI job made on this site that a refused form unit places
|
|
82
|
+
* instead (AGL-3765), as the draft record the unit reports; `null` when the
|
|
83
|
+
* job is not a unit, or no other job's form qualifies. The newest qualifying
|
|
84
|
+
* form wins.
|
|
85
|
+
*/ export async function aiSiblingJobForm(firestore, input, deps = {}) {
|
|
86
|
+
var _deps_listJobs;
|
|
87
|
+
const { job } = input;
|
|
88
|
+
const hostId = job.hostId;
|
|
89
|
+
const originJobId = aiOriginJobId(job);
|
|
90
|
+
// Only a unit of a job that places the form; a form job builds its own.
|
|
91
|
+
if (!hostId || originJobId === job.$id) return null;
|
|
92
|
+
const listJobs = (_deps_listJobs = deps.listJobs) != null ? _deps_listJobs : listAiJobs;
|
|
93
|
+
const jobs = await listJobs(firestore, job.orgId, {
|
|
94
|
+
hostId,
|
|
95
|
+
limit: AI_FORM_REUSE_JOBS_READ
|
|
96
|
+
});
|
|
97
|
+
const candidates = aiSiblingJobFormIds(jobs, {
|
|
98
|
+
hostId,
|
|
99
|
+
originJobId
|
|
100
|
+
});
|
|
101
|
+
candidates.delete(job.$id);
|
|
102
|
+
if (!candidates.size) return null;
|
|
103
|
+
const forms = firestore.collection('hosts').doc(hostId).collection('forms');
|
|
104
|
+
const read = await Promise.all([
|
|
105
|
+
...candidates.entries()
|
|
106
|
+
].map(async ([id, { sameBrief }])=>({
|
|
107
|
+
id,
|
|
108
|
+
sameBrief,
|
|
109
|
+
doc: await forms.doc(id).get()
|
|
110
|
+
})));
|
|
111
|
+
const now = input.now.getTime();
|
|
112
|
+
const qualifying = read.filter(({ doc })=>doc.exists && !isFormArchived({
|
|
113
|
+
archivedAt: doc.get('archivedAt')
|
|
114
|
+
})).map(({ id, sameBrief, doc })=>{
|
|
115
|
+
var _doc_get;
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
sameBrief,
|
|
119
|
+
name: String((_doc_get = doc.get('displayName')) != null ? _doc_get : ''),
|
|
120
|
+
createdAt: aiDraftInstantMs(doc.get('createdAt'))
|
|
121
|
+
};
|
|
122
|
+
}).filter((form)=>form.sameBrief || form.createdAt !== null && now - form.createdAt <= AI_FORM_REUSE_WINDOW_MS).sort((a, b)=>{
|
|
123
|
+
var _b_createdAt, _a_createdAt;
|
|
124
|
+
return ((_b_createdAt = b.createdAt) != null ? _b_createdAt : 0) - ((_a_createdAt = a.createdAt) != null ? _a_createdAt : 0);
|
|
125
|
+
});
|
|
126
|
+
const [form] = qualifying;
|
|
127
|
+
if (!form) return null;
|
|
128
|
+
return {
|
|
129
|
+
id: form.id,
|
|
130
|
+
versionId: null,
|
|
131
|
+
name: form.name,
|
|
132
|
+
hostSubdomain: await aiSiteSubdomain(firestore, hostId)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=ai-job-form-reuse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-form-reuse.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 { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiDraftInstantMs, aiSiteSubdomain, type AiDraftRecord } from './ai-job-drafts'\nimport { listAiJobs } from './ai-jobs'\n\n/**\n * A FORM ANOTHER BUILD JUST MADE, PLACED INSTEAD OF REFUSED (AGL-3765).\n *\n * Two page jobs started from one brief on a Free site, minutes apart, were\n * each planned while the site had no form, and each planned a new one. The\n * first to reach its form step made the site's one Free form; the second\n * reached its own ten seconds later, was refused at the band, and stopped\n * without the page it was asked for. A third job, planned after the form\n * existed, placed it by id and was built.\n *\n * So a form UNIT — the form a page, a guided start or a build makes before\n * the page that places it — that the band refuses looks for a form another AI\n * job made on the same site, and reports that form as what the unit built.\n * The page then binds it by id, exactly as it binds a form the site always\n * had. Another job's form qualifies when the site holds it now, in use, and it\n * was made in the last {@link AI_FORM_REUSE_WINDOW_MS}, or by a job of the\n * same brief. Which job made it is read from what the jobs record: the\n * form's id is the id a job's plan minted for its form creation before the\n * write (`AiBuildPlanCreate.id`), and the output the job recorded after it.\n *\n * A job the member asked for a form by itself never borrows one: it was asked\n * for a new form, not for a page that needs one.\n */\n\n/** How recently another job must have made a form for a refused unit to place it. */\nexport const AI_FORM_REUSE_WINDOW_MS = 15 * 60_000\n\n/** What the unit's output says when it placed another build's form. */\nexport const AI_FORM_REUSED_NOTE = 'Reused the form another build just made.'\n\n/** How many of the site's newest jobs are read for the form's maker. */\nexport const AI_FORM_REUSE_JOBS_READ = 20\n\n/** The fields of a job this module reads. */\nexport type AiFormReuseJob = Pick<AiJob, '$id' | 'hostId' | 'brief' | 'outputs' | 'plan'>\n\n/** A brief as two jobs of the same brief share it: case and spacing aside. */\nfunction briefKey(brief: string | null | undefined): string {\n return (brief ?? '').trim().replace(/\\s+/g, ' ').toLowerCase()\n}\n\n/**\n * The forms other jobs on this site made or were about to make, each with\n * whether its job had the same brief as the job the member started. Read off\n * each job's recorded outputs and its plan's minted form ids; the job the\n * member started is never another job.\n */\nexport function aiSiblingJobFormIds(\n jobs: readonly AiFormReuseJob[],\n input: { hostId: string; originJobId: string },\n): Map<string, { sameBrief: boolean }> {\n const origin = jobs.find((job) => job.$id === input.originJobId)\n const brief = origin ? briefKey(origin.brief) : ''\n const found = new Map<string, { sameBrief: boolean }>()\n for (const job of jobs) {\n if (job.$id === input.originJobId || job.hostId !== input.hostId) continue\n const sameBrief = Boolean(brief) && briefKey(job.brief) === brief\n const ids = [\n ...(job.outputs ?? [])\n .filter((output) => output.resource === 'form' && (output.hostId ?? input.hostId) === input.hostId)\n .map((output) => output.id),\n ...(job.plan?.create ?? []).filter((entry) => entry.kind === 'form').map((entry) => entry.id),\n ]\n for (const id of ids) {\n if (!id) continue\n found.set(id, { sameBrief: sameBrief || (found.get(id)?.sameBrief ?? false) })\n }\n }\n return found\n}\n\nexport interface AiSiblingJobFormDeps {\n /** The site's newest jobs; the machine's list otherwise. */\n listJobs?: (\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n options: { hostId: string; limit: number },\n ) => Promise<readonly AiFormReuseJob[]>\n}\n\n/**\n * The form another AI job made on this site that a refused form unit places\n * instead (AGL-3765), as the draft record the unit reports; `null` when the\n * job is not a unit, or no other job's form qualifies. The newest qualifying\n * form wins.\n */\nexport async function aiSiblingJobForm(\n firestore: FirebaseFirestore.Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'inputs'>; now: Date },\n deps: AiSiblingJobFormDeps = {},\n): Promise<AiDraftRecord | null> {\n const { job } = input\n const hostId = job.hostId\n const originJobId = aiOriginJobId(job)\n // Only a unit of a job that places the form; a form job builds its own.\n if (!hostId || originJobId === job.$id) return null\n const listJobs = deps.listJobs ?? listAiJobs\n const jobs = await listJobs(firestore, job.orgId, { hostId, limit: AI_FORM_REUSE_JOBS_READ })\n const candidates = aiSiblingJobFormIds(jobs, { hostId, originJobId })\n candidates.delete(job.$id)\n if (!candidates.size) return null\n const forms = firestore.collection('hosts').doc(hostId).collection('forms')\n const read = await Promise.all(\n [...candidates.entries()].map(async ([id, { sameBrief }]) => ({ id, sameBrief, doc: await forms.doc(id).get() })),\n )\n const now = input.now.getTime()\n const qualifying = read\n .filter(({ doc }) => doc.exists && !isFormArchived({ archivedAt: doc.get('archivedAt') }))\n .map(({ id, sameBrief, doc }) => ({\n id,\n sameBrief,\n name: String(doc.get('displayName') ?? ''),\n createdAt: aiDraftInstantMs(doc.get('createdAt')),\n }))\n .filter(\n (form) =>\n form.sameBrief || (form.createdAt !== null && now - form.createdAt <= AI_FORM_REUSE_WINDOW_MS),\n )\n .sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))\n const [form] = qualifying\n if (!form) return null\n return {\n id: form.id,\n versionId: null,\n name: form.name,\n hostSubdomain: await aiSiteSubdomain(firestore, hostId),\n }\n}\n"],"names":["isFormArchived","aiOriginJobId","aiDraftInstantMs","aiSiteSubdomain","listAiJobs","AI_FORM_REUSE_WINDOW_MS","AI_FORM_REUSED_NOTE","AI_FORM_REUSE_JOBS_READ","briefKey","brief","trim","replace","toLowerCase","aiSiblingJobFormIds","jobs","input","origin","find","job","$id","originJobId","found","Map","hostId","sameBrief","Boolean","ids","outputs","filter","output","resource","map","id","plan","create","entry","kind","set","get","aiSiblingJobForm","firestore","deps","listJobs","orgId","limit","candidates","delete","size","forms","collection","doc","read","Promise","all","entries","now","getTime","qualifying","exists","archivedAt","name","String","createdAt","form","sort","a","b","versionId","hostSubdomain"],"mappings":"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"}
|
|
@@ -25,6 +25,7 @@ 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
27
|
import { type AiFormDataset, type AiFormDatasetBinding, type AiFormDatasetReadDeps } from './ai-job-form-dataset';
|
|
28
|
+
import { aiSiblingJobForm } from './ai-job-form-reuse';
|
|
28
29
|
import type { AiJobStepRunner } from './ai-job-text-step';
|
|
29
30
|
/**
|
|
30
31
|
* The form step (AGL-2913): the generation step of a `form` job, run once a
|
|
@@ -64,6 +65,14 @@ import type { AiJobStepRunner } from './ai-job-text-step';
|
|
|
64
65
|
* one (AGL-3024). A copy still bound to the form it was copied from is a
|
|
65
66
|
* failure, never an output.
|
|
66
67
|
*
|
|
68
|
+
* A site that holds every form its plan includes refuses the draft
|
|
69
|
+
* (AGL-3765). A form the step builds for a page (a unit of a page job, a
|
|
70
|
+
* guided start or a build) then places the form another AI job made on the
|
|
71
|
+
* site moments before (`ai-job-form-reuse.ts`), as two jobs started from one
|
|
72
|
+
* brief race to. Failing that, a site that still had room when the plan was
|
|
73
|
+
* confirmed stops the job on our side, its credits given back; one that was
|
|
74
|
+
* already full is the workspace's limit, for the member to change.
|
|
75
|
+
*
|
|
67
76
|
* Like the other generation steps, it runs on the job beat, never at an inline
|
|
68
77
|
* door: it registers the least time its lookup rounds, its answer and its
|
|
69
78
|
* re-ask need (AGL-3035), and runs without extended thinking under the routing
|
|
@@ -301,6 +310,12 @@ export declare function aiJobFormPrompt(job: Pick<AiJob, 'brief'>, plan: AiJobPl
|
|
|
301
310
|
* (AGL-3143 §15).
|
|
302
311
|
*/
|
|
303
312
|
export declare const aiFormJobAdmission: AiJobAdmission;
|
|
313
|
+
/**
|
|
314
|
+
* What a form step says when the site's last form was taken after its plan
|
|
315
|
+
* was confirmed (AGL-3765): by another build running beside it, or by a
|
|
316
|
+
* member. The failure is ours, so what the job spent is given back.
|
|
317
|
+
*/
|
|
318
|
+
export declare const AI_FORM_BAND_TAKEN_COPY = "Another build on this site used the last form your plan includes while this one was running, so this one stopped. Describe it again to build it with the form the site has now.";
|
|
304
319
|
export interface AiJobFormStepDeps {
|
|
305
320
|
/** The inventory reader; specs hand in a fake. */
|
|
306
321
|
readInventory?: typeof readSiteInventory;
|
|
@@ -308,6 +323,8 @@ export interface AiJobFormStepDeps {
|
|
|
308
323
|
duplicate?: typeof duplicateResource;
|
|
309
324
|
/** The data plugin's writer lookup the form's dataset is read through (AGL-3616); specs hand in a fake. */
|
|
310
325
|
datasetWriterFor?: AiFormDatasetReadDeps['writerFor'];
|
|
326
|
+
/** The form another build just made that a refused unit places (AGL-3765); specs hand in a fake. */
|
|
327
|
+
siblingForm?: typeof aiSiblingJobForm;
|
|
311
328
|
}
|
|
312
329
|
export declare function createAiJobFormStep(deps?: AiJobFormStepDeps): AiJobStepRunner;
|
|
313
330
|
export declare const runAiJobFormStep: AiJobStepRunner;
|
|
@@ -31,7 +31,8 @@ import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
|
31
31
|
import { AI_FORM_DATASET_MADE_INPUT, aiBindFormToDataset, aiFormDatasetNote, aiFormDatasetPromptLine, aiReadFormDataset } from "./ai-job-form-dataset.js";
|
|
32
32
|
import { aiJobPlanCreationsRefusal } from "./ai-job-plan-creations.js";
|
|
33
33
|
import { aiJobDraftId } from "./ai-job-draft-ids.js";
|
|
34
|
-
import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, writeAiDraft } from "./ai-job-drafts.js";
|
|
34
|
+
import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, readAiDraftBandTakenSince, writeAiDraft } from "./ai-job-drafts.js";
|
|
35
|
+
import { AI_FORM_REUSED_NOTE, aiSiblingJobForm } from "./ai-job-form-reuse.js";
|
|
35
36
|
import { aiBracketedFactsNote, aiConfirmedPlan, aiDoctrineReview, aiGenerationSpent, aiJobBriefLine, aiLimitReview, aiModelNodeIds, aiPlanCreation, aiPlanReferenceLines, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
36
37
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
37
38
|
import { registerAiJobStep } from "./ai-jobs.js";
|
|
@@ -73,6 +74,14 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
73
74
|
* one (AGL-3024). A copy still bound to the form it was copied from is a
|
|
74
75
|
* failure, never an output.
|
|
75
76
|
*
|
|
77
|
+
* A site that holds every form its plan includes refuses the draft
|
|
78
|
+
* (AGL-3765). A form the step builds for a page (a unit of a page job, a
|
|
79
|
+
* guided start or a build) then places the form another AI job made on the
|
|
80
|
+
* site moments before (`ai-job-form-reuse.ts`), as two jobs started from one
|
|
81
|
+
* brief race to. Failing that, a site that still had room when the plan was
|
|
82
|
+
* confirmed stops the job on our side, its credits given back; one that was
|
|
83
|
+
* already full is the workspace's limit, for the member to change.
|
|
84
|
+
*
|
|
76
85
|
* Like the other generation steps, it runs on the job beat, never at an inline
|
|
77
86
|
* door: it registers the least time its lookup rounds, its answer and its
|
|
78
87
|
* re-ask need (AGL-3035), and runs without extended thinking under the routing
|
|
@@ -611,10 +620,16 @@ function listOf(items) {
|
|
|
611
620
|
org: context.org,
|
|
612
621
|
ownCheck: (hostId)=>aiJobPlanCreationsRefusal('form', context, hostId)
|
|
613
622
|
});
|
|
623
|
+
/**
|
|
624
|
+
* What a form step says when the site's last form was taken after its plan
|
|
625
|
+
* was confirmed (AGL-3765): by another build running beside it, or by a
|
|
626
|
+
* member. The failure is ours, so what the job spent is given back.
|
|
627
|
+
*/ export const AI_FORM_BAND_TAKEN_COPY = 'Another build on this site used the last form your plan includes while this one was running, so this one stopped. Describe it again to build it with the form the site has now.';
|
|
614
628
|
export function createAiJobFormStep(deps = {}) {
|
|
615
|
-
var _deps_readInventory, _deps_duplicate;
|
|
629
|
+
var _deps_readInventory, _deps_duplicate, _deps_siblingForm;
|
|
616
630
|
const readInventory = (_deps_readInventory = deps.readInventory) != null ? _deps_readInventory : readSiteInventory;
|
|
617
631
|
const duplicate = (_deps_duplicate = deps.duplicate) != null ? _deps_duplicate : duplicateResource;
|
|
632
|
+
const siblingForm = (_deps_siblingForm = deps.siblingForm) != null ? _deps_siblingForm : aiSiblingJobForm;
|
|
618
633
|
return async ({ job, now, signal, firestore, modelFor })=>{
|
|
619
634
|
var _ref, _orgSnapshot_data;
|
|
620
635
|
var _job_inputs;
|
|
@@ -656,6 +671,49 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
656
671
|
const plan = aiConfirmedPlan(job);
|
|
657
672
|
const creation = aiPlanCreation(plan, 'form');
|
|
658
673
|
const name = (creation == null ? void 0 : creation.name) || AI_JOB_FORM_DEFAULT_NAME;
|
|
674
|
+
// The site holds every form its plan includes (AGL-3765). A unit places a
|
|
675
|
+
// form another build on this site just made, as its page would place any
|
|
676
|
+
// form the site has. Otherwise a site that had room when the plan was
|
|
677
|
+
// confirmed and was filled since stopped this job on our side, given
|
|
678
|
+
// back; one that was already full is the workspace's limit to change. A
|
|
679
|
+
// lookup that fails leaves the refusal as it was.
|
|
680
|
+
const refused = async (refusal, spent)=>{
|
|
681
|
+
const lookup = async (read, fallback)=>{
|
|
682
|
+
try {
|
|
683
|
+
return await read();
|
|
684
|
+
} catch (error) {
|
|
685
|
+
console.error('ai form step: band refusal lookup failed', {
|
|
686
|
+
jobId: job.$id,
|
|
687
|
+
hostId,
|
|
688
|
+
error
|
|
689
|
+
});
|
|
690
|
+
return fallback;
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
const reused = await lookup(()=>siblingForm(firestore, {
|
|
694
|
+
job,
|
|
695
|
+
now
|
|
696
|
+
}), null);
|
|
697
|
+
if (reused) return _extends({}, spent, {
|
|
698
|
+
outputs: [
|
|
699
|
+
output(reused, {
|
|
700
|
+
note: AI_FORM_REUSED_NOTE
|
|
701
|
+
})
|
|
702
|
+
]
|
|
703
|
+
});
|
|
704
|
+
const taken = await lookup(()=>readAiDraftBandTakenSince(firestore, {
|
|
705
|
+
kind: 'form',
|
|
706
|
+
hostId,
|
|
707
|
+
org,
|
|
708
|
+
since: plan == null ? void 0 : plan.confirmedAt
|
|
709
|
+
}), false);
|
|
710
|
+
if (taken) return _extends({}, spent, {
|
|
711
|
+
failure: AI_FORM_BAND_TAKEN_COPY
|
|
712
|
+
});
|
|
713
|
+
return _extends({}, spent, {
|
|
714
|
+
review: aiLimitReview(refusal)
|
|
715
|
+
});
|
|
716
|
+
};
|
|
659
717
|
// The plan starts from a copy of a form the site has (rule 15): the copy
|
|
660
718
|
// is the draft, and nothing is generated. A source gone since the plan
|
|
661
719
|
// was made is built from the brief instead, and one that does not collect
|
|
@@ -725,9 +783,7 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
725
783
|
});
|
|
726
784
|
}
|
|
727
785
|
if (copy.ok === false && copy.status === 403) {
|
|
728
|
-
return aiUnspentOutcome(model
|
|
729
|
-
review: aiLimitReview(copy.error)
|
|
730
|
-
});
|
|
786
|
+
return refused(copy.error, aiUnspentOutcome(model));
|
|
731
787
|
}
|
|
732
788
|
}
|
|
733
789
|
const allowance = await aiDraftAllowanceRefusal(firestore, {
|
|
@@ -735,9 +791,7 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
735
791
|
hostId,
|
|
736
792
|
org
|
|
737
793
|
});
|
|
738
|
-
if (allowance) return aiUnspentOutcome(model
|
|
739
|
-
review: aiLimitReview(allowance)
|
|
740
|
-
});
|
|
794
|
+
if (allowance) return refused(allowance, aiUnspentOutcome(model));
|
|
741
795
|
// What the person answered about this form, where they were asked
|
|
742
796
|
// (AGL-2918): a scaffold's form unit carries the guided start's inputs.
|
|
743
797
|
const decisions = {
|
|
@@ -818,9 +872,7 @@ export function createAiJobFormStep(deps = {}) {
|
|
|
818
872
|
});
|
|
819
873
|
if (write.ok === false) {
|
|
820
874
|
if (write.status === 404) throw new Error(`site ${hostId} vanished while its form was generated`);
|
|
821
|
-
return
|
|
822
|
-
review: aiLimitReview(write.error)
|
|
823
|
-
});
|
|
875
|
+
return refused(write.error, spent);
|
|
824
876
|
}
|
|
825
877
|
// What the person decides next, then the facts the brief did not give (AGL-3056).
|
|
826
878
|
const note = [
|