@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.230
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/README.md +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +35 -5
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- package/src/lib/components/ai-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +140 -16
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +657 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-step.js +6 -6
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
- package/src/lib/jobs/ai-job-plan-step.js +79 -11
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-site-step.js +200 -58
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +318 -13
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +377 -13
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +471 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +56 -0
- package/src/lib/model/ai-build-progress.js +128 -0
- package/src/lib/model/ai-build-progress.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +3 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +80 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +20 -3
- package/src/lib/model/ai-site-build-progress.js +170 -10
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +240 -11
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +81 -4
- package/src/lib/providers/catalog.js +80 -9
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
- package/src/lib/runtime/ai-doctrine-validators.js +89 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +11 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +15 -1
- package/src/lib/runtime/ai-eval.js +52 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
- package/src/lib/tools/ai-workflow-tool.js +31 -0
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
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 { AiJobItemLedger, AiJobSummary } from './ai-jobs.types';
|
|
18
|
+
/**
|
|
19
|
+
* What a `build` job shows of each item (AGL-3616), wherever it is shown —
|
|
20
|
+
* the AI jobs drawer, the job's own page, the chat card: the item, where it
|
|
21
|
+
* stands, what it cost, and what became of its credits when it failed.
|
|
22
|
+
*
|
|
23
|
+
* Pure: no React, no request.
|
|
24
|
+
*/
|
|
25
|
+
/** Where one item stands, as a row's icon reads it. */
|
|
26
|
+
export type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped';
|
|
27
|
+
export interface AiBuildItemRow {
|
|
28
|
+
slot: string;
|
|
29
|
+
/** "Page: About", "Form: Contact". */
|
|
30
|
+
label: string;
|
|
31
|
+
state: AiBuildItemRowState;
|
|
32
|
+
/** What the person should know: the failure, the note, what was given back. */
|
|
33
|
+
detail: string | null;
|
|
34
|
+
}
|
|
35
|
+
/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */
|
|
36
|
+
export declare function aiBuildOpNoun(op: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* What became of a failed item's credits (AGL-3616), in the words every
|
|
39
|
+
* surface uses for a failure on our side: "This one’s on us — you weren’t
|
|
40
|
+
* charged." A failure that was not ours — the model declining, a site at its
|
|
41
|
+
* allowance — says what it used, which was charged. `null` for an item that
|
|
42
|
+
* did not fail.
|
|
43
|
+
*/
|
|
44
|
+
export declare function aiBuildItemRefundCopy(row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>): string | null;
|
|
45
|
+
/** One row an item, in the order the build planned them. */
|
|
46
|
+
export declare function aiBuildItemRows(job: Pick<AiJobSummary, 'items'>): AiBuildItemRow[];
|
|
47
|
+
/** How a build came out, in one line: "4 of 5 built; 1 failed." `null` while it runs. */
|
|
48
|
+
export declare function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null;
|
|
49
|
+
/** Whether a finished build — or a site that built part of itself — has failed items to try again. */
|
|
50
|
+
export declare function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* What a guided start that built part of its site says (AGL-3616), in the
|
|
53
|
+
* words every surface uses: "Built 1 of 2 pages. Home couldn’t be built —
|
|
54
|
+
* that one’s on us…". `null` when every page was built, or none was.
|
|
55
|
+
*/
|
|
56
|
+
export declare function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null;
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
*/ const OP_NOUNS = {
|
|
17
|
+
page: 'Page',
|
|
18
|
+
layout: 'Layout',
|
|
19
|
+
form: 'Form',
|
|
20
|
+
component: 'Component',
|
|
21
|
+
email: 'Email design',
|
|
22
|
+
template: 'Page template',
|
|
23
|
+
campaign: 'Email campaign',
|
|
24
|
+
workflow: 'Automation'
|
|
25
|
+
};
|
|
26
|
+
/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */ export function aiBuildOpNoun(op) {
|
|
27
|
+
const known = OP_NOUNS[op];
|
|
28
|
+
if (known) return known;
|
|
29
|
+
const words = op.replace(/[-_]+/g, ' ').trim();
|
|
30
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What became of a failed item's credits (AGL-3616), in the words every
|
|
34
|
+
* surface uses for a failure on our side: "This one’s on us — you weren’t
|
|
35
|
+
* charged." A failure that was not ours — the model declining, a site at its
|
|
36
|
+
* allowance — says what it used, which was charged. `null` for an item that
|
|
37
|
+
* did not fail.
|
|
38
|
+
*/ export function aiBuildItemRefundCopy(row) {
|
|
39
|
+
var _row_creditsSpent;
|
|
40
|
+
var _row_failure;
|
|
41
|
+
if (row.status !== 'failed') return null;
|
|
42
|
+
if ((_row_failure = row.failure) == null ? void 0 : _row_failure.ours) {
|
|
43
|
+
var _row_creditsRefunded;
|
|
44
|
+
const refunded = Math.max(0, Math.floor((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0));
|
|
45
|
+
if (refunded > 0) {
|
|
46
|
+
return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`;
|
|
47
|
+
}
|
|
48
|
+
return 'This one’s on us — you weren’t charged.';
|
|
49
|
+
}
|
|
50
|
+
const spent = Math.max(0, Math.floor((_row_creditsSpent = row.creditsSpent) != null ? _row_creditsSpent : 0));
|
|
51
|
+
return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null;
|
|
52
|
+
}
|
|
53
|
+
const STATES = {
|
|
54
|
+
pending: 'waiting',
|
|
55
|
+
running: 'active',
|
|
56
|
+
succeeded: 'done',
|
|
57
|
+
degraded: 'done',
|
|
58
|
+
failed: 'failed',
|
|
59
|
+
skipped: 'skipped'
|
|
60
|
+
};
|
|
61
|
+
/** One row an item, in the order the build planned them. */ export function aiBuildItemRows(job) {
|
|
62
|
+
var _job_items;
|
|
63
|
+
return ((_job_items = job.items) != null ? _job_items : []).map((row)=>{
|
|
64
|
+
var _STATES_row_status;
|
|
65
|
+
var _row_failure;
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (row.status === 'failed' && ((_row_failure = row.failure) == null ? void 0 : _row_failure.message)) parts.push(row.failure.message);
|
|
68
|
+
if (row.note) parts.push(row.note);
|
|
69
|
+
const refund = aiBuildItemRefundCopy(row);
|
|
70
|
+
if (refund) parts.push(refund);
|
|
71
|
+
return {
|
|
72
|
+
slot: row.slot,
|
|
73
|
+
label: `${aiBuildOpNoun(row.op)}: ${row.label}`,
|
|
74
|
+
state: (_STATES_row_status = STATES[row.status]) != null ? _STATES_row_status : 'waiting',
|
|
75
|
+
detail: parts.length ? parts.join(' ') : null
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** How a build came out, in one line: "4 of 5 built; 1 failed." `null` while it runs. */ export function aiBuildOutcomeLine(job) {
|
|
80
|
+
var _job_items;
|
|
81
|
+
const items = (_job_items = job.items) != null ? _job_items : [];
|
|
82
|
+
if (!items.length || job.status !== 'done' && job.status !== 'failed') return null;
|
|
83
|
+
const built = items.filter((row)=>row.status === 'succeeded' || row.status === 'degraded').length;
|
|
84
|
+
const failed = items.filter((row)=>row.status === 'failed').length;
|
|
85
|
+
const skipped = items.filter((row)=>row.status === 'skipped').length;
|
|
86
|
+
const tail = [
|
|
87
|
+
failed ? `${failed} failed` : '',
|
|
88
|
+
skipped ? `${skipped} not built` : ''
|
|
89
|
+
].filter(Boolean);
|
|
90
|
+
return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`;
|
|
91
|
+
}
|
|
92
|
+
/** Whether a finished build — or a site that built part of itself — has failed items to try again. */ export function aiBuildCanRetry(job) {
|
|
93
|
+
var _job_items;
|
|
94
|
+
// A site that built nothing starts over from its own answers instead.
|
|
95
|
+
const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done';
|
|
96
|
+
return finished && ((_job_items = job.items) != null ? _job_items : []).some((row)=>{
|
|
97
|
+
var _row_degradedBy;
|
|
98
|
+
return row.status === 'failed' || row.status === 'skipped' && !((_row_degradedBy = row.degradedBy) == null ? void 0 : _row_degradedBy.length);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* What a guided start that built part of its site says (AGL-3616), in the
|
|
103
|
+
* words every surface uses: "Built 1 of 2 pages. Home couldn’t be built —
|
|
104
|
+
* that one’s on us…". `null` when every page was built, or none was.
|
|
105
|
+
*/ export function aiSitePartialCopy(job) {
|
|
106
|
+
var _job_items, _job_items1;
|
|
107
|
+
const pages = ((_job_items = job.items) != null ? _job_items : []).filter((row)=>row.op === 'page');
|
|
108
|
+
const built = pages.filter((row)=>row.status === 'succeeded' || row.status === 'degraded');
|
|
109
|
+
const unbuilt = ((_job_items1 = job.items) != null ? _job_items1 : []).filter((row)=>row.status === 'failed' || row.status === 'skipped');
|
|
110
|
+
if (job.status !== 'done' || !unbuilt.length || !built.length) return null;
|
|
111
|
+
const names = unbuilt.map((row)=>row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`);
|
|
112
|
+
const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
113
|
+
const refunded = unbuilt.reduce((total, row)=>{
|
|
114
|
+
var _row_creditsRefunded;
|
|
115
|
+
return total + Math.max(0, Math.floor((_row_creditsRefunded = row.creditsRefunded) != null ? _row_creditsRefunded : 0));
|
|
116
|
+
}, 0);
|
|
117
|
+
const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : '';
|
|
118
|
+
// Only a failure on our side says so; a part the AI declined or an allowance
|
|
119
|
+
// stopped was charged for what it used, and the row says that.
|
|
120
|
+
const ours = unbuilt.every((row)=>{
|
|
121
|
+
var _row_failure;
|
|
122
|
+
return row.status === 'skipped' || ((_row_failure = row.failure) == null ? void 0 : _row_failure.ours) === true;
|
|
123
|
+
});
|
|
124
|
+
const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.';
|
|
125
|
+
return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=ai-build-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-progress.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 type { AiJobItemLedger, AiJobSummary } from './ai-jobs.types'\n\n/**\n * What a `build` job shows of each item (AGL-3616), wherever it is shown —\n * the AI jobs drawer, the job's own page, the chat card: the item, where it\n * stands, what it cost, and what became of its credits when it failed.\n *\n * Pure: no React, no request.\n */\n\n/** Where one item stands, as a row's icon reads it. */\nexport type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiBuildItemRow {\n slot: string\n /** \"Page: About\", \"Form: Contact\". */\n label: string\n state: AiBuildItemRowState\n /** What the person should know: the failure, the note, what was given back. */\n detail: string | null\n}\n\nconst OP_NOUNS: Readonly<Record<string, string>> = {\n page: 'Page',\n layout: 'Layout',\n form: 'Form',\n component: 'Component',\n email: 'Email design',\n template: 'Page template',\n campaign: 'Email campaign',\n workflow: 'Automation',\n}\n\n/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */\nexport function aiBuildOpNoun(op: string): string {\n const known = OP_NOUNS[op]\n if (known) return known\n const words = op.replace(/[-_]+/g, ' ').trim()\n return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item'\n}\n\n/**\n * What became of a failed item's credits (AGL-3616), in the words every\n * surface uses for a failure on our side: \"This one’s on us — you weren’t\n * charged.\" A failure that was not ours — the model declining, a site at its\n * allowance — says what it used, which was charged. `null` for an item that\n * did not fail.\n */\nexport function aiBuildItemRefundCopy(\n row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>,\n): string | null {\n if (row.status !== 'failed') return null\n if (row.failure?.ours) {\n const refunded = Math.max(0, Math.floor(row.creditsRefunded ?? 0))\n if (refunded > 0) {\n return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`\n }\n return 'This one’s on us — you weren’t charged.'\n }\n const spent = Math.max(0, Math.floor(row.creditsSpent ?? 0))\n return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null\n}\n\nconst STATES: Readonly<Record<AiJobItemLedger['status'], AiBuildItemRowState>> = {\n pending: 'waiting',\n running: 'active',\n succeeded: 'done',\n degraded: 'done',\n failed: 'failed',\n skipped: 'skipped',\n}\n\n/** One row an item, in the order the build planned them. */\nexport function aiBuildItemRows(job: Pick<AiJobSummary, 'items'>): AiBuildItemRow[] {\n return (job.items ?? []).map((row) => {\n const parts: string[] = []\n if (row.status === 'failed' && row.failure?.message) parts.push(row.failure.message)\n if (row.note) parts.push(row.note)\n const refund = aiBuildItemRefundCopy(row)\n if (refund) parts.push(refund)\n return {\n slot: row.slot,\n label: `${aiBuildOpNoun(row.op)}: ${row.label}`,\n state: STATES[row.status] ?? 'waiting',\n detail: parts.length ? parts.join(' ') : null,\n }\n })\n}\n\n/** How a build came out, in one line: \"4 of 5 built; 1 failed.\" `null` while it runs. */\nexport function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const items = job.items ?? []\n if (!items.length || (job.status !== 'done' && job.status !== 'failed')) return null\n const built = items.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const failed = items.filter((row) => row.status === 'failed').length\n const skipped = items.filter((row) => row.status === 'skipped').length\n const tail = [failed ? `${failed} failed` : '', skipped ? `${skipped} not built` : ''].filter(Boolean)\n return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`\n}\n\n/** Whether a finished build — or a site that built part of itself — has failed items to try again. */\nexport function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean {\n // A site that built nothing starts over from its own answers instead.\n const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done'\n return (\n finished &&\n (job.items ?? []).some((row) => row.status === 'failed' || (row.status === 'skipped' && !row.degradedBy?.length))\n )\n}\n\n/**\n * What a guided start that built part of its site says (AGL-3616), in the\n * words every surface uses: \"Built 1 of 2 pages. Home couldn’t be built —\n * that one’s on us…\". `null` when every page was built, or none was.\n */\nexport function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n const built = pages.filter((row) => row.status === 'succeeded' || row.status === 'degraded')\n const unbuilt = (job.items ?? []).filter((row) => row.status === 'failed' || row.status === 'skipped')\n if (job.status !== 'done' || !unbuilt.length || !built.length) return null\n const names = unbuilt.map((row) => (row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`))\n const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const refunded = unbuilt.reduce((total, row) => total + Math.max(0, Math.floor(row.creditsRefunded ?? 0)), 0)\n const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : ''\n // Only a failure on our side says so; a part the AI declined or an allowance\n // stopped was charged for what it used, and the row says that.\n const ours = unbuilt.every((row) => row.status === 'skipped' || row.failure?.ours === true)\n const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.'\n return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`\n}\n"],"names":["OP_NOUNS","page","layout","form","component","email","template","campaign","workflow","aiBuildOpNoun","op","known","words","replace","trim","toUpperCase","slice","aiBuildItemRefundCopy","row","status","failure","ours","refunded","Math","max","floor","creditsRefunded","spent","creditsSpent","STATES","pending","running","succeeded","degraded","failed","skipped","aiBuildItemRows","job","items","map","parts","message","push","note","refund","slot","label","state","detail","length","join","aiBuildOutcomeLine","built","filter","tail","Boolean","aiBuildCanRetry","finished","kind","some","degradedBy","aiSitePartialCopy","pages","unbuilt","names","toLowerCase","list","reduce","total","back","every","why"],"mappings":"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"}
|
|
@@ -44,10 +44,12 @@ import type { AiJob, AiJobOutput } from './ai-jobs.types';
|
|
|
44
44
|
* weekly digest, to the members who reach its site.
|
|
45
45
|
*/
|
|
46
46
|
/**
|
|
47
|
-
* Where an insight was asked: the console page whose Assist panel
|
|
48
|
-
* or the weekly digest,
|
|
47
|
+
* Where an insight was asked: the console page whose Assist panel — or
|
|
48
|
+
* whose "Ask AI about these numbers" card — asked it, or the weekly digest,
|
|
49
|
+
* which no person asked. A site's Marketing, Automation and Bookings pages
|
|
50
|
+
* and the workspace's own pages joined at AGL-3603.
|
|
49
51
|
*/
|
|
50
|
-
export type AiInsightSurface = 'analytics' | 'datasets' | 'crm-reports' | 'digest';
|
|
52
|
+
export type AiInsightSurface = 'analytics' | 'datasets' | 'crm-reports' | 'marketing' | 'automations' | 'bookings' | 'workspace' | 'digest';
|
|
51
53
|
export declare const AI_INSIGHT_SURFACES: readonly AiInsightSurface[];
|
|
52
54
|
/** The surfaces a person asks from. */
|
|
53
55
|
export declare const AI_INSIGHT_ASK_SURFACES: readonly AiInsightSurface[];
|
|
@@ -55,12 +57,17 @@ export declare const AI_INSIGHT_ASK_SURFACES: readonly AiInsightSurface[];
|
|
|
55
57
|
export declare function aiInsightSurface(inputs: Readonly<Record<string, unknown>> | null | undefined): AiInsightSurface | null;
|
|
56
58
|
/**
|
|
57
59
|
* The readers a surface may offer, by the first word of a reader's id. The
|
|
58
|
-
* Analytics page
|
|
59
|
-
*
|
|
60
|
-
*
|
|
60
|
+
* Analytics page — and a site's dashboard — answers about the site's traffic,
|
|
61
|
+
* what it sold, took and collected, its automations' runs and its CRM
|
|
62
|
+
* pipeline; a Data page about its datasets; CRM Reports about where people
|
|
63
|
+
* came from, what they did and the pipeline; the Marketing page about what
|
|
64
|
+
* its campaigns, overlays and tests caused and earned, beside the traffic and
|
|
65
|
+
* the forms they drove; the Automation page about its runs; the Bookings page
|
|
66
|
+
* about its bookings; and the workspace's own pages about its pipeline and
|
|
67
|
+
* its datasets.
|
|
61
68
|
*/
|
|
62
69
|
export declare const AI_INSIGHT_SURFACE_READERS: Readonly<Record<AiInsightSurface, readonly string[]>>;
|
|
63
|
-
/** Whether a surface answers about one site; the Data page may answer about the whole workspace. */
|
|
70
|
+
/** Whether a surface answers about one site; the Data page and the workspace's pages may answer about the whole workspace. */
|
|
64
71
|
export declare function aiInsightSurfaceNeedsSite(surface: AiInsightSurface): boolean;
|
|
65
72
|
/**
|
|
66
73
|
* The readers a weekly digest reads, in the order the digest names them:
|
|
@@ -157,9 +164,12 @@ export declare function aiInsightOutput(job: Pick<AiJob, '$id' | 'hostId'>, reco
|
|
|
157
164
|
/** The answer on the wire: the stored record, with nothing a client does not render. */
|
|
158
165
|
export type AiInsightAnswerWire = AiInsightRecord;
|
|
159
166
|
/**
|
|
160
|
-
* The Assist panel's insight surface for a console path, or `null` off one
|
|
161
|
-
*
|
|
162
|
-
*
|
|
167
|
+
* The Assist panel's insight surface for a console path, or `null` off one:
|
|
168
|
+
* a site's dashboard, Analytics, Data, CRM Reports, Marketing, Automation and
|
|
169
|
+
* Bookings pages; the workspace's Data page; and the workspace's sites page
|
|
170
|
+
* and CRM Reports, which read the workspace's pipeline and datasets. The
|
|
171
|
+
* organization's Marketing page reads site figures, so it is not one — it
|
|
172
|
+
* asks about the site it is showing, through its own in-page entry.
|
|
163
173
|
*/
|
|
164
174
|
export declare function aiInsightSurfaceForPath(pathname: string | null | undefined): {
|
|
165
175
|
surface: Exclude<AiInsightSurface, 'digest'>;
|
|
@@ -17,12 +17,20 @@
|
|
|
17
17
|
'analytics',
|
|
18
18
|
'datasets',
|
|
19
19
|
'crm-reports',
|
|
20
|
+
'marketing',
|
|
21
|
+
'automations',
|
|
22
|
+
'bookings',
|
|
23
|
+
'workspace',
|
|
20
24
|
'digest'
|
|
21
25
|
];
|
|
22
26
|
/** The surfaces a person asks from. */ export const AI_INSIGHT_ASK_SURFACES = [
|
|
23
27
|
'analytics',
|
|
24
28
|
'datasets',
|
|
25
|
-
'crm-reports'
|
|
29
|
+
'crm-reports',
|
|
30
|
+
'marketing',
|
|
31
|
+
'automations',
|
|
32
|
+
'bookings',
|
|
33
|
+
'workspace'
|
|
26
34
|
];
|
|
27
35
|
/** The surface an insight job's inputs name, or `null` for one no insight job is asked from. */ export function aiInsightSurface(inputs) {
|
|
28
36
|
const surface = inputs == null ? void 0 : inputs['surface'];
|
|
@@ -30,14 +38,23 @@
|
|
|
30
38
|
}
|
|
31
39
|
/**
|
|
32
40
|
* The readers a surface may offer, by the first word of a reader's id. The
|
|
33
|
-
* Analytics page
|
|
34
|
-
*
|
|
35
|
-
*
|
|
41
|
+
* Analytics page — and a site's dashboard — answers about the site's traffic,
|
|
42
|
+
* what it sold, took and collected, its automations' runs and its CRM
|
|
43
|
+
* pipeline; a Data page about its datasets; CRM Reports about where people
|
|
44
|
+
* came from, what they did and the pipeline; the Marketing page about what
|
|
45
|
+
* its campaigns, overlays and tests caused and earned, beside the traffic and
|
|
46
|
+
* the forms they drove; the Automation page about its runs; the Bookings page
|
|
47
|
+
* about its bookings; and the workspace's own pages about its pipeline and
|
|
48
|
+
* its datasets.
|
|
36
49
|
*/ export const AI_INSIGHT_SURFACE_READERS = {
|
|
50
|
+
// `funnels` (AGL-3605): a site's funnels, step by step, beside its traffic.
|
|
37
51
|
analytics: [
|
|
52
|
+
'automations',
|
|
38
53
|
'bookings',
|
|
39
54
|
'commerce',
|
|
55
|
+
'crm',
|
|
40
56
|
'forms',
|
|
57
|
+
'funnels',
|
|
41
58
|
'marketing',
|
|
42
59
|
'traffic'
|
|
43
60
|
],
|
|
@@ -46,11 +63,28 @@
|
|
|
46
63
|
],
|
|
47
64
|
'crm-reports': [
|
|
48
65
|
'bookings',
|
|
66
|
+
'commerce',
|
|
67
|
+
'crm',
|
|
68
|
+
'forms',
|
|
69
|
+
'marketing',
|
|
70
|
+
'traffic'
|
|
71
|
+
],
|
|
72
|
+
marketing: [
|
|
49
73
|
'commerce',
|
|
50
74
|
'forms',
|
|
51
75
|
'marketing',
|
|
52
76
|
'traffic'
|
|
53
77
|
],
|
|
78
|
+
automations: [
|
|
79
|
+
'automations'
|
|
80
|
+
],
|
|
81
|
+
bookings: [
|
|
82
|
+
'bookings'
|
|
83
|
+
],
|
|
84
|
+
workspace: [
|
|
85
|
+
'crm',
|
|
86
|
+
'datasets'
|
|
87
|
+
],
|
|
54
88
|
digest: [
|
|
55
89
|
'bookings',
|
|
56
90
|
'commerce',
|
|
@@ -59,8 +93,8 @@
|
|
|
59
93
|
'traffic'
|
|
60
94
|
]
|
|
61
95
|
};
|
|
62
|
-
/** Whether a surface answers about one site; the Data page may answer about the whole workspace. */ export function aiInsightSurfaceNeedsSite(surface) {
|
|
63
|
-
return surface !== 'datasets';
|
|
96
|
+
/** Whether a surface answers about one site; the Data page and the workspace's pages may answer about the whole workspace. */ export function aiInsightSurfaceNeedsSite(surface) {
|
|
97
|
+
return surface !== 'datasets' && surface !== 'workspace';
|
|
64
98
|
}
|
|
65
99
|
/**
|
|
66
100
|
* The readers a weekly digest reads, in the order the digest names them:
|
|
@@ -115,9 +149,12 @@
|
|
|
115
149
|
};
|
|
116
150
|
}
|
|
117
151
|
/**
|
|
118
|
-
* The Assist panel's insight surface for a console path, or `null` off one
|
|
119
|
-
*
|
|
120
|
-
*
|
|
152
|
+
* The Assist panel's insight surface for a console path, or `null` off one:
|
|
153
|
+
* a site's dashboard, Analytics, Data, CRM Reports, Marketing, Automation and
|
|
154
|
+
* Bookings pages; the workspace's Data page; and the workspace's sites page
|
|
155
|
+
* and CRM Reports, which read the workspace's pipeline and datasets. The
|
|
156
|
+
* organization's Marketing page reads site figures, so it is not one — it
|
|
157
|
+
* asks about the site it is showing, through its own in-page entry.
|
|
121
158
|
*/ export function aiInsightSurfaceForPath(pathname) {
|
|
122
159
|
const segments = String(pathname != null ? pathname : '').split('?')[0].split('/').filter(Boolean);
|
|
123
160
|
// `/{org}/hosts/{host}/…` on a site, `/{org}/…` on the workspace.
|
|
@@ -136,12 +173,38 @@
|
|
|
136
173
|
surface: 'crm-reports',
|
|
137
174
|
host
|
|
138
175
|
};
|
|
176
|
+
if (rest[0] === 'marketing') return {
|
|
177
|
+
surface: 'marketing',
|
|
178
|
+
host
|
|
179
|
+
};
|
|
180
|
+
if (rest[0] === 'automation') return {
|
|
181
|
+
surface: 'automations',
|
|
182
|
+
host
|
|
183
|
+
};
|
|
184
|
+
if (rest[0] === 'bookings') return {
|
|
185
|
+
surface: 'bookings',
|
|
186
|
+
host
|
|
187
|
+
};
|
|
139
188
|
return null;
|
|
140
189
|
}
|
|
190
|
+
// The site's dashboard is `/{org}/hosts/{host}`.
|
|
191
|
+
if (segments[1] === 'hosts' && segments.length === 3) return {
|
|
192
|
+
surface: 'analytics',
|
|
193
|
+
host: segments[2]
|
|
194
|
+
};
|
|
141
195
|
if (segments.length >= 2 && segments[1] === 'data') return {
|
|
142
196
|
surface: 'datasets',
|
|
143
197
|
host: null
|
|
144
198
|
};
|
|
199
|
+
// The workspace's sites page, `/{org}/hosts`, which carries its dashboard row.
|
|
200
|
+
if (segments.length === 2 && segments[1] === 'hosts') return {
|
|
201
|
+
surface: 'workspace',
|
|
202
|
+
host: null
|
|
203
|
+
};
|
|
204
|
+
if (segments[1] === 'crm' && segments[2] === 'reports') return {
|
|
205
|
+
surface: 'workspace',
|
|
206
|
+
host: null
|
|
207
|
+
};
|
|
145
208
|
return null;
|
|
146
209
|
}
|
|
147
210
|
/** The console page a table's figures come from, or `null` when the table names none. */ export function aiInsightSourceHref(table, where) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-insight.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 type { PluginFigureTable } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport type { AiJob, AiJobOutput } from './ai-jobs.types'\n\n/**\n * Insights (AGL-2915): what an `insight` job answers, in the shape the step\n * writes and the console reads. Pure data — no server import — so the answer\n * card and the digest email type what they render from the module the step\n * writes it with.\n *\n * ## A number is read, never written\n *\n * The model never runs a query and never sees a record. Code reads figures —\n * traffic, orders, bookings, forms, campaigns, datasets, experiments — through\n * fixed, typed readers, each answering with one compact table that says where\n * its figures come from. The model picks the readers and phrases what their\n * tables say, and every insight it writes cites the rows its figures come from.\n * An insight whose numbers cannot be found in the rows it cites is left out\n * (`runtime/ai-insight-check.ts`), so each one a person reads can be traced to\n * a number they can open in the console.\n *\n * ## Read-only, and kept apart from the job\n *\n * An insight job writes one document besides its own record: the answer, at\n * `orgs/{orgId}/aiInsights/{jobId}`, which no rule lets a client read. The\n * job's output names it and carries no figure, because a job document is\n * readable by every member of the workspace and the figures are not: a\n * collaborator on one site may not see another site's revenue. The answer is\n * served by `GET /api/ai/insights/{jobId}` to the member who asked and, for a\n * weekly digest, to the members who reach its site.\n */\n\n/**\n * Where an insight was asked: the console page whose Assist panel asked it,\n * or the weekly digest, which no person asked.\n */\nexport type AiInsightSurface = 'analytics' | 'datasets' | 'crm-reports' | 'digest'\n\nexport const AI_INSIGHT_SURFACES: readonly AiInsightSurface[] = [\n 'analytics',\n 'datasets',\n 'crm-reports',\n 'digest',\n]\n\n/** The surfaces a person asks from. */\nexport const AI_INSIGHT_ASK_SURFACES: readonly AiInsightSurface[] = ['analytics', 'datasets', 'crm-reports']\n\n/** The surface an insight job's inputs name, or `null` for one no insight job is asked from. */\nexport function aiInsightSurface(inputs: Readonly<Record<string, unknown>> | null | undefined): AiInsightSurface | null {\n const surface = inputs?.['surface']\n return typeof surface === 'string' && (AI_INSIGHT_SURFACES as readonly string[]).includes(surface)\n ? (surface as AiInsightSurface)\n : null\n}\n\n/**\n * The readers a surface may offer, by the first word of a reader's id. The\n * Analytics page answers about the site's traffic and what it sold, took and\n * collected; a Data page about its datasets; CRM Reports about where people\n * came from and what they did.\n */\nexport const AI_INSIGHT_SURFACE_READERS: Readonly<Record<AiInsightSurface, readonly string[]>> = {\n analytics: ['bookings', 'commerce', 'forms', 'marketing', 'traffic'],\n datasets: ['datasets'],\n 'crm-reports': ['bookings', 'commerce', 'forms', 'marketing', 'traffic'],\n digest: ['bookings', 'commerce', 'forms', 'marketing', 'traffic'],\n}\n\n/** Whether a surface answers about one site; the Data page may answer about the whole workspace. */\nexport function aiInsightSurfaceNeedsSite(surface: AiInsightSurface): boolean {\n return surface !== 'datasets'\n}\n\n/**\n * The readers a weekly digest reads, in the order the digest names them:\n * the traffic change, the top page, the days that stood out, the forms, the\n * campaigns, and what the site sold and took. Code reads these; no model\n * picks them.\n */\nexport const AI_INSIGHT_DIGEST_READERS: readonly string[] = [\n 'traffic.summary',\n 'traffic.pages',\n 'traffic.daily',\n 'forms.performance',\n 'marketing.campaigns',\n 'commerce.sales',\n 'bookings.services',\n]\n\n/** The windows, in days, an insight reads: the Traffic card's own ranges. */\nexport const AI_INSIGHT_WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** The window a question that names none reads: the Traffic card's default. */\nexport const AI_INSIGHT_DEFAULT_DAYS = 14\n\n/** The window a weekly digest reads. */\nexport const AI_INSIGHT_DIGEST_DAYS = 7\n\n/** A window an insight's inputs or a read names, held to the readers' own ranges. */\nexport function aiInsightDays(value: unknown): number {\n const days = typeof value === 'number' ? value : Number(value)\n return AI_INSIGHT_WINDOWS.includes(days) ? days : AI_INSIGHT_DEFAULT_DAYS\n}\n\n/** The most insights one answer carries: a person reads them at a glance. */\nexport const AI_INSIGHT_MAX_INSIGHTS = 5\n\n/** The longest one insight may run, in characters. */\nexport const AI_INSIGHT_MAX_CHARS = 280\n\n/** The longest the sentence about what the figures could not answer may run. */\nexport const AI_INSIGHT_GAP_MAX_CHARS = 200\n\n/** The most tables one citation list names. */\nexport const AI_INSIGHT_MAX_CITES = 3\n\n/** The most rows one citation names. */\nexport const AI_INSIGHT_MAX_CITED_ROWS = 10\n\n/** The most readers one answer reads. */\nexport const AI_INSIGHT_MAX_READS = 4\n\n/** The longest question an insight job admits. */\nexport const AI_INSIGHT_QUESTION_MAX_CHARS = 1_000\n\n/**\n * A table as an insight job keeps it: the figures a reader returned, the\n * handle the model cites them by, and the reader they came from.\n */\nexport interface AiInsightTable extends PluginFigureTable {\n /** `t1`, `t2`, …, in the order the job read them. */\n ref: string\n /** The reader's id (`commerce.sales`). */\n reader: string\n /** The window the reader was asked for, in days; 0 for a reader of current counts. */\n days: number\n /** Whether the table is about one site (its path under that site) or the workspace. */\n scope: 'site' | 'org'\n}\n\n/** The rows of one table an insight's figures come from, by index. */\nexport interface AiInsightCitation {\n table: string\n rows: number[]\n}\n\nexport interface AiInsight {\n /** One or two plain sentences. */\n text: string\n cites: AiInsightCitation[]\n}\n\n/** What the model answers with. */\nexport interface AiInsightAnswer {\n insights: AiInsight[]\n /** What the figures could not answer, in one sentence without numbers; `null` when nothing. */\n gap: string | null\n}\n\n/** The answer collection under the org: server-written, and read through the insights door alone. */\nexport const AI_INSIGHTS_COLLECTION = 'aiInsights'\n\n/** An insight job's answer, as the step stores it. */\nexport interface AiInsightRecord {\n jobId: string\n orgId: string\n hostId: string | null\n surface: AiInsightSurface\n /** The member whose question it answers; for a digest, the member it was made for. */\n createdBy: string\n /** The question, verbatim; a digest's is the digest's own title. */\n question: string\n /** The window the answer was asked about, in days. */\n days: number\n /** The insights that trace to the rows they cite; every other one was left out. */\n insights: AiInsight[]\n gap: string | null\n /** Every table the job read, so each citation opens the figures it names. */\n tables: AiInsightTable[]\n /** How many insights the answer carried that were left out as not traceable. */\n left: number\n /** The ISO week a digest covers (`2026-W38`); `null` on a question. */\n week: string | null\n createdAtMs: number\n}\n\n/** The output id an answer is named by. */\nexport const AI_INSIGHT_OUTPUT_LABEL = 'Answer'\n\n/**\n * The output an insight job records: the answer's address and how many\n * insights it holds, and no figure. The count is a statement about the\n * answer rather than about the site, so every member may read it.\n */\nexport function aiInsightOutput(\n job: Pick<AiJob, '$id' | 'hostId'>,\n record: Pick<AiInsightRecord, 'insights' | 'surface'>,\n hostSubdomain: string | null,\n): AiJobOutput {\n const count = record.insights.length\n return {\n resource: 'insight',\n id: job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain,\n label: record.surface === 'digest' ? 'Weekly insights' : AI_INSIGHT_OUTPUT_LABEL,\n note: count\n ? `${count} ${count === 1 ? 'insight' : 'insights'}, each traced to the figures it cites.`\n : 'The figures did not answer this question.',\n }\n}\n\n/** The answer on the wire: the stored record, with nothing a client does not render. */\nexport type AiInsightAnswerWire = AiInsightRecord\n\n/**\n * The Assist panel's insight surface for a console path, or `null` off one.\n * A site's Analytics, Data and CRM Reports pages, and the workspace's Data\n * page; the workspace's CRM Reports reads site figures, so it is not one.\n */\nexport function aiInsightSurfaceForPath(\n pathname: string | null | undefined,\n): { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null {\n const segments = String(pathname ?? '')\n .split('?')[0]\n .split('/')\n .filter(Boolean)\n // `/{org}/hosts/{host}/…` on a site, `/{org}/…` on the workspace.\n if (segments[1] === 'hosts' && segments.length >= 4) {\n const host = segments[2]\n const rest = segments.slice(3)\n if (rest[0] === 'analytics') return { surface: 'analytics', host }\n if (rest[0] === 'data') return { surface: 'datasets', host }\n if (rest[0] === 'crm' && rest[1] === 'reports') return { surface: 'crm-reports', host }\n return null\n }\n if (segments.length >= 2 && segments[1] === 'data') return { surface: 'datasets', host: null }\n return null\n}\n\n/** The console page a table's figures come from, or `null` when the table names none. */\nexport function aiInsightSourceHref(\n table: Pick<AiInsightTable, 'source' | 'scope'>,\n where: { orgSlug: string; host: string | null },\n): string | null {\n const path = table.source.path\n if (!path || !where.orgSlug) return null\n if (table.scope === 'org') return `/${where.orgSlug}/${path}`\n return where.host ? `/${where.orgSlug}/hosts/${where.host}/${path}` : null\n}\n\n/** How a cell reads to a person, by its column's kind; `null` is \"—\". */\nexport function aiInsightCellText(\n table: Pick<AiInsightTable, 'columns'>,\n key: string,\n value: string | number | null | undefined,\n): string {\n if (value === null || value === undefined || value === '') return '—'\n const column = table.columns.find((entry) => entry.key === key)\n if (typeof value === 'string' || !column) return String(value)\n switch (column.kind) {\n case 'money':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: column.currency ?? 'USD',\n }).format(value)\n case 'percent':\n return `${value.toLocaleString('en-US', { maximumFractionDigits: 1 })}%`\n case 'change':\n return `${value > 0 ? '+' : ''}${value.toLocaleString('en-US', { maximumFractionDigits: 1 })}%`\n case 'duration': {\n const seconds = Math.round(value)\n if (seconds < 60) return `${seconds}s`\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ${String(seconds % 60).padStart(2, '0')}s`\n return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, '0')}m`\n }\n default:\n return value.toLocaleString('en-US', { maximumFractionDigits: 2 })\n }\n}\n\n/** One cited row as a line a person reads: every column, labeled. */\nexport function aiInsightRowText(table: Pick<AiInsightTable, 'columns' | 'rows'>, index: number): string {\n const row = table.rows[index]\n if (!row) return ''\n return table.columns\n .map((column) => `${column.label}: ${aiInsightCellText(table, column.key, row[column.key])}`)\n .join(' · ')\n}\n\n/** The ISO 8601 week an instant falls in, in UTC (`2026-W38`). */\nexport function aiInsightIsoWeek(at: Date): string {\n const date = new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate()))\n // Thursday decides the week's year.\n const weekday = date.getUTCDay() || 7\n date.setUTCDate(date.getUTCDate() + 4 - weekday)\n const yearStart = Date.UTC(date.getUTCFullYear(), 0, 1)\n const week = Math.ceil(((date.getTime() - yearStart) / 86_400_000 + 1) / 7)\n return `${date.getUTCFullYear()}-W${String(week).padStart(2, '0')}`\n}\n"],"names":["AI_INSIGHT_SURFACES","AI_INSIGHT_ASK_SURFACES","aiInsightSurface","inputs","surface","includes","AI_INSIGHT_SURFACE_READERS","analytics","datasets","digest","aiInsightSurfaceNeedsSite","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_WINDOWS","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_DIGEST_DAYS","aiInsightDays","value","days","Number","AI_INSIGHT_MAX_INSIGHTS","AI_INSIGHT_MAX_CHARS","AI_INSIGHT_GAP_MAX_CHARS","AI_INSIGHT_MAX_CITES","AI_INSIGHT_MAX_CITED_ROWS","AI_INSIGHT_MAX_READS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","AI_INSIGHT_OUTPUT_LABEL","aiInsightOutput","job","record","hostSubdomain","count","insights","length","resource","id","$id","versionId","hostId","label","note","aiInsightSurfaceForPath","pathname","segments","String","split","filter","Boolean","host","rest","slice","aiInsightSourceHref","table","where","path","source","orgSlug","scope","aiInsightCellText","key","undefined","column","columns","find","entry","kind","Intl","NumberFormat","style","currency","format","toLocaleString","maximumFractionDigits","seconds","Math","round","minutes","floor","padStart","aiInsightRowText","index","row","rows","map","join","aiInsightIsoWeek","at","date","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","weekday","getUTCDay","setUTCDate","yearStart","week","ceil","getTime"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-insight.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 type { PluginFigureTable } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport type { AiJob, AiJobOutput } from './ai-jobs.types'\n\n/**\n * Insights (AGL-2915): what an `insight` job answers, in the shape the step\n * writes and the console reads. Pure data — no server import — so the answer\n * card and the digest email type what they render from the module the step\n * writes it with.\n *\n * ## A number is read, never written\n *\n * The model never runs a query and never sees a record. Code reads figures —\n * traffic, orders, bookings, forms, campaigns, datasets, experiments — through\n * fixed, typed readers, each answering with one compact table that says where\n * its figures come from. The model picks the readers and phrases what their\n * tables say, and every insight it writes cites the rows its figures come from.\n * An insight whose numbers cannot be found in the rows it cites is left out\n * (`runtime/ai-insight-check.ts`), so each one a person reads can be traced to\n * a number they can open in the console.\n *\n * ## Read-only, and kept apart from the job\n *\n * An insight job writes one document besides its own record: the answer, at\n * `orgs/{orgId}/aiInsights/{jobId}`, which no rule lets a client read. The\n * job's output names it and carries no figure, because a job document is\n * readable by every member of the workspace and the figures are not: a\n * collaborator on one site may not see another site's revenue. The answer is\n * served by `GET /api/ai/insights/{jobId}` to the member who asked and, for a\n * weekly digest, to the members who reach its site.\n */\n\n/**\n * Where an insight was asked: the console page whose Assist panel — or\n * whose \"Ask AI about these numbers\" card — asked it, or the weekly digest,\n * which no person asked. A site's Marketing, Automation and Bookings pages\n * and the workspace's own pages joined at AGL-3603.\n */\nexport type AiInsightSurface =\n | 'analytics'\n | 'datasets'\n | 'crm-reports'\n | 'marketing'\n | 'automations'\n | 'bookings'\n | 'workspace'\n | 'digest'\n\nexport const AI_INSIGHT_SURFACES: readonly AiInsightSurface[] = [\n 'analytics',\n 'datasets',\n 'crm-reports',\n 'marketing',\n 'automations',\n 'bookings',\n 'workspace',\n 'digest',\n]\n\n/** The surfaces a person asks from. */\nexport const AI_INSIGHT_ASK_SURFACES: readonly AiInsightSurface[] = [\n 'analytics',\n 'datasets',\n 'crm-reports',\n 'marketing',\n 'automations',\n 'bookings',\n 'workspace',\n]\n\n/** The surface an insight job's inputs name, or `null` for one no insight job is asked from. */\nexport function aiInsightSurface(inputs: Readonly<Record<string, unknown>> | null | undefined): AiInsightSurface | null {\n const surface = inputs?.['surface']\n return typeof surface === 'string' && (AI_INSIGHT_SURFACES as readonly string[]).includes(surface)\n ? (surface as AiInsightSurface)\n : null\n}\n\n/**\n * The readers a surface may offer, by the first word of a reader's id. The\n * Analytics page — and a site's dashboard — answers about the site's traffic,\n * what it sold, took and collected, its automations' runs and its CRM\n * pipeline; a Data page about its datasets; CRM Reports about where people\n * came from, what they did and the pipeline; the Marketing page about what\n * its campaigns, overlays and tests caused and earned, beside the traffic and\n * the forms they drove; the Automation page about its runs; the Bookings page\n * about its bookings; and the workspace's own pages about its pipeline and\n * its datasets.\n */\nexport const AI_INSIGHT_SURFACE_READERS: Readonly<Record<AiInsightSurface, readonly string[]>> = {\n // `funnels` (AGL-3605): a site's funnels, step by step, beside its traffic.\n analytics: ['automations', 'bookings', 'commerce', 'crm', 'forms', 'funnels', 'marketing', 'traffic'],\n datasets: ['datasets'],\n 'crm-reports': ['bookings', 'commerce', 'crm', 'forms', 'marketing', 'traffic'],\n marketing: ['commerce', 'forms', 'marketing', 'traffic'],\n automations: ['automations'],\n bookings: ['bookings'],\n workspace: ['crm', 'datasets'],\n digest: ['bookings', 'commerce', 'forms', 'marketing', 'traffic'],\n}\n\n/** Whether a surface answers about one site; the Data page and the workspace's pages may answer about the whole workspace. */\nexport function aiInsightSurfaceNeedsSite(surface: AiInsightSurface): boolean {\n return surface !== 'datasets' && surface !== 'workspace'\n}\n\n/**\n * The readers a weekly digest reads, in the order the digest names them:\n * the traffic change, the top page, the days that stood out, the forms, the\n * campaigns, and what the site sold and took. Code reads these; no model\n * picks them.\n */\nexport const AI_INSIGHT_DIGEST_READERS: readonly string[] = [\n 'traffic.summary',\n 'traffic.pages',\n 'traffic.daily',\n 'forms.performance',\n 'marketing.campaigns',\n 'commerce.sales',\n 'bookings.services',\n]\n\n/** The windows, in days, an insight reads: the Traffic card's own ranges. */\nexport const AI_INSIGHT_WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** The window a question that names none reads: the Traffic card's default. */\nexport const AI_INSIGHT_DEFAULT_DAYS = 14\n\n/** The window a weekly digest reads. */\nexport const AI_INSIGHT_DIGEST_DAYS = 7\n\n/** A window an insight's inputs or a read names, held to the readers' own ranges. */\nexport function aiInsightDays(value: unknown): number {\n const days = typeof value === 'number' ? value : Number(value)\n return AI_INSIGHT_WINDOWS.includes(days) ? days : AI_INSIGHT_DEFAULT_DAYS\n}\n\n/** The most insights one answer carries: a person reads them at a glance. */\nexport const AI_INSIGHT_MAX_INSIGHTS = 5\n\n/** The longest one insight may run, in characters. */\nexport const AI_INSIGHT_MAX_CHARS = 280\n\n/** The longest the sentence about what the figures could not answer may run. */\nexport const AI_INSIGHT_GAP_MAX_CHARS = 200\n\n/** The most tables one citation list names. */\nexport const AI_INSIGHT_MAX_CITES = 3\n\n/** The most rows one citation names. */\nexport const AI_INSIGHT_MAX_CITED_ROWS = 10\n\n/** The most readers one answer reads. */\nexport const AI_INSIGHT_MAX_READS = 4\n\n/** The longest question an insight job admits. */\nexport const AI_INSIGHT_QUESTION_MAX_CHARS = 1_000\n\n/**\n * A table as an insight job keeps it: the figures a reader returned, the\n * handle the model cites them by, and the reader they came from.\n */\nexport interface AiInsightTable extends PluginFigureTable {\n /** `t1`, `t2`, …, in the order the job read them. */\n ref: string\n /** The reader's id (`commerce.sales`). */\n reader: string\n /** The window the reader was asked for, in days; 0 for a reader of current counts. */\n days: number\n /** Whether the table is about one site (its path under that site) or the workspace. */\n scope: 'site' | 'org'\n}\n\n/** The rows of one table an insight's figures come from, by index. */\nexport interface AiInsightCitation {\n table: string\n rows: number[]\n}\n\nexport interface AiInsight {\n /** One or two plain sentences. */\n text: string\n cites: AiInsightCitation[]\n}\n\n/** What the model answers with. */\nexport interface AiInsightAnswer {\n insights: AiInsight[]\n /** What the figures could not answer, in one sentence without numbers; `null` when nothing. */\n gap: string | null\n}\n\n/** The answer collection under the org: server-written, and read through the insights door alone. */\nexport const AI_INSIGHTS_COLLECTION = 'aiInsights'\n\n/** An insight job's answer, as the step stores it. */\nexport interface AiInsightRecord {\n jobId: string\n orgId: string\n hostId: string | null\n surface: AiInsightSurface\n /** The member whose question it answers; for a digest, the member it was made for. */\n createdBy: string\n /** The question, verbatim; a digest's is the digest's own title. */\n question: string\n /** The window the answer was asked about, in days. */\n days: number\n /** The insights that trace to the rows they cite; every other one was left out. */\n insights: AiInsight[]\n gap: string | null\n /** Every table the job read, so each citation opens the figures it names. */\n tables: AiInsightTable[]\n /** How many insights the answer carried that were left out as not traceable. */\n left: number\n /** The ISO week a digest covers (`2026-W38`); `null` on a question. */\n week: string | null\n createdAtMs: number\n}\n\n/** The output id an answer is named by. */\nexport const AI_INSIGHT_OUTPUT_LABEL = 'Answer'\n\n/**\n * The output an insight job records: the answer's address and how many\n * insights it holds, and no figure. The count is a statement about the\n * answer rather than about the site, so every member may read it.\n */\nexport function aiInsightOutput(\n job: Pick<AiJob, '$id' | 'hostId'>,\n record: Pick<AiInsightRecord, 'insights' | 'surface'>,\n hostSubdomain: string | null,\n): AiJobOutput {\n const count = record.insights.length\n return {\n resource: 'insight',\n id: job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain,\n label: record.surface === 'digest' ? 'Weekly insights' : AI_INSIGHT_OUTPUT_LABEL,\n note: count\n ? `${count} ${count === 1 ? 'insight' : 'insights'}, each traced to the figures it cites.`\n : 'The figures did not answer this question.',\n }\n}\n\n/** The answer on the wire: the stored record, with nothing a client does not render. */\nexport type AiInsightAnswerWire = AiInsightRecord\n\n/**\n * The Assist panel's insight surface for a console path, or `null` off one:\n * a site's dashboard, Analytics, Data, CRM Reports, Marketing, Automation and\n * Bookings pages; the workspace's Data page; and the workspace's sites page\n * and CRM Reports, which read the workspace's pipeline and datasets. The\n * organization's Marketing page reads site figures, so it is not one — it\n * asks about the site it is showing, through its own in-page entry.\n */\nexport function aiInsightSurfaceForPath(\n pathname: string | null | undefined,\n): { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null {\n const segments = String(pathname ?? '')\n .split('?')[0]\n .split('/')\n .filter(Boolean)\n // `/{org}/hosts/{host}/…` on a site, `/{org}/…` on the workspace.\n if (segments[1] === 'hosts' && segments.length >= 4) {\n const host = segments[2]\n const rest = segments.slice(3)\n if (rest[0] === 'analytics') return { surface: 'analytics', host }\n if (rest[0] === 'data') return { surface: 'datasets', host }\n if (rest[0] === 'crm' && rest[1] === 'reports') return { surface: 'crm-reports', host }\n if (rest[0] === 'marketing') return { surface: 'marketing', host }\n if (rest[0] === 'automation') return { surface: 'automations', host }\n if (rest[0] === 'bookings') return { surface: 'bookings', host }\n return null\n }\n // The site's dashboard is `/{org}/hosts/{host}`.\n if (segments[1] === 'hosts' && segments.length === 3) return { surface: 'analytics', host: segments[2] }\n if (segments.length >= 2 && segments[1] === 'data') return { surface: 'datasets', host: null }\n // The workspace's sites page, `/{org}/hosts`, which carries its dashboard row.\n if (segments.length === 2 && segments[1] === 'hosts') return { surface: 'workspace', host: null }\n if (segments[1] === 'crm' && segments[2] === 'reports') return { surface: 'workspace', host: null }\n return null\n}\n\n/** The console page a table's figures come from, or `null` when the table names none. */\nexport function aiInsightSourceHref(\n table: Pick<AiInsightTable, 'source' | 'scope'>,\n where: { orgSlug: string; host: string | null },\n): string | null {\n const path = table.source.path\n if (!path || !where.orgSlug) return null\n if (table.scope === 'org') return `/${where.orgSlug}/${path}`\n return where.host ? `/${where.orgSlug}/hosts/${where.host}/${path}` : null\n}\n\n/** How a cell reads to a person, by its column's kind; `null` is \"—\". */\nexport function aiInsightCellText(\n table: Pick<AiInsightTable, 'columns'>,\n key: string,\n value: string | number | null | undefined,\n): string {\n if (value === null || value === undefined || value === '') return '—'\n const column = table.columns.find((entry) => entry.key === key)\n if (typeof value === 'string' || !column) return String(value)\n switch (column.kind) {\n case 'money':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: column.currency ?? 'USD',\n }).format(value)\n case 'percent':\n return `${value.toLocaleString('en-US', { maximumFractionDigits: 1 })}%`\n case 'change':\n return `${value > 0 ? '+' : ''}${value.toLocaleString('en-US', { maximumFractionDigits: 1 })}%`\n case 'duration': {\n const seconds = Math.round(value)\n if (seconds < 60) return `${seconds}s`\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${minutes}m ${String(seconds % 60).padStart(2, '0')}s`\n return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, '0')}m`\n }\n default:\n return value.toLocaleString('en-US', { maximumFractionDigits: 2 })\n }\n}\n\n/** One cited row as a line a person reads: every column, labeled. */\nexport function aiInsightRowText(table: Pick<AiInsightTable, 'columns' | 'rows'>, index: number): string {\n const row = table.rows[index]\n if (!row) return ''\n return table.columns\n .map((column) => `${column.label}: ${aiInsightCellText(table, column.key, row[column.key])}`)\n .join(' · ')\n}\n\n/** The ISO 8601 week an instant falls in, in UTC (`2026-W38`). */\nexport function aiInsightIsoWeek(at: Date): string {\n const date = new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate()))\n // Thursday decides the week's year.\n const weekday = date.getUTCDay() || 7\n date.setUTCDate(date.getUTCDate() + 4 - weekday)\n const yearStart = Date.UTC(date.getUTCFullYear(), 0, 1)\n const week = Math.ceil(((date.getTime() - yearStart) / 86_400_000 + 1) / 7)\n return `${date.getUTCFullYear()}-W${String(week).padStart(2, '0')}`\n}\n"],"names":["AI_INSIGHT_SURFACES","AI_INSIGHT_ASK_SURFACES","aiInsightSurface","inputs","surface","includes","AI_INSIGHT_SURFACE_READERS","analytics","datasets","marketing","automations","bookings","workspace","digest","aiInsightSurfaceNeedsSite","AI_INSIGHT_DIGEST_READERS","AI_INSIGHT_WINDOWS","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_DIGEST_DAYS","aiInsightDays","value","days","Number","AI_INSIGHT_MAX_INSIGHTS","AI_INSIGHT_MAX_CHARS","AI_INSIGHT_GAP_MAX_CHARS","AI_INSIGHT_MAX_CITES","AI_INSIGHT_MAX_CITED_ROWS","AI_INSIGHT_MAX_READS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHTS_COLLECTION","AI_INSIGHT_OUTPUT_LABEL","aiInsightOutput","job","record","hostSubdomain","count","insights","length","resource","id","$id","versionId","hostId","label","note","aiInsightSurfaceForPath","pathname","segments","String","split","filter","Boolean","host","rest","slice","aiInsightSourceHref","table","where","path","source","orgSlug","scope","aiInsightCellText","key","undefined","column","columns","find","entry","kind","Intl","NumberFormat","style","currency","format","toLocaleString","maximumFractionDigits","seconds","Math","round","minutes","floor","padStart","aiInsightRowText","index","row","rows","map","join","aiInsightIsoWeek","at","date","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","weekday","getUTCDay","setUTCDate","yearStart","week","ceil","getTime"],"mappings":"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"}
|
|
@@ -131,10 +131,12 @@ export function aiJobsActivity(jobs) {
|
|
|
131
131
|
email: 'email',
|
|
132
132
|
campaign: 'campaign',
|
|
133
133
|
workflow: 'automation',
|
|
134
|
+
logic: 'function',
|
|
134
135
|
theme: 'theme',
|
|
135
136
|
products: 'products',
|
|
136
137
|
experiment: 'experiment',
|
|
137
|
-
insight: 'answer'
|
|
138
|
+
insight: 'answer',
|
|
139
|
+
build: 'build'
|
|
138
140
|
};
|
|
139
141
|
/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */ export function aiJobKindNoun(kind) {
|
|
140
142
|
var _KIND_NOUNS_kind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review') {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n"],"names":["AI_JOB_PLAN_STEP","aiJobPhase","job","status","review","reason","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","theme","products","experiment","insight","aiJobKindNoun","kind"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-activity.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 {\n AI_JOB_PLAN_STEP,\n type AiJobKind,\n type AiJobReviewReason,\n type AiJobStatus,\n type AiJobStepStatus,\n} from './ai-jobs.types'\n\n/**\n * WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).\n *\n * A job's `status` is the machine's word: `needs_review` covers both a plan\n * waiting to be confirmed and an answer that broke a building rule twice, and\n * `queued`/`running` say nothing about whether the job is still planning or\n * already building. The console's surfaces — the AI jobs drawer's header and\n * rows, the top-bar indicator, the Assist launcher's badge, the dialog that\n * started the job, and the notification the machine raises — each tell a\n * person the same thing, so they read it from here and nowhere else. A drawer\n * that counted a refused plan as \"running\" while the chip said \"needs you\" is\n * the disagreement this exists to rule out.\n *\n * Pure, and over the fields a stored job and its wire form share, so the\n * server's notifier and the console read the same answer.\n */\n\n/** The fields a phase is read from: a stored job and its wire summary both carry them. */\nexport interface AiJobPhaseSource {\n status: AiJobStatus\n review?: { reason: AiJobReviewReason } | null\n steps: ReadonlyArray<{ name: string; status: AiJobStepStatus }>\n}\n\n/**\n * Where one job stands, as a person reads it:\n *\n * - `queued` — created, no step has started.\n * - `planning` — its plan step is the one running or next.\n * - `plan-ready` — its plan waits for the person to confirm it.\n * - `building` — a step after the plan (or a kind with no plan) is running or next.\n * - `attention` — it stopped for something only a person can change: an\n * answer that broke a building rule, a site at an allowance, the\n * workspace's credits or a switch.\n * - `done`, `failed`, `canceled` — settled.\n */\nexport type AiJobPhase =\n | 'queued'\n | 'planning'\n | 'plan-ready'\n | 'building'\n | 'attention'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport function aiJobPhase(job: AiJobPhaseSource): AiJobPhase {\n if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {\n return job.status\n }\n if (job.status === 'needs_review') {\n return job.review?.reason === 'plan' ? 'plan-ready' : 'attention'\n }\n if (job.status === 'needs_input') return 'attention'\n // Moving: the first step that has not finished says which half it is in.\n const next = job.steps.find((step) => step.status === 'running' || step.status === 'pending')\n if (next?.name === AI_JOB_PLAN_STEP) return 'planning'\n if (job.status === 'queued' && job.steps.every((step) => step.status === 'pending')) {\n return 'queued'\n }\n return 'building'\n}\n\n/** Each phase in the words a status line and a chip use. */\nexport const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>> = {\n queued: 'Queued',\n planning: 'Planning',\n 'plan-ready': 'Plan ready',\n building: 'Building',\n attention: 'Needs attention',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\n/**\n * What the top-bar indicator, the launcher badge and the drawer's header\n * count a job as, or `null` for a settled job, which none of them count.\n *\n * - `needs-you` — a plan waits to be confirmed: the one decision that, left\n * alone, leaves the person's site unbuilt.\n * - `attention` — stopped for something a person must change, which is not a\n * plan to confirm.\n * - `running` — queued, planning or building. Never a stopped job.\n */\nexport type AiJobActivityState = 'needs-you' | 'attention' | 'running'\n\n/** Loudest first: the order the indicator picks its one state by. */\nexport const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[] = [\n 'needs-you',\n 'attention',\n 'running',\n]\n\nexport function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null {\n const phase = aiJobPhase(job)\n if (phase === 'plan-ready') return 'needs-you'\n if (phase === 'attention') return 'attention'\n if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running'\n return null\n}\n\n/**\n * The statuses a job the indicator counts can have: every one that is not in\n * `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks\n * Firestore for exactly these, under the (status, createdAt) index.\n */\nexport const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n]\n\n/** How many of a list's jobs stand in each state. */\nexport type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>\n\nexport function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts {\n const counts: Record<AiJobActivityState, number> = { 'needs-you': 0, attention: 0, running: 0 }\n for (const job of jobs) {\n const state = aiJobActivityState(job)\n if (state) counts[state] += 1\n }\n return counts\n}\n\n/**\n * What the indicator and the launcher show for a list of jobs: the loudest\n * state any of them is in, how many are in it, and the job the indicator\n * opens — the oldest of them, which has waited longest. `null` when none is\n * moving or waiting, which is when both show nothing.\n */\nexport interface AiJobsActivity<T> {\n state: AiJobActivityState\n count: number\n /** The job a click targets. */\n job: T\n /** Planning rather than building, for the running chip's word. */\n planning: boolean\n}\n\nexport function aiJobsActivity<T extends AiJobPhaseSource>(\n jobs: readonly T[],\n): AiJobsActivity<T> | null {\n for (const state of AI_JOB_ACTIVITY_PRECEDENCE) {\n const matching = jobs.filter((job) => aiJobActivityState(job) === state)\n if (!matching.length) continue\n return {\n state,\n count: matching.length,\n // Lists arrive newest first.\n job: matching[matching.length - 1],\n planning:\n state === 'running' &&\n matching.every((job) => {\n const phase = aiJobPhase(job)\n return phase === 'planning' || phase === 'queued'\n }),\n }\n }\n return null\n}\n\n/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */\nexport function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string {\n const { state, count } = activity\n if (state === 'needs-you') {\n return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`\n }\n if (state === 'attention') {\n return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`\n }\n const verb = activity.planning ? 'planning' : 'working'\n return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`\n}\n\n/**\n * The launcher's accessible name: what the button opens, and what waits in it.\n * `Aglyn Assist, 1 AI job needs you`.\n */\nexport function aiJobsLauncherLabel(\n productName: string,\n activity: AiJobsActivity<unknown> | null,\n): string {\n const base = `Open ${productName} Assist`\n if (!activity) return base\n const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`\n const verb = activity.count === 1 ? 'needs' : 'need'\n if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`\n if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`\n return `${base}, ${jobs} running`\n}\n\n/** The drawer header's chips, one per state that has a job, loudest first. */\nexport function aiJobsHeaderChips(\n counts: AiJobActivityCounts,\n): Array<{ state: AiJobActivityState; label: string }> {\n return AI_JOB_ACTIVITY_PRECEDENCE.filter((state) => counts[state] > 0).map((state) => ({\n state,\n label:\n state === 'needs-you'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you`\n : state === 'attention'\n ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention`\n : `${counts[state]} running`,\n }))\n}\n\n/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */\nconst KIND_NOUNS: Partial<Record<AiJobKind, string>> = {\n site: 'site',\n page: 'page',\n template: 'page template',\n layout: 'layout',\n form: 'form',\n component: 'component',\n email: 'email',\n campaign: 'campaign',\n workflow: 'automation',\n logic: 'function',\n theme: 'theme',\n products: 'products',\n experiment: 'experiment',\n insight: 'answer',\n build: 'build',\n}\n\n/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */\nexport function aiJobKindNoun(kind: AiJobKind): string {\n return KIND_NOUNS[kind] ?? 'AI job'\n}\n"],"names":["AI_JOB_PLAN_STEP","aiJobPhase","job","status","review","reason","next","steps","find","step","name","every","AI_JOB_PHASE_LABELS","queued","planning","building","attention","done","failed","canceled","AI_JOB_ACTIVITY_PRECEDENCE","aiJobActivityState","phase","AI_JOB_ACTIVE_STATUSES","aiJobActivityCounts","jobs","counts","running","state","aiJobsActivity","matching","filter","length","count","aiJobsActivityLabel","activity","verb","aiJobsLauncherLabel","productName","base","aiJobsHeaderChips","map","label","KIND_NOUNS","site","page","template","layout","form","component","email","campaign","workflow","logic","theme","products","experiment","insight","build","aiJobKindNoun","kind"],"mappings":"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"}
|