@aglyn/plugins-ai 1.0.0-beta.221 → 1.0.0-beta.222
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-job-plan.component.js +1 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/jobs/ai-job-crm-step.d.ts +44 -3
- package/src/lib/jobs/ai-job-crm-step.js +298 -8
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +33 -1
- package/src/lib/jobs/ai-job-page-sections.js +55 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +20 -11
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +6 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +52 -15
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -0
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +8 -7
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +2 -2
- package/src/lib/jobs/ai-workflow-records.js +2 -2
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js +1 -0
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-free-page-recording.js +2 -0
- package/src/lib/jobs/fixtures/ai-free-page-recording.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-page-briefs.js +2 -0
- package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -1
- package/src/lib/model/ai-automation-draft.d.ts +4 -4
- package/src/lib/model/ai-automation-draft.js +15 -10
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-automation-format.d.ts +139 -0
- package/src/lib/model/ai-automation-format.js +42 -0
- package/src/lib/model/ai-automation-format.js.map +1 -0
- package/src/lib/model/ai-automation-outline.d.ts +4 -4
- package/src/lib/model/ai-automation-outline.js +10 -8
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +39 -2
- package/src/lib/model/ai-build-plan.js +80 -3
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-workflow-job.js +2 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +23 -0
- package/src/lib/providers/catalog.js +29 -1
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +8 -0
- package/src/lib/runtime/ai-doctrine-validators.js +34 -3
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +1 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +2 -2
- package/src/lib/runtime/ai-eval.js +3 -3
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +8 -1
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +5 -2
- package/src/lib/runtime/seo-audit.js +16 -7
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +10 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +31 -5
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +5 -1
- package/src/lib/tools/ai-seo-tool.js +6 -2
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +15 -4
- package/src/lib/tools/ai-workflow-tool.js +70 -12
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage-axes.js +14 -1
- package/src/lib/usage-axes.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-ai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.222",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-errors": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
41
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.222",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.222",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.222",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.222",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.222",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.222",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.222",
|
|
35
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.222",
|
|
36
|
+
"@aglyn/shared-util-email": "1.0.0-beta.222",
|
|
37
|
+
"@aglyn/shared-util-errors": "1.0.0-beta.222",
|
|
38
|
+
"@aglyn/shared-util-http": "1.0.0-beta.222",
|
|
39
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.222",
|
|
40
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.222",
|
|
41
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.222",
|
|
42
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.222",
|
|
43
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.222",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -125,10 +125,7 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false }) {
|
|
|
125
125
|
variant: "body2",
|
|
126
126
|
role: "listitem",
|
|
127
127
|
children: [
|
|
128
|
-
|
|
129
|
-
screen.title,
|
|
130
|
-
" at ",
|
|
131
|
-
screen.slug,
|
|
128
|
+
screen.record ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…` : `Builds the page ${screen.title} at ${screen.slug}`,
|
|
132
129
|
screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '',
|
|
133
130
|
screen.layout ? ` in ${named(screen.layout)}` : '',
|
|
134
131
|
screen.sections.length ? `: ${screen.sections.map((section)=>section.name).join(', ')}` : ''
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.component.tsx"],"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 {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobPlanCreditEstimate } from '../model/ai-site-job'\nimport { Box, Button, Collapse, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer:\n * what the job reuses from the site, what it creates and why, the screens it\n * builds — and, while the job waits for a person, the one button that\n * confirms the plan or tries a refused step again. The drawer owns the\n * request; this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /** Confirms the plan, or tries the refused step again. */\n onResume: (job: AiJobSummary) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n const waiting = job.status === 'needs_review' && review !== null\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one — the plan's own passes at the nominal credits a step\n // holds, where what a step really costs is its model's tokens. A page job\n // counts the creations it builds before its page (AGL-3031).\n const estimate =\n plan && waiting && review.reason === 'plan' ? aiJobPlanCreditEstimate(job.kind, plan) : 0\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind === 'screen' ? 'page' : entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n Builds the page {screen.title} at {screen.slug}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n {(plan.embeds ?? []).map((embed, index) => (\n <Typography key={`embed-${index}`} variant=\"body2\" role=\"listitem\">\n {aiPlanEmbedLine(embed, named)}\n </Typography>\n ))}\n </Stack>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {estimate > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`}\n </Typography>\n )}\n {waiting && (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy}\n onClick={() => onResume(job)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiJobPlanCreditEstimate","Box","Button","Collapse","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","aiPlanEmbedLine","embed","named","host","link","url","asked","AiJobPlan","job","onResume","busy","staff","plan","detailsOpen","setDetailsOpen","details","ref","slice","labels","waiting","status","estimate","kind","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","title","slug","layout","sections","section","embeds","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","display","toLocaleString","disabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.component.tsx"],"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 {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobPlanCreditEstimate } from '../model/ai-site-job'\nimport { Box, Button, Collapse, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer:\n * what the job reuses from the site, what it creates and why, the screens it\n * builds — and, while the job waits for a person, the one button that\n * confirms the plan or tries a refused step again. The drawer owns the\n * request; this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /** Confirms the plan, or tries the refused step again. */\n onResume: (job: AiJobSummary) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n const waiting = job.status === 'needs_review' && review !== null\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one — the plan's own passes at the nominal credits a step\n // holds, where what a step really costs is its model's tokens. A page job\n // counts the creations it builds before its page (AGL-3031).\n const estimate =\n plan && waiting && review.reason === 'plan' ? aiJobPlanCreditEstimate(job.kind, plan) : 0\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind === 'screen' ? 'page' : entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n {screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n {(plan.embeds ?? []).map((embed, index) => (\n <Typography key={`embed-${index}`} variant=\"body2\" role=\"listitem\">\n {aiPlanEmbedLine(embed, named)}\n </Typography>\n ))}\n </Stack>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {estimate > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`}\n </Typography>\n )}\n {waiting && (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={busy}\n onClick={() => onResume(job)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiJobPlanCreditEstimate","Box","Button","Collapse","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","aiPlanEmbedLine","embed","named","host","link","url","asked","AiJobPlan","job","onResume","busy","staff","plan","detailsOpen","setDetailsOpen","details","ref","slice","labels","waiting","status","estimate","kind","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","embeds","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","display","toLocaleString","disabled"],"mappings":"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"}
|
|
@@ -104,11 +104,52 @@ export declare function aiCrmRecordInstructions(kind: AiCrmRecordKind): AiSystem
|
|
|
104
104
|
export declare const AI_CRM_EMAIL_INSTRUCTIONS: AiSystemBlock[];
|
|
105
105
|
export declare const AI_CRM_MAPPING_INSTRUCTIONS: AiSystemBlock[];
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
* A record's facts as the CRM's reader marks them when it answered the whole
|
|
108
|
+
* record (AGL-3520): `wholeRecord: true`, set only while the organization has
|
|
109
|
+
* `release_crm_assist_whole_record` on. Absent, the facts are the disclosed
|
|
110
|
+
* ones and are written by {@link aiCrmDisclosedFactsLines}.
|
|
111
|
+
*/
|
|
112
|
+
export declare const AI_CRM_WHOLE_RECORD_FIELD = "wholeRecord";
|
|
113
|
+
/**
|
|
114
|
+
* The facts as lines of text while the published pages describe the narrow
|
|
115
|
+
* set: only the fields named here are written, whatever else a reader
|
|
116
|
+
* reports, so what reaches a prompt is decided in two places that both have
|
|
117
|
+
* to agree — the CRM's disclosed builders and this list. A company's account
|
|
118
|
+
* fields beyond its headcount, and a lead's profile fields, stay out of the
|
|
119
|
+
* prompt as they always have.
|
|
120
|
+
*/
|
|
121
|
+
export declare function aiCrmDisclosedFactsLines(kind: AiCrmRecordKind, facts: Facts): string[];
|
|
122
|
+
/**
|
|
123
|
+
* The facts as lines of text: the WHOLE record the CRM reports (AGL-3520) —
|
|
124
|
+
* every standard field, its contact details and addresses, its picklist
|
|
125
|
+
* labels, its custom fields under their labels, its consent, notes,
|
|
126
|
+
* timeline, open tasks and deals. A field the record does not hold writes
|
|
127
|
+
* no line. The CRM's builders decide what a record is; nothing here drops a
|
|
128
|
+
* field they report, and no reader reports a token, an account identifier
|
|
129
|
+
* or a record id beyond a stage's.
|
|
110
130
|
*/
|
|
111
131
|
export declare function aiCrmFactsLines(kind: AiCrmRecordKind, facts: Facts): string[];
|
|
132
|
+
/**
|
|
133
|
+
* The most characters a record's facts take in a request (AGL-3520) —
|
|
134
|
+
* about six thousand tokens at the three characters a token the routing
|
|
135
|
+
* ceilings are measured with. The CRM's own cuts keep a record well under
|
|
136
|
+
* it; a record that holds everything at once is fitted by
|
|
137
|
+
* {@link aiCrmFitFacts}, which shortens its long texts and drops no field.
|
|
138
|
+
*/
|
|
139
|
+
export declare const AI_CRM_FACTS_MAX_CHARS = 18000;
|
|
140
|
+
/**
|
|
141
|
+
* The facts, fitted to {@link AI_CRM_FACTS_MAX_CHARS} as lines: unchanged
|
|
142
|
+
* when they fit, otherwise with the long texts — the notes, each timeline
|
|
143
|
+
* entry's text, each task's notes and each custom value — cut shorter step
|
|
144
|
+
* by step. Every field stays; only how much of a long text is sent changes.
|
|
145
|
+
*/
|
|
146
|
+
export declare function aiCrmFitFacts(kind: AiCrmRecordKind, facts: Facts): Facts;
|
|
147
|
+
/**
|
|
148
|
+
* The lines a prompt carries for a record: the whole record, fitted, when
|
|
149
|
+
* the reader marked it {@link AI_CRM_WHOLE_RECORD_FIELD}; otherwise the
|
|
150
|
+
* disclosed lines alone.
|
|
151
|
+
*/
|
|
152
|
+
export declare function aiCrmPromptFactsLines(kind: AiCrmRecordKind, facts: Facts): string[];
|
|
112
153
|
/** The user turn for a record. */
|
|
113
154
|
export declare function aiCrmRecordPrompt(kind: AiCrmRecordKind, facts: Facts): string;
|
|
114
155
|
/** The user turn for an email draft. */
|
|
@@ -170,6 +170,8 @@ function timelineLines(value) {
|
|
|
170
170
|
...entries.map((entry)=>{
|
|
171
171
|
const detail = [
|
|
172
172
|
text(entry['direction']),
|
|
173
|
+
entry['from'] ? `from ${text(entry['from'])}` : '',
|
|
174
|
+
entry['to'] ? `to ${text(entry['to'])}` : '',
|
|
173
175
|
text(entry['delivery']),
|
|
174
176
|
text(entry['outcome'])
|
|
175
177
|
].filter(Boolean);
|
|
@@ -188,11 +190,17 @@ function taskLines(value) {
|
|
|
188
190
|
...tasks.map((task)=>`- ${text(task['title'])} (${[
|
|
189
191
|
text(task['kind']),
|
|
190
192
|
`${text(task['priority']) || 'normal'} priority`,
|
|
193
|
+
...task['status'] ? [
|
|
194
|
+
text(task['status'])
|
|
195
|
+
] : [],
|
|
191
196
|
task['due'] ? `due ${text(task['due'])}` : 'no due date',
|
|
192
197
|
...task['overdue'] === true ? [
|
|
193
198
|
'overdue'
|
|
199
|
+
] : [],
|
|
200
|
+
...task['assignee'] ? [
|
|
201
|
+
`assigned to ${text(task['assignee'])}`
|
|
194
202
|
] : []
|
|
195
|
-
].join(', ')})`)
|
|
203
|
+
].join(', ')})` + (task['notes'] ? `: ${text(task['notes'])}` : ''))
|
|
196
204
|
];
|
|
197
205
|
}
|
|
198
206
|
function dealLines(value) {
|
|
@@ -210,18 +218,184 @@ function dealLines(value) {
|
|
|
210
218
|
].filter(Boolean).join(', ')}`)
|
|
211
219
|
];
|
|
212
220
|
}
|
|
221
|
+
/** The record's custom field values under their labels (AGL-3520). */ function customLines(value) {
|
|
222
|
+
const fields = list(value).filter((field)=>text(field['label']) && text(field['value']));
|
|
223
|
+
return fields.length ? [
|
|
224
|
+
'Custom fields:',
|
|
225
|
+
...fields.map((field)=>`- ${text(field['label'])}: ${text(field['value'])}`)
|
|
226
|
+
] : [];
|
|
227
|
+
}
|
|
228
|
+
const yesNo = (label, value)=>value === true ? [
|
|
229
|
+
label
|
|
230
|
+
] : [];
|
|
231
|
+
/**
|
|
232
|
+
* A record's facts as the CRM's reader marks them when it answered the whole
|
|
233
|
+
* record (AGL-3520): `wholeRecord: true`, set only while the organization has
|
|
234
|
+
* `release_crm_assist_whole_record` on. Absent, the facts are the disclosed
|
|
235
|
+
* ones and are written by {@link aiCrmDisclosedFactsLines}.
|
|
236
|
+
*/ export const AI_CRM_WHOLE_RECORD_FIELD = 'wholeRecord';
|
|
237
|
+
/** Keeps only `keys` of each entry: the disclosed writer's allow-list for nested rows. */ const only = (value, keys)=>list(value).map((entry)=>Object.fromEntries(keys.filter((key)=>key in entry).map((key)=>[
|
|
238
|
+
key,
|
|
239
|
+
entry[key]
|
|
240
|
+
])));
|
|
241
|
+
/** What the disclosed writer reads of a timeline entry: never who an email was from or to. */ const DISCLOSED_TIMELINE_KEYS = [
|
|
242
|
+
'on',
|
|
243
|
+
'kind',
|
|
244
|
+
'direction',
|
|
245
|
+
'subject',
|
|
246
|
+
'text',
|
|
247
|
+
'outcome',
|
|
248
|
+
'delivery'
|
|
249
|
+
];
|
|
250
|
+
/** What the disclosed writer reads of an open task: never its assignee, status or notes. */ const DISCLOSED_TASK_KEYS = [
|
|
251
|
+
'title',
|
|
252
|
+
'kind',
|
|
253
|
+
'priority',
|
|
254
|
+
'due',
|
|
255
|
+
'overdue'
|
|
256
|
+
];
|
|
257
|
+
/**
|
|
258
|
+
* The facts as lines of text while the published pages describe the narrow
|
|
259
|
+
* set: only the fields named here are written, whatever else a reader
|
|
260
|
+
* reports, so what reaches a prompt is decided in two places that both have
|
|
261
|
+
* to agree — the CRM's disclosed builders and this list. A company's account
|
|
262
|
+
* fields beyond its headcount, and a lead's profile fields, stay out of the
|
|
263
|
+
* prompt as they always have.
|
|
264
|
+
*/ export function aiCrmDisclosedFactsLines(kind, facts) {
|
|
265
|
+
const timeline = timelineLines(only(facts['timeline'], DISCLOSED_TIMELINE_KEYS));
|
|
266
|
+
const openTasks = taskLines(only(facts['openTasks'], DISCLOSED_TASK_KEYS));
|
|
267
|
+
if (kind === 'contact') {
|
|
268
|
+
const orders = Number(facts['orders']) || 0;
|
|
269
|
+
return [
|
|
270
|
+
...line('Contact', facts['name']),
|
|
271
|
+
...line('Salutation', facts['salutation']),
|
|
272
|
+
...line('Job title', facts['jobTitle']),
|
|
273
|
+
...line('Department', facts['department']),
|
|
274
|
+
// A request the person made, which a drafted next step must respect (AGL-3515).
|
|
275
|
+
...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),
|
|
276
|
+
...line('Company', facts['company']),
|
|
277
|
+
...line('Lifecycle stage', facts['lifecycleStage']),
|
|
278
|
+
...line('Tags', words(facts['tags']).join(', ')),
|
|
279
|
+
...line('Came in through', words(facts['sources']).join(', ')),
|
|
280
|
+
...orders ? [
|
|
281
|
+
`Orders: ${orders}${facts['lastPurchase'] ? `, the last on ${text(facts['lastPurchase'])}` : ''}`
|
|
282
|
+
] : [],
|
|
283
|
+
...line('In the CRM since', facts['since']),
|
|
284
|
+
...line('Last opened or clicked an email', facts['lastEmailEngagement']),
|
|
285
|
+
...line('Notes', facts['notes']),
|
|
286
|
+
...timeline,
|
|
287
|
+
...openTasks,
|
|
288
|
+
...dealLines(facts['deals'])
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
if (kind === 'company') {
|
|
292
|
+
var _facts_employees;
|
|
293
|
+
return [
|
|
294
|
+
...line('Company', facts['name']),
|
|
295
|
+
...line('Domain', facts['domain']),
|
|
296
|
+
...line('Industry', facts['industry']),
|
|
297
|
+
...line('Employees', (_facts_employees = facts['employees']) != null ? _facts_employees : ''),
|
|
298
|
+
...line('Tags', words(facts['tags']).join(', ')),
|
|
299
|
+
...line('People in the CRM', facts['people']),
|
|
300
|
+
...line('In the CRM since', facts['since']),
|
|
301
|
+
...line('Notes', facts['notes']),
|
|
302
|
+
...timeline,
|
|
303
|
+
...openTasks,
|
|
304
|
+
...dealLines(facts['deals'])
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
if (kind === 'deal') {
|
|
308
|
+
const stages = list(facts['stages']);
|
|
309
|
+
const withWhom = [
|
|
310
|
+
text(facts['contact']),
|
|
311
|
+
text(facts['company'])
|
|
312
|
+
].filter(Boolean).join(' at ');
|
|
313
|
+
return [
|
|
314
|
+
...line('Deal', facts['title']),
|
|
315
|
+
...line('Pipeline', facts['pipeline']),
|
|
316
|
+
...line('Status', facts['status']),
|
|
317
|
+
...line('Stage', facts['stage'] ? `${text(facts['stage'])} (id ${text(facts['stageId'])})` : ''),
|
|
318
|
+
...line('Stages in order', stages.map((stage)=>`${text(stage['id'])} "${text(stage['name'])}" (${text(stage['kind'])})`).join('; ')),
|
|
319
|
+
...line('In this stage since', facts['inStageSince']),
|
|
320
|
+
...line('Amount', facts['amount']),
|
|
321
|
+
// Salesforce's Opportunity fields (AGL-3516).
|
|
322
|
+
...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),
|
|
323
|
+
...line('Forecast category', facts['forecastCategory']),
|
|
324
|
+
...line('Type', facts['type']),
|
|
325
|
+
...line('Lead source', facts['leadSource']),
|
|
326
|
+
...line('Next step', facts['nextStep']),
|
|
327
|
+
...line('Expected to close', facts['expectedClose']),
|
|
328
|
+
...line('Lost reason', facts['lostReason']),
|
|
329
|
+
...line('With', withWhom),
|
|
330
|
+
...contactRoleLines(facts['contactRoles']),
|
|
331
|
+
...line('Products on the deal', typeof facts['products'] === 'number' ? facts['products'] || '' : ''),
|
|
332
|
+
...line('In the CRM since', facts['since']),
|
|
333
|
+
...line('Notes', facts['notes']),
|
|
334
|
+
...timeline,
|
|
335
|
+
...openTasks
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
return [
|
|
339
|
+
...line('Lead', facts['name']),
|
|
340
|
+
...line('Status', facts['status']),
|
|
341
|
+
...line('Captured through', words(facts['sources']).join(', ')),
|
|
342
|
+
...line('Captures', Number(facts['captures']) || ''),
|
|
343
|
+
...line('First seen', facts['firstSeen']),
|
|
344
|
+
...line('Last seen', facts['lastSeen']),
|
|
345
|
+
`Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,
|
|
346
|
+
`Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,
|
|
347
|
+
...line('Unqualified reason', facts['unqualifiedReason']),
|
|
348
|
+
...line('Notes', facts['notes']),
|
|
349
|
+
...timeline
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
/** Every contact on a deal and the part each plays (AGL-3521), by name. */ function contactRoleLines(value) {
|
|
353
|
+
return line('Contact roles', list(value).map((row)=>{
|
|
354
|
+
const part = [
|
|
355
|
+
text(row['role']),
|
|
356
|
+
row['primary'] === true ? 'Primary' : ''
|
|
357
|
+
].filter(Boolean).join(', ');
|
|
358
|
+
return part ? `${text(row['name'])} (${part})` : text(row['name']);
|
|
359
|
+
}).join('; '));
|
|
360
|
+
}
|
|
213
361
|
/**
|
|
214
|
-
* The facts as lines of text
|
|
215
|
-
*
|
|
216
|
-
*
|
|
362
|
+
* The facts as lines of text: the WHOLE record the CRM reports (AGL-3520) —
|
|
363
|
+
* every standard field, its contact details and addresses, its picklist
|
|
364
|
+
* labels, its custom fields under their labels, its consent, notes,
|
|
365
|
+
* timeline, open tasks and deals. A field the record does not hold writes
|
|
366
|
+
* no line. The CRM's builders decide what a record is; nothing here drops a
|
|
367
|
+
* field they report, and no reader reports a token, an account identifier
|
|
368
|
+
* or a record id beyond a stage's.
|
|
217
369
|
*/ export function aiCrmFactsLines(kind, facts) {
|
|
370
|
+
var _facts_employees;
|
|
218
371
|
if (kind === 'contact') {
|
|
219
372
|
const orders = Number(facts['orders']) || 0;
|
|
220
373
|
return [
|
|
221
374
|
...line('Contact', facts['name']),
|
|
375
|
+
...line('Salutation', facts['salutation']),
|
|
376
|
+
...line('First name', facts['firstName']),
|
|
377
|
+
...line('Last name', facts['lastName']),
|
|
378
|
+
...line('Email', words(facts['emails']).join(', ')),
|
|
379
|
+
...line('Phone', facts['phone']),
|
|
380
|
+
...line('Mobile phone', facts['mobilePhone']),
|
|
381
|
+
...line('Home phone', facts['homePhone']),
|
|
382
|
+
...line('Other phone', facts['otherPhone']),
|
|
383
|
+
...line('Fax', facts['fax']),
|
|
384
|
+
// A request the person made, which a drafted next step must respect (AGL-3515).
|
|
385
|
+
...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),
|
|
222
386
|
...line('Job title', facts['jobTitle']),
|
|
387
|
+
...line('Department', facts['department']),
|
|
223
388
|
...line('Company', facts['company']),
|
|
389
|
+
...line('Reports to', facts['reportsTo']),
|
|
390
|
+
...line('Assistant', facts['assistant']),
|
|
391
|
+
...line('Assistant phone', facts['assistantPhone']),
|
|
392
|
+
...line('Birthdate', facts['birthdate']),
|
|
393
|
+
...line('Mailing address', facts['mailingAddress']),
|
|
394
|
+
...line('Other address', facts['otherAddress']),
|
|
224
395
|
...line('Lifecycle stage', facts['lifecycleStage']),
|
|
396
|
+
...line('Lead source', facts['leadSource']),
|
|
397
|
+
...line('Owner', facts['owner']),
|
|
398
|
+
...line('Marketing email consent', facts['marketingConsent']),
|
|
225
399
|
...line('Tags', words(facts['tags']).join(', ')),
|
|
226
400
|
...line('Came in through', words(facts['sources']).join(', ')),
|
|
227
401
|
...orders ? [
|
|
@@ -229,6 +403,7 @@ function dealLines(value) {
|
|
|
229
403
|
] : [],
|
|
230
404
|
...line('In the CRM since', facts['since']),
|
|
231
405
|
...line('Last opened or clicked an email', facts['lastEmailEngagement']),
|
|
406
|
+
...customLines(facts['custom']),
|
|
232
407
|
...line('Notes', facts['notes']),
|
|
233
408
|
...timelineLines(facts['timeline']),
|
|
234
409
|
...taskLines(facts['openTasks']),
|
|
@@ -236,13 +411,33 @@ function dealLines(value) {
|
|
|
236
411
|
];
|
|
237
412
|
}
|
|
238
413
|
if (kind === 'company') {
|
|
414
|
+
var _facts_employees1;
|
|
239
415
|
return [
|
|
240
416
|
...line('Company', facts['name']),
|
|
241
417
|
...line('Domain', facts['domain']),
|
|
418
|
+
...line('Website', facts['website']),
|
|
419
|
+
...line('Phone', facts['phone']),
|
|
420
|
+
...line('Fax', facts['fax']),
|
|
421
|
+
// Salesforce's Account fields (AGL-3514).
|
|
422
|
+
...line('Type', facts['type']),
|
|
242
423
|
...line('Industry', facts['industry']),
|
|
424
|
+
...line('Rating', facts['rating']),
|
|
425
|
+
...line('Ownership', facts['ownership']),
|
|
426
|
+
...line('Account source', facts['accountSource']),
|
|
427
|
+
...line('Employees', (_facts_employees1 = facts['employees']) != null ? _facts_employees1 : ''),
|
|
428
|
+
...line('Annual revenue', facts['annualRevenue']),
|
|
429
|
+
...line('Account number', facts['accountNumber']),
|
|
430
|
+
...line('Account site', facts['site']),
|
|
431
|
+
...line('Ticker symbol', facts['tickerSymbol']),
|
|
432
|
+
...line('SIC code', facts['sicCode']),
|
|
433
|
+
...line('Billing address', facts['billingAddress']),
|
|
434
|
+
...line('Shipping address', facts['shippingAddress']),
|
|
435
|
+
...line('Parent company', facts['parentCompany']),
|
|
436
|
+
...line('Owner', facts['owner']),
|
|
243
437
|
...line('Tags', words(facts['tags']).join(', ')),
|
|
244
438
|
...line('People in the CRM', facts['people']),
|
|
245
439
|
...line('In the CRM since', facts['since']),
|
|
440
|
+
...customLines(facts['custom']),
|
|
246
441
|
...line('Notes', facts['notes']),
|
|
247
442
|
...timelineLines(facts['timeline']),
|
|
248
443
|
...taskLines(facts['openTasks']),
|
|
@@ -251,6 +446,7 @@ function dealLines(value) {
|
|
|
251
446
|
}
|
|
252
447
|
if (kind === 'deal') {
|
|
253
448
|
const stages = list(facts['stages']);
|
|
449
|
+
const products = list(facts['products']);
|
|
254
450
|
const withWhom = [
|
|
255
451
|
text(facts['contact']),
|
|
256
452
|
text(facts['company'])
|
|
@@ -263,11 +459,24 @@ function dealLines(value) {
|
|
|
263
459
|
...line('Stages in order', stages.map((stage)=>`${text(stage['id'])} "${text(stage['name'])}" (${text(stage['kind'])})`).join('; ')),
|
|
264
460
|
...line('In this stage since', facts['inStageSince']),
|
|
265
461
|
...line('Amount', facts['amount']),
|
|
462
|
+
// Salesforce's Opportunity fields (AGL-3516).
|
|
463
|
+
...line('Probability', typeof facts['probability'] === 'number' ? `${facts['probability']}%` : ''),
|
|
464
|
+
...line('Forecast category', facts['forecastCategory']),
|
|
465
|
+
...line('Type', facts['type']),
|
|
466
|
+
...line('Lead source', facts['leadSource']),
|
|
467
|
+
...line('Campaign', facts['campaign']),
|
|
468
|
+
...line('Next step', facts['nextStep']),
|
|
266
469
|
...line('Expected to close', facts['expectedClose']),
|
|
267
470
|
...line('Lost reason', facts['lostReason']),
|
|
268
471
|
...line('With', withWhom),
|
|
269
|
-
...line('
|
|
472
|
+
...line('Owner', facts['owner']),
|
|
473
|
+
...contactRoleLines(facts['contactRoles']),
|
|
474
|
+
...products.length ? [
|
|
475
|
+
'Products:',
|
|
476
|
+
...products.map((product)=>`- ${text(product['name'])} × ${Number(product['quantity']) || 0}` + (product['unitAmount'] ? ` at ${text(product['unitAmount'])}` : ''))
|
|
477
|
+
] : [],
|
|
270
478
|
...line('In the CRM since', facts['since']),
|
|
479
|
+
...customLines(facts['custom']),
|
|
271
480
|
...line('Notes', facts['notes']),
|
|
272
481
|
...timelineLines(facts['timeline']),
|
|
273
482
|
...taskLines(facts['openTasks'])
|
|
@@ -275,7 +484,28 @@ function dealLines(value) {
|
|
|
275
484
|
}
|
|
276
485
|
return [
|
|
277
486
|
...line('Lead', facts['name']),
|
|
487
|
+
...line('Salutation', facts['salutation']),
|
|
488
|
+
...line('First name', facts['firstName']),
|
|
489
|
+
...line('Last name', facts['lastName']),
|
|
490
|
+
...line('Email', facts['email']),
|
|
491
|
+
...line('Phone', facts['phone']),
|
|
492
|
+
...line('Mobile phone', facts['mobilePhone']),
|
|
493
|
+
...line('Fax', facts['fax']),
|
|
494
|
+
...yesNo('Do not call: they asked not to be phoned', facts['doNotCall']),
|
|
495
|
+
...line('Company', facts['company']),
|
|
496
|
+
...line('Job title', facts['jobTitle']),
|
|
497
|
+
...line('Website', facts['website']),
|
|
498
|
+
...line('Address', facts['address']),
|
|
499
|
+
...line('Industry', facts['industry']),
|
|
500
|
+
...line('Rating', facts['rating']),
|
|
501
|
+
...line('Employees', (_facts_employees = facts['employees']) != null ? _facts_employees : ''),
|
|
502
|
+
...line('Annual revenue', facts['annualRevenue']),
|
|
503
|
+
...line('Lead source', facts['leadSource']),
|
|
278
504
|
...line('Status', facts['status']),
|
|
505
|
+
...line('Owner', facts['owner']),
|
|
506
|
+
...line('Campaigns', words(facts['campaigns']).join(', ')),
|
|
507
|
+
...line('Marketing email consent', facts['marketingConsent']),
|
|
508
|
+
...line('Tags', words(facts['tags']).join(', ')),
|
|
279
509
|
...line('Captured through', words(facts['sources']).join(', ')),
|
|
280
510
|
...line('Captures', Number(facts['captures']) || ''),
|
|
281
511
|
...line('First seen', facts['firstSeen']),
|
|
@@ -283,14 +513,74 @@ function dealLines(value) {
|
|
|
283
513
|
`Assigned to a teammate: ${facts['assigned'] === true ? 'yes' : 'no'}`,
|
|
284
514
|
`Converted to a contact: ${facts['converted'] === true ? 'yes' : 'no'}`,
|
|
285
515
|
...line('Unqualified reason', facts['unqualifiedReason']),
|
|
516
|
+
...customLines(facts['custom']),
|
|
286
517
|
...line('Notes', facts['notes']),
|
|
287
|
-
...timelineLines(facts['timeline'])
|
|
518
|
+
...timelineLines(facts['timeline']),
|
|
519
|
+
...list(facts['openTasks']).length ? taskLines(facts['openTasks']) : []
|
|
288
520
|
];
|
|
289
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* The most characters a record's facts take in a request (AGL-3520) —
|
|
524
|
+
* about six thousand tokens at the three characters a token the routing
|
|
525
|
+
* ceilings are measured with. The CRM's own cuts keep a record well under
|
|
526
|
+
* it; a record that holds everything at once is fitted by
|
|
527
|
+
* {@link aiCrmFitFacts}, which shortens its long texts and drops no field.
|
|
528
|
+
*/ export const AI_CRM_FACTS_MAX_CHARS = 18000;
|
|
529
|
+
/** The cuts {@link aiCrmFitFacts} tries in turn: notes, then each timeline text, then each custom value and task note. */ const FIT_STEPS = [
|
|
530
|
+
{
|
|
531
|
+
notes: 300,
|
|
532
|
+
text: 160,
|
|
533
|
+
custom: 160
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
notes: 160,
|
|
537
|
+
text: 80,
|
|
538
|
+
custom: 80
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
notes: 80,
|
|
542
|
+
text: 40,
|
|
543
|
+
custom: 40
|
|
544
|
+
}
|
|
545
|
+
];
|
|
546
|
+
const cut = (value, max)=>typeof value === 'string' && value.length > max ? `${value.slice(0, max - 1).trimEnd()}…` : value;
|
|
547
|
+
/**
|
|
548
|
+
* The facts, fitted to {@link AI_CRM_FACTS_MAX_CHARS} as lines: unchanged
|
|
549
|
+
* when they fit, otherwise with the long texts — the notes, each timeline
|
|
550
|
+
* entry's text, each task's notes and each custom value — cut shorter step
|
|
551
|
+
* by step. Every field stays; only how much of a long text is sent changes.
|
|
552
|
+
*/ export function aiCrmFitFacts(kind, facts) {
|
|
553
|
+
const size = (candidate)=>aiCrmFactsLines(kind, candidate).join('\n').length;
|
|
554
|
+
if (size(facts) <= AI_CRM_FACTS_MAX_CHARS) return facts;
|
|
555
|
+
let fitted = facts;
|
|
556
|
+
for (const step of FIT_STEPS){
|
|
557
|
+
fitted = _extends({}, facts, {
|
|
558
|
+
notes: cut(facts['notes'], step.notes),
|
|
559
|
+
timeline: list(facts['timeline']).map((entry)=>_extends({}, entry, {
|
|
560
|
+
text: cut(entry['text'], step.text)
|
|
561
|
+
})),
|
|
562
|
+
openTasks: list(facts['openTasks']).map((task)=>_extends({}, task, {
|
|
563
|
+
notes: cut(task['notes'], step.text)
|
|
564
|
+
})),
|
|
565
|
+
custom: list(facts['custom']).map((field)=>_extends({}, field, {
|
|
566
|
+
value: cut(field['value'], step.custom)
|
|
567
|
+
}))
|
|
568
|
+
});
|
|
569
|
+
if (size(fitted) <= AI_CRM_FACTS_MAX_CHARS) return fitted;
|
|
570
|
+
}
|
|
571
|
+
return fitted;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The lines a prompt carries for a record: the whole record, fitted, when
|
|
575
|
+
* the reader marked it {@link AI_CRM_WHOLE_RECORD_FIELD}; otherwise the
|
|
576
|
+
* disclosed lines alone.
|
|
577
|
+
*/ export function aiCrmPromptFactsLines(kind, facts) {
|
|
578
|
+
return facts[AI_CRM_WHOLE_RECORD_FIELD] === true ? aiCrmFactsLines(kind, aiCrmFitFacts(kind, facts)) : aiCrmDisclosedFactsLines(kind, facts);
|
|
579
|
+
}
|
|
290
580
|
/** The user turn for a record. */ export function aiCrmRecordPrompt(kind, facts) {
|
|
291
581
|
return [
|
|
292
582
|
`Record: ${kind}`,
|
|
293
|
-
...
|
|
583
|
+
...aiCrmPromptFactsLines(kind, facts)
|
|
294
584
|
].join('\n');
|
|
295
585
|
}
|
|
296
586
|
/** The user turn for an email draft. */ export function aiCrmEmailPrompt(input) {
|
|
@@ -300,7 +590,7 @@ function dealLines(value) {
|
|
|
300
590
|
`The team member asks: ${input.request.replace(/\s+/g, ' ').trim().slice(0, AI_CRM_EMAIL_REQUEST_MAX_CHARS)}`,
|
|
301
591
|
'',
|
|
302
592
|
'Facts:',
|
|
303
|
-
...
|
|
593
|
+
...aiCrmPromptFactsLines(input.kind, input.facts)
|
|
304
594
|
].join('\n');
|
|
305
595
|
}
|
|
306
596
|
/** The user turn for an import's columns. */ export function aiCrmMappingPrompt(input) {
|