@aglyn/plugins-ai 1.0.0-beta.225 → 1.0.0-beta.227
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 +2 -1
- package/package.json +17 -17
- package/src/lib/billing/ai-month-meter.js +5 -3
- package/src/lib/billing/ai-month-meter.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +3 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
- package/src/lib/components/ai-brief-dialog.component.js +35 -17
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +17 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-dialog.component.js +24 -2
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-events.d.ts +14 -0
- package/src/lib/components/ai-job-events.js +76 -0
- package/src/lib/components/ai-job-events.js.map +1 -0
- package/src/lib/components/ai-job-follow.component.d.ts +49 -0
- package/src/lib/components/ai-job-follow.component.js +210 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -0
- package/src/lib/components/ai-job-links.d.ts +42 -0
- package/src/lib/components/ai-job-links.js +140 -0
- package/src/lib/components/ai-job-links.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +13 -6
- package/src/lib/components/ai-job-plan.component.js +29 -3
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +24 -0
- package/src/lib/components/ai-job-requests.js +76 -0
- package/src/lib/components/ai-job-requests.js.map +1 -0
- package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
- package/src/lib/components/ai-jobs-indicator.component.js +80 -0
- package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
- package/src/lib/components/ai-jobs-store.d.ts +81 -0
- package/src/lib/components/ai-jobs-store.js +218 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -0
- package/src/lib/components/ai-page-brief-dialog.component.d.ts +3 -3
- package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.d.ts +23 -0
- package/src/lib/components/ai-site-build-page.component.js +377 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +522 -130
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
- package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
- package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
- package/src/lib/components/assist-jobs-drawer.component.js +152 -172
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +62 -6
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
- package/src/lib/components/staff-ai-credit-return.component.js +346 -0
- package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
- package/src/lib/components/staff-org-ai-card.component.js +364 -279
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
- package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.js +5 -18
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
- package/src/lib/jobs/ai-job-plan-step.js +161 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-site-step.js +34 -4
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
- package/src/lib/jobs/ai-jobs-notify.js +51 -0
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
- package/src/lib/jobs/ai-jobs.d.ts +31 -4
- package/src/lib/jobs/ai-jobs.js +196 -32
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +112 -0
- package/src/lib/model/ai-job-activity.js +144 -0
- package/src/lib/model/ai-job-activity.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
- package/src/lib/model/ai-job-failure-copy.js +78 -0
- package/src/lib/model/ai-job-failure-copy.js.map +1 -0
- package/src/lib/model/ai-job-notice.d.ts +61 -0
- package/src/lib/model/ai-job-notice.js +112 -0
- package/src/lib/model/ai-job-notice.js.map +1 -0
- package/src/lib/model/ai-jobs.types.d.ts +26 -0
- package/src/lib/model/ai-jobs.types.js +1 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +45 -0
- package/src/lib/model/ai-site-build-progress.js +76 -0
- package/src/lib/model/ai-site-build-progress.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +9 -0
- package/src/lib/model/ai-site-inventory.js +7 -2
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +93 -2
- package/src/lib/model/ai-site-job.js +88 -7
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +3 -1
- package/src/lib/model/ai-site-start.js +6 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +4 -3
- package/src/lib/model/ai-template-subjects.js +5 -4
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +3 -3
- package/src/lib/plan-entitlements.js +3 -3
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
- package/src/lib/runtime/ai-abuse-guards.js +10 -7
- package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +31 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-gate.d.ts +3 -2
- package/src/lib/runtime/ai-gate.js +3 -2
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +12 -4
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +16 -1
- package/src/lib/runtime/site-inventory.js +43 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-credits.d.ts +21 -0
- package/src/lib/server/ai-admin-credits.js +213 -0
- package/src/lib/server/ai-admin-credits.js.map +1 -0
- package/src/lib/server/ai-admin-org.js +12 -5
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +3 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +12 -1
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-allotments.js +3 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +20 -2
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +3 -2
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/billing-credits.js +4 -3
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +3 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.js +11 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
- package/src/lib/usage/assist-credit-returns-write.js +150 -0
- package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
- package/src/lib/usage/assist-credit-returns.d.ts +68 -0
- package/src/lib/usage/assist-credit-returns.js +79 -0
- package/src/lib/usage/assist-credit-returns.js.map +1 -0
- package/src/lib/usage/assist-free-taste.js +6 -3
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +100 -0
- package/src/lib/usage/assist-job-refund.js +109 -0
- package/src/lib/usage/assist-job-refund.js.map +1 -0
- package/src/lib/usage/assist-usage.js +5 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.d.ts +31 -1
- package/src/lib/usage/staff-org-ai.js +19 -2
- package/src/lib/usage/staff-org-ai.js.map +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
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 AiJobKind, type AiJobReviewReason, type AiJobStatus, type AiJobStepStatus } from './ai-jobs.types';
|
|
18
|
+
/**
|
|
19
|
+
* WHAT A JOB IS DOING, IN THE WORDS EVERY SURFACE SHOWS (AGL-3593).
|
|
20
|
+
*
|
|
21
|
+
* A job's `status` is the machine's word: `needs_review` covers both a plan
|
|
22
|
+
* waiting to be confirmed and an answer that broke a building rule twice, and
|
|
23
|
+
* `queued`/`running` say nothing about whether the job is still planning or
|
|
24
|
+
* already building. The console's surfaces — the AI jobs drawer's header and
|
|
25
|
+
* rows, the top-bar indicator, the Assist launcher's badge, the dialog that
|
|
26
|
+
* started the job, and the notification the machine raises — each tell a
|
|
27
|
+
* person the same thing, so they read it from here and nowhere else. A drawer
|
|
28
|
+
* that counted a refused plan as "running" while the chip said "needs you" is
|
|
29
|
+
* the disagreement this exists to rule out.
|
|
30
|
+
*
|
|
31
|
+
* Pure, and over the fields a stored job and its wire form share, so the
|
|
32
|
+
* server's notifier and the console read the same answer.
|
|
33
|
+
*/
|
|
34
|
+
/** The fields a phase is read from: a stored job and its wire summary both carry them. */
|
|
35
|
+
export interface AiJobPhaseSource {
|
|
36
|
+
status: AiJobStatus;
|
|
37
|
+
review?: {
|
|
38
|
+
reason: AiJobReviewReason;
|
|
39
|
+
} | null;
|
|
40
|
+
steps: ReadonlyArray<{
|
|
41
|
+
name: string;
|
|
42
|
+
status: AiJobStepStatus;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Where one job stands, as a person reads it:
|
|
47
|
+
*
|
|
48
|
+
* - `queued` — created, no step has started.
|
|
49
|
+
* - `planning` — its plan step is the one running or next.
|
|
50
|
+
* - `plan-ready` — its plan waits for the person to confirm it.
|
|
51
|
+
* - `building` — a step after the plan (or a kind with no plan) is running or next.
|
|
52
|
+
* - `attention` — it stopped for something only a person can change: an
|
|
53
|
+
* answer that broke a building rule, a site at an allowance, the
|
|
54
|
+
* workspace's credits or a switch.
|
|
55
|
+
* - `done`, `failed`, `canceled` — settled.
|
|
56
|
+
*/
|
|
57
|
+
export type AiJobPhase = 'queued' | 'planning' | 'plan-ready' | 'building' | 'attention' | 'done' | 'failed' | 'canceled';
|
|
58
|
+
export declare function aiJobPhase(job: AiJobPhaseSource): AiJobPhase;
|
|
59
|
+
/** Each phase in the words a status line and a chip use. */
|
|
60
|
+
export declare const AI_JOB_PHASE_LABELS: Readonly<Record<AiJobPhase, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* What the top-bar indicator, the launcher badge and the drawer's header
|
|
63
|
+
* count a job as, or `null` for a settled job, which none of them count.
|
|
64
|
+
*
|
|
65
|
+
* - `needs-you` — a plan waits to be confirmed: the one decision that, left
|
|
66
|
+
* alone, leaves the person's site unbuilt.
|
|
67
|
+
* - `attention` — stopped for something a person must change, which is not a
|
|
68
|
+
* plan to confirm.
|
|
69
|
+
* - `running` — queued, planning or building. Never a stopped job.
|
|
70
|
+
*/
|
|
71
|
+
export type AiJobActivityState = 'needs-you' | 'attention' | 'running';
|
|
72
|
+
/** Loudest first: the order the indicator picks its one state by. */
|
|
73
|
+
export declare const AI_JOB_ACTIVITY_PRECEDENCE: readonly AiJobActivityState[];
|
|
74
|
+
export declare function aiJobActivityState(job: AiJobPhaseSource): AiJobActivityState | null;
|
|
75
|
+
/**
|
|
76
|
+
* The statuses a job the indicator counts can have: every one that is not in
|
|
77
|
+
* `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks
|
|
78
|
+
* Firestore for exactly these, under the (status, createdAt) index.
|
|
79
|
+
*/
|
|
80
|
+
export declare const AI_JOB_ACTIVE_STATUSES: readonly AiJobStatus[];
|
|
81
|
+
/** How many of a list's jobs stand in each state. */
|
|
82
|
+
export type AiJobActivityCounts = Readonly<Record<AiJobActivityState, number>>;
|
|
83
|
+
export declare function aiJobActivityCounts(jobs: readonly AiJobPhaseSource[]): AiJobActivityCounts;
|
|
84
|
+
/**
|
|
85
|
+
* What the indicator and the launcher show for a list of jobs: the loudest
|
|
86
|
+
* state any of them is in, how many are in it, and the job the indicator
|
|
87
|
+
* opens — the oldest of them, which has waited longest. `null` when none is
|
|
88
|
+
* moving or waiting, which is when both show nothing.
|
|
89
|
+
*/
|
|
90
|
+
export interface AiJobsActivity<T> {
|
|
91
|
+
state: AiJobActivityState;
|
|
92
|
+
count: number;
|
|
93
|
+
/** The job a click targets. */
|
|
94
|
+
job: T;
|
|
95
|
+
/** Planning rather than building, for the running chip's word. */
|
|
96
|
+
planning: boolean;
|
|
97
|
+
}
|
|
98
|
+
export declare function aiJobsActivity<T extends AiJobPhaseSource>(jobs: readonly T[]): AiJobsActivity<T> | null;
|
|
99
|
+
/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */
|
|
100
|
+
export declare function aiJobsActivityLabel(activity: AiJobsActivity<unknown>): string;
|
|
101
|
+
/**
|
|
102
|
+
* The launcher's accessible name: what the button opens, and what waits in it.
|
|
103
|
+
* `Aglyn Assist, 1 AI job needs you`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function aiJobsLauncherLabel(productName: string, activity: AiJobsActivity<unknown> | null): string;
|
|
106
|
+
/** The drawer header's chips, one per state that has a job, loudest first. */
|
|
107
|
+
export declare function aiJobsHeaderChips(counts: AiJobActivityCounts): Array<{
|
|
108
|
+
state: AiJobActivityState;
|
|
109
|
+
label: string;
|
|
110
|
+
}>;
|
|
111
|
+
/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */
|
|
112
|
+
export declare function aiJobKindNoun(kind: AiJobKind): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { AI_JOB_PLAN_STEP } from "./ai-jobs.types.js";
|
|
17
|
+
export function aiJobPhase(job) {
|
|
18
|
+
if (job.status === 'done' || job.status === 'failed' || job.status === 'canceled') {
|
|
19
|
+
return job.status;
|
|
20
|
+
}
|
|
21
|
+
if (job.status === 'needs_review') {
|
|
22
|
+
var _job_review;
|
|
23
|
+
return ((_job_review = job.review) == null ? void 0 : _job_review.reason) === 'plan' ? 'plan-ready' : 'attention';
|
|
24
|
+
}
|
|
25
|
+
if (job.status === 'needs_input') return 'attention';
|
|
26
|
+
// Moving: the first step that has not finished says which half it is in.
|
|
27
|
+
const next = job.steps.find((step)=>step.status === 'running' || step.status === 'pending');
|
|
28
|
+
if ((next == null ? void 0 : next.name) === AI_JOB_PLAN_STEP) return 'planning';
|
|
29
|
+
if (job.status === 'queued' && job.steps.every((step)=>step.status === 'pending')) {
|
|
30
|
+
return 'queued';
|
|
31
|
+
}
|
|
32
|
+
return 'building';
|
|
33
|
+
}
|
|
34
|
+
/** Each phase in the words a status line and a chip use. */ export const AI_JOB_PHASE_LABELS = {
|
|
35
|
+
queued: 'Queued',
|
|
36
|
+
planning: 'Planning',
|
|
37
|
+
'plan-ready': 'Plan ready',
|
|
38
|
+
building: 'Building',
|
|
39
|
+
attention: 'Needs attention',
|
|
40
|
+
done: 'Done',
|
|
41
|
+
failed: 'Failed',
|
|
42
|
+
canceled: 'Canceled'
|
|
43
|
+
};
|
|
44
|
+
/** Loudest first: the order the indicator picks its one state by. */ export const AI_JOB_ACTIVITY_PRECEDENCE = [
|
|
45
|
+
'needs-you',
|
|
46
|
+
'attention',
|
|
47
|
+
'running'
|
|
48
|
+
];
|
|
49
|
+
export function aiJobActivityState(job) {
|
|
50
|
+
const phase = aiJobPhase(job);
|
|
51
|
+
if (phase === 'plan-ready') return 'needs-you';
|
|
52
|
+
if (phase === 'attention') return 'attention';
|
|
53
|
+
if (phase === 'queued' || phase === 'planning' || phase === 'building') return 'running';
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The statuses a job the indicator counts can have: every one that is not in
|
|
58
|
+
* `AI_JOB_TERMINAL_STATUSES`. The jobs route's `status=active` list asks
|
|
59
|
+
* Firestore for exactly these, under the (status, createdAt) index.
|
|
60
|
+
*/ export const AI_JOB_ACTIVE_STATUSES = [
|
|
61
|
+
'queued',
|
|
62
|
+
'running',
|
|
63
|
+
'needs_input',
|
|
64
|
+
'needs_review'
|
|
65
|
+
];
|
|
66
|
+
export function aiJobActivityCounts(jobs) {
|
|
67
|
+
const counts = {
|
|
68
|
+
'needs-you': 0,
|
|
69
|
+
attention: 0,
|
|
70
|
+
running: 0
|
|
71
|
+
};
|
|
72
|
+
for (const job of jobs){
|
|
73
|
+
const state = aiJobActivityState(job);
|
|
74
|
+
if (state) counts[state] += 1;
|
|
75
|
+
}
|
|
76
|
+
return counts;
|
|
77
|
+
}
|
|
78
|
+
export function aiJobsActivity(jobs) {
|
|
79
|
+
for (const state of AI_JOB_ACTIVITY_PRECEDENCE){
|
|
80
|
+
const matching = jobs.filter((job)=>aiJobActivityState(job) === state);
|
|
81
|
+
if (!matching.length) continue;
|
|
82
|
+
return {
|
|
83
|
+
state,
|
|
84
|
+
count: matching.length,
|
|
85
|
+
// Lists arrive newest first.
|
|
86
|
+
job: matching[matching.length - 1],
|
|
87
|
+
planning: state === 'running' && matching.every((job)=>{
|
|
88
|
+
const phase = aiJobPhase(job);
|
|
89
|
+
return phase === 'planning' || phase === 'queued';
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/** The chip's words: `AI · plan ready`, `AI · 2 need attention`, `AI · planning`. */ export function aiJobsActivityLabel(activity) {
|
|
96
|
+
const { state, count } = activity;
|
|
97
|
+
if (state === 'needs-you') {
|
|
98
|
+
return count === 1 ? 'AI · plan ready' : `AI · ${count} plans ready`;
|
|
99
|
+
}
|
|
100
|
+
if (state === 'attention') {
|
|
101
|
+
return count === 1 ? 'AI · needs attention' : `AI · ${count} need attention`;
|
|
102
|
+
}
|
|
103
|
+
const verb = activity.planning ? 'planning' : 'working';
|
|
104
|
+
return count === 1 ? `AI · ${verb}` : `AI · ${count} ${verb}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The launcher's accessible name: what the button opens, and what waits in it.
|
|
108
|
+
* `Aglyn Assist, 1 AI job needs you`.
|
|
109
|
+
*/ export function aiJobsLauncherLabel(productName, activity) {
|
|
110
|
+
const base = `Open ${productName} Assist`;
|
|
111
|
+
if (!activity) return base;
|
|
112
|
+
const jobs = activity.count === 1 ? '1 AI job' : `${activity.count} AI jobs`;
|
|
113
|
+
const verb = activity.count === 1 ? 'needs' : 'need';
|
|
114
|
+
if (activity.state === 'needs-you') return `${base}, ${jobs} ${verb} you`;
|
|
115
|
+
if (activity.state === 'attention') return `${base}, ${jobs} ${verb} attention`;
|
|
116
|
+
return `${base}, ${jobs} running`;
|
|
117
|
+
}
|
|
118
|
+
/** The drawer header's chips, one per state that has a job, loudest first. */ export function aiJobsHeaderChips(counts) {
|
|
119
|
+
return AI_JOB_ACTIVITY_PRECEDENCE.filter((state)=>counts[state] > 0).map((state)=>({
|
|
120
|
+
state,
|
|
121
|
+
label: state === 'needs-you' ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} you` : state === 'attention' ? `${counts[state]} ${counts[state] === 1 ? 'needs' : 'need'} attention` : `${counts[state]} running`
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/** What a job makes, as a sentence's subject: `Your site`, `Your page`. */ const KIND_NOUNS = {
|
|
125
|
+
site: 'site',
|
|
126
|
+
page: 'page',
|
|
127
|
+
template: 'page template',
|
|
128
|
+
layout: 'layout',
|
|
129
|
+
form: 'form',
|
|
130
|
+
component: 'component',
|
|
131
|
+
email: 'email',
|
|
132
|
+
campaign: 'campaign',
|
|
133
|
+
workflow: 'automation',
|
|
134
|
+
theme: 'theme',
|
|
135
|
+
products: 'products',
|
|
136
|
+
experiment: 'experiment',
|
|
137
|
+
insight: 'answer'
|
|
138
|
+
};
|
|
139
|
+
/** The noun for what a job of this kind makes, or `AI job` for a kind with none. */ export function aiJobKindNoun(kind) {
|
|
140
|
+
var _KIND_NOUNS_kind;
|
|
141
|
+
return (_KIND_NOUNS_kind = KIND_NOUNS[kind]) != null ? _KIND_NOUNS_kind : 'AI job';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//# sourceMappingURL=ai-job-activity.js.map
|
|
@@ -0,0 +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":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,gBAAgB,QAKX,qBAAiB;AAgDxB,OAAO,SAASC,WAAWC,GAAqB;IAC9C,IAAIA,IAAIC,MAAM,KAAK,UAAUD,IAAIC,MAAM,KAAK,YAAYD,IAAIC,MAAM,KAAK,YAAY;QACjF,OAAOD,IAAIC,MAAM;IACnB;IACA,IAAID,IAAIC,MAAM,KAAK,gBAAgB;YAC1BD;QAAP,OAAOA,EAAAA,cAAAA,IAAIE,MAAM,qBAAVF,YAAYG,MAAM,MAAK,SAAS,eAAe;IACxD;IACA,IAAIH,IAAIC,MAAM,KAAK,eAAe,OAAO;IACzC,yEAAyE;IACzE,MAAMG,OAAOJ,IAAIK,KAAK,CAACC,IAAI,CAAC,CAACC,OAASA,KAAKN,MAAM,KAAK,aAAaM,KAAKN,MAAM,KAAK;IACnF,IAAIG,CAAAA,wBAAAA,KAAMI,IAAI,MAAKV,kBAAkB,OAAO;IAC5C,IAAIE,IAAIC,MAAM,KAAK,YAAYD,IAAIK,KAAK,CAACI,KAAK,CAAC,CAACF,OAASA,KAAKN,MAAM,KAAK,YAAY;QACnF,OAAO;IACT;IACA,OAAO;AACT;AAEA,0DAA0D,GAC1D,OAAO,MAAMS,sBAA4D;IACvEC,QAAQ;IACRC,UAAU;IACV,cAAc;IACdC,UAAU;IACVC,WAAW;IACXC,MAAM;IACNC,QAAQ;IACRC,UAAU;AACZ,EAAC;AAcD,mEAAmE,GACnE,OAAO,MAAMC,6BAA4D;IACvE;IACA;IACA;CACD,CAAA;AAED,OAAO,SAASC,mBAAmBnB,GAAqB;IACtD,MAAMoB,QAAQrB,WAAWC;IACzB,IAAIoB,UAAU,cAAc,OAAO;IACnC,IAAIA,UAAU,aAAa,OAAO;IAClC,IAAIA,UAAU,YAAYA,UAAU,cAAcA,UAAU,YAAY,OAAO;IAC/E,OAAO;AACT;AAEA;;;;CAIC,GACD,OAAO,MAAMC,yBAAiD;IAC5D;IACA;IACA;IACA;CACD,CAAA;AAKD,OAAO,SAASC,oBAAoBC,IAAiC;IACnE,MAAMC,SAA6C;QAAE,aAAa;QAAGV,WAAW;QAAGW,SAAS;IAAE;IAC9F,KAAK,MAAMzB,OAAOuB,KAAM;QACtB,MAAMG,QAAQP,mBAAmBnB;QACjC,IAAI0B,OAAOF,MAAM,CAACE,MAAM,IAAI;IAC9B;IACA,OAAOF;AACT;AAiBA,OAAO,SAASG,eACdJ,IAAkB;IAElB,KAAK,MAAMG,SAASR,2BAA4B;QAC9C,MAAMU,WAAWL,KAAKM,MAAM,CAAC,CAAC7B,MAAQmB,mBAAmBnB,SAAS0B;QAClE,IAAI,CAACE,SAASE,MAAM,EAAE;QACtB,OAAO;YACLJ;YACAK,OAAOH,SAASE,MAAM;YACtB,6BAA6B;YAC7B9B,KAAK4B,QAAQ,CAACA,SAASE,MAAM,GAAG,EAAE;YAClClB,UACEc,UAAU,aACVE,SAASnB,KAAK,CAAC,CAACT;gBACd,MAAMoB,QAAQrB,WAAWC;gBACzB,OAAOoB,UAAU,cAAcA,UAAU;YAC3C;QACJ;IACF;IACA,OAAO;AACT;AAEA,mFAAmF,GACnF,OAAO,SAASY,oBAAoBC,QAAiC;IACnE,MAAM,EAAEP,KAAK,EAAEK,KAAK,EAAE,GAAGE;IACzB,IAAIP,UAAU,aAAa;QACzB,OAAOK,UAAU,IAAI,oBAAoB,CAAC,KAAK,EAAEA,MAAM,YAAY,CAAC;IACtE;IACA,IAAIL,UAAU,aAAa;QACzB,OAAOK,UAAU,IAAI,yBAAyB,CAAC,KAAK,EAAEA,MAAM,eAAe,CAAC;IAC9E;IACA,MAAMG,OAAOD,SAASrB,QAAQ,GAAG,aAAa;IAC9C,OAAOmB,UAAU,IAAI,CAAC,KAAK,EAAEG,MAAM,GAAG,CAAC,KAAK,EAAEH,MAAM,CAAC,EAAEG,MAAM;AAC/D;AAEA;;;CAGC,GACD,OAAO,SAASC,oBACdC,WAAmB,EACnBH,QAAwC;IAExC,MAAMI,OAAO,CAAC,KAAK,EAAED,YAAY,OAAO,CAAC;IACzC,IAAI,CAACH,UAAU,OAAOI;IACtB,MAAMd,OAAOU,SAASF,KAAK,KAAK,IAAI,aAAa,GAAGE,SAASF,KAAK,CAAC,QAAQ,CAAC;IAC5E,MAAMG,OAAOD,SAASF,KAAK,KAAK,IAAI,UAAU;IAC9C,IAAIE,SAASP,KAAK,KAAK,aAAa,OAAO,GAAGW,KAAK,EAAE,EAAEd,KAAK,CAAC,EAAEW,KAAK,IAAI,CAAC;IACzE,IAAID,SAASP,KAAK,KAAK,aAAa,OAAO,GAAGW,KAAK,EAAE,EAAEd,KAAK,CAAC,EAAEW,KAAK,UAAU,CAAC;IAC/E,OAAO,GAAGG,KAAK,EAAE,EAAEd,KAAK,QAAQ,CAAC;AACnC;AAEA,4EAA4E,GAC5E,OAAO,SAASe,kBACdd,MAA2B;IAE3B,OAAON,2BAA2BW,MAAM,CAAC,CAACH,QAAUF,MAAM,CAACE,MAAM,GAAG,GAAGa,GAAG,CAAC,CAACb,QAAW,CAAA;YACrFA;YACAc,OACEd,UAAU,cACN,GAAGF,MAAM,CAACE,MAAM,CAAC,CAAC,EAAEF,MAAM,CAACE,MAAM,KAAK,IAAI,UAAU,OAAO,IAAI,CAAC,GAChEA,UAAU,cACR,GAAGF,MAAM,CAACE,MAAM,CAAC,CAAC,EAAEF,MAAM,CAACE,MAAM,KAAK,IAAI,UAAU,OAAO,UAAU,CAAC,GACtE,GAAGF,MAAM,CAACE,MAAM,CAAC,QAAQ,CAAC;QACpC,CAAA;AACF;AAEA,yEAAyE,GACzE,MAAMe,aAAiD;IACrDC,MAAM;IACNC,MAAM;IACNC,UAAU;IACVC,QAAQ;IACRC,MAAM;IACNC,WAAW;IACXC,OAAO;IACPC,UAAU;IACVC,UAAU;IACVC,OAAO;IACPC,UAAU;IACVC,YAAY;IACZC,SAAS;AACX;AAEA,kFAAkF,GAClF,OAAO,SAASC,cAAcC,IAAe;QACpCf;IAAP,QAAOA,mBAAAA,UAAU,CAACe,KAAK,YAAhBf,mBAAoB;AAC7B"}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { AiJobKind, AiJobSummary } from './ai-jobs.types';
|
|
18
|
+
/**
|
|
19
|
+
* What a member reads when a plan could not be made (AGL-3594): one plain
|
|
20
|
+
* sentence and one thing to do, never a rule number or a validator's words.
|
|
21
|
+
*
|
|
22
|
+
* The plan step's own checks refuse an answer in the doctrine's vocabulary —
|
|
23
|
+
* "Rule 10 (Navigation and SEO travel with a page): A page reuses an
|
|
24
|
+
* address…" — which is what the one re-ask is told and what staff read to
|
|
25
|
+
* tell a rule that should bend from a model that ignored it. Shown to the
|
|
26
|
+
* person who asked for a site, the same words read as the product breaking.
|
|
27
|
+
* So the review a refused plan stops for carries this sentence as its
|
|
28
|
+
* `message`, and the doctrine's sentence and findings ride beside it as
|
|
29
|
+
* `detail` and `findings`, which the job document keeps and only staff are
|
|
30
|
+
* shown.
|
|
31
|
+
*
|
|
32
|
+
* Pure: no React, no Admin SDK. The drawer's plan card and the plan step read
|
|
33
|
+
* the same words.
|
|
34
|
+
*/
|
|
35
|
+
/** The violation code the Free wall refuses a plan with. */
|
|
36
|
+
export declare const AI_PLAN_FREE_WALL_CODE = "plan-over-free-wall";
|
|
37
|
+
export interface AiPlanFailureCopyInput {
|
|
38
|
+
/** The job kind whose plan was refused. */
|
|
39
|
+
kind: AiJobKind | string;
|
|
40
|
+
/** The codes of the violations its last answer broke. */
|
|
41
|
+
codes: readonly string[];
|
|
42
|
+
/** The step's credits went back to the Free allowance (AGL-3594). */
|
|
43
|
+
refunded: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The sentence a refused plan stops its job with. Over the Free wall, the way
|
|
47
|
+
* out is fewer pages or a paid plan; anything else is ours to have got wrong,
|
|
48
|
+
* and the way out is trying again — said to cost nothing only when it did.
|
|
49
|
+
*/
|
|
50
|
+
export declare function aiPlanFailureCopy(input: AiPlanFailureCopyInput): string;
|
|
51
|
+
/**
|
|
52
|
+
* Why trying a refused plan again cannot work yet, or `null` when it can: the
|
|
53
|
+
* Free allowance left this month is less than a plan of this kind can cost at
|
|
54
|
+
* its worst. Said with the figures, and with the two ways out.
|
|
55
|
+
*/
|
|
56
|
+
export declare function aiPlanRetryRefusal(input: {
|
|
57
|
+
creditsLeft: number;
|
|
58
|
+
planCredits: number;
|
|
59
|
+
}): string | null;
|
|
60
|
+
/** The guided start's way back to the starter (AGL-3594). */
|
|
61
|
+
export declare const AI_SITE_STARTER_FALLBACK_LABEL = "Use the starter site instead";
|
|
62
|
+
/**
|
|
63
|
+
* Whether a job is a guided start that did not work out and can still be
|
|
64
|
+
* traded for the starter (AGL-3594): a site job that failed, was canceled, or
|
|
65
|
+
* stopped for a person on a refused step — never one waiting on its plan,
|
|
66
|
+
* which is a plan to confirm — and that has built nothing, since the starter
|
|
67
|
+
* is written only to a site with no page or layout.
|
|
68
|
+
*/
|
|
69
|
+
export declare function aiSiteStarterFallbackOffered(job: Pick<AiJobSummary, 'kind' | 'status' | 'hostId' | 'outputs' | 'review'>): boolean;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* What a member reads when a plan could not be made (AGL-3594): one plain
|
|
18
|
+
* sentence and one thing to do, never a rule number or a validator's words.
|
|
19
|
+
*
|
|
20
|
+
* The plan step's own checks refuse an answer in the doctrine's vocabulary —
|
|
21
|
+
* "Rule 10 (Navigation and SEO travel with a page): A page reuses an
|
|
22
|
+
* address…" — which is what the one re-ask is told and what staff read to
|
|
23
|
+
* tell a rule that should bend from a model that ignored it. Shown to the
|
|
24
|
+
* person who asked for a site, the same words read as the product breaking.
|
|
25
|
+
* So the review a refused plan stops for carries this sentence as its
|
|
26
|
+
* `message`, and the doctrine's sentence and findings ride beside it as
|
|
27
|
+
* `detail` and `findings`, which the job document keeps and only staff are
|
|
28
|
+
* shown.
|
|
29
|
+
*
|
|
30
|
+
* Pure: no React, no Admin SDK. The drawer's plan card and the plan step read
|
|
31
|
+
* the same words.
|
|
32
|
+
*/ /** The violation code the Free wall refuses a plan with. */ export const AI_PLAN_FREE_WALL_CODE = 'plan-over-free-wall';
|
|
33
|
+
/** What the plan was for, as the sentence names it. */ function planningWhat(kind) {
|
|
34
|
+
switch(kind){
|
|
35
|
+
case 'site':
|
|
36
|
+
return 'your site';
|
|
37
|
+
case 'page':
|
|
38
|
+
return 'this page';
|
|
39
|
+
default:
|
|
40
|
+
return 'this';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The sentence a refused plan stops its job with. Over the Free wall, the way
|
|
45
|
+
* out is fewer pages or a paid plan; anything else is ours to have got wrong,
|
|
46
|
+
* and the way out is trying again — said to cost nothing only when it did.
|
|
47
|
+
*/ export function aiPlanFailureCopy(input) {
|
|
48
|
+
const spent = input.refunded ? ', and it did not use any of your AI credits' : '';
|
|
49
|
+
if (input.codes.includes(AI_PLAN_FREE_WALL_CODE)) {
|
|
50
|
+
return `We couldn't fit this plan into your Free AI credits${spent}. Try fewer pages, or upgrade for more.`;
|
|
51
|
+
}
|
|
52
|
+
return `Something went wrong planning ${planningWhat(input.kind)}${spent}. Try again.`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Why trying a refused plan again cannot work yet, or `null` when it can: the
|
|
56
|
+
* Free allowance left this month is less than a plan of this kind can cost at
|
|
57
|
+
* its worst. Said with the figures, and with the two ways out.
|
|
58
|
+
*/ export function aiPlanRetryRefusal(input) {
|
|
59
|
+
const left = Math.max(0, Math.floor(input.creditsLeft));
|
|
60
|
+
if (left >= input.planCredits) return null;
|
|
61
|
+
return `You have ${left} AI ${left === 1 ? 'credit' : 'credits'} left this month, and a plan needs up to ${input.planCredits}. Your credits refresh next month, or upgrade for more.`;
|
|
62
|
+
}
|
|
63
|
+
/** The guided start's way back to the starter (AGL-3594). */ export const AI_SITE_STARTER_FALLBACK_LABEL = 'Use the starter site instead';
|
|
64
|
+
/**
|
|
65
|
+
* Whether a job is a guided start that did not work out and can still be
|
|
66
|
+
* traded for the starter (AGL-3594): a site job that failed, was canceled, or
|
|
67
|
+
* stopped for a person on a refused step — never one waiting on its plan,
|
|
68
|
+
* which is a plan to confirm — and that has built nothing, since the starter
|
|
69
|
+
* is written only to a site with no page or layout.
|
|
70
|
+
*/ export function aiSiteStarterFallbackOffered(job) {
|
|
71
|
+
var _job_review;
|
|
72
|
+
if (job.kind !== 'site' || !job.hostId) return false;
|
|
73
|
+
if (job.outputs.length > 0) return false;
|
|
74
|
+
if (job.status === 'failed' || job.status === 'canceled') return true;
|
|
75
|
+
return job.status === 'needs_review' && ((_job_review = job.review) == null ? void 0 : _job_review.reason) === 'doctrine';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=ai-job-failure-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-failure-copy.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 { AiJobKind, AiJobSummary } from './ai-jobs.types'\n\n/**\n * What a member reads when a plan could not be made (AGL-3594): one plain\n * sentence and one thing to do, never a rule number or a validator's words.\n *\n * The plan step's own checks refuse an answer in the doctrine's vocabulary —\n * \"Rule 10 (Navigation and SEO travel with a page): A page reuses an\n * address…\" — which is what the one re-ask is told and what staff read to\n * tell a rule that should bend from a model that ignored it. Shown to the\n * person who asked for a site, the same words read as the product breaking.\n * So the review a refused plan stops for carries this sentence as its\n * `message`, and the doctrine's sentence and findings ride beside it as\n * `detail` and `findings`, which the job document keeps and only staff are\n * shown.\n *\n * Pure: no React, no Admin SDK. The drawer's plan card and the plan step read\n * the same words.\n */\n\n/** The violation code the Free wall refuses a plan with. */\nexport const AI_PLAN_FREE_WALL_CODE = 'plan-over-free-wall'\n\n/** What the plan was for, as the sentence names it. */\nfunction planningWhat(kind: AiJobKind | string): string {\n switch (kind) {\n case 'site':\n return 'your site'\n case 'page':\n return 'this page'\n default:\n return 'this'\n }\n}\n\nexport interface AiPlanFailureCopyInput {\n /** The job kind whose plan was refused. */\n kind: AiJobKind | string\n /** The codes of the violations its last answer broke. */\n codes: readonly string[]\n /** The step's credits went back to the Free allowance (AGL-3594). */\n refunded: boolean\n}\n\n/**\n * The sentence a refused plan stops its job with. Over the Free wall, the way\n * out is fewer pages or a paid plan; anything else is ours to have got wrong,\n * and the way out is trying again — said to cost nothing only when it did.\n */\nexport function aiPlanFailureCopy(input: AiPlanFailureCopyInput): string {\n const spent = input.refunded ? ', and it did not use any of your AI credits' : ''\n if (input.codes.includes(AI_PLAN_FREE_WALL_CODE)) {\n return `We couldn't fit this plan into your Free AI credits${spent}. Try fewer pages, or upgrade for more.`\n }\n return `Something went wrong planning ${planningWhat(input.kind)}${spent}. Try again.`\n}\n\n/**\n * Why trying a refused plan again cannot work yet, or `null` when it can: the\n * Free allowance left this month is less than a plan of this kind can cost at\n * its worst. Said with the figures, and with the two ways out.\n */\nexport function aiPlanRetryRefusal(input: { creditsLeft: number; planCredits: number }): string | null {\n const left = Math.max(0, Math.floor(input.creditsLeft))\n if (left >= input.planCredits) return null\n return `You have ${left} AI ${left === 1 ? 'credit' : 'credits'} left this month, and a plan needs up to ${input.planCredits}. Your credits refresh next month, or upgrade for more.`\n}\n\n/** The guided start's way back to the starter (AGL-3594). */\nexport const AI_SITE_STARTER_FALLBACK_LABEL = 'Use the starter site instead'\n\n/**\n * Whether a job is a guided start that did not work out and can still be\n * traded for the starter (AGL-3594): a site job that failed, was canceled, or\n * stopped for a person on a refused step — never one waiting on its plan,\n * which is a plan to confirm — and that has built nothing, since the starter\n * is written only to a site with no page or layout.\n */\nexport function aiSiteStarterFallbackOffered(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'hostId' | 'outputs' | 'review'>,\n): boolean {\n if (job.kind !== 'site' || !job.hostId) return false\n if (job.outputs.length > 0) return false\n if (job.status === 'failed' || job.status === 'canceled') return true\n return job.status === 'needs_review' && job.review?.reason === 'doctrine'\n}\n"],"names":["AI_PLAN_FREE_WALL_CODE","planningWhat","kind","aiPlanFailureCopy","input","spent","refunded","codes","includes","aiPlanRetryRefusal","left","Math","max","floor","creditsLeft","planCredits","AI_SITE_STARTER_FALLBACK_LABEL","aiSiteStarterFallbackOffered","job","hostId","outputs","length","status","review","reason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;CAgBC,GAED,0DAA0D,GAC1D,OAAO,MAAMA,yBAAyB,sBAAqB;AAE3D,qDAAqD,GACrD,SAASC,aAAaC,IAAwB;IAC5C,OAAQA;QACN,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT;YACE,OAAO;IACX;AACF;AAWA;;;;CAIC,GACD,OAAO,SAASC,kBAAkBC,KAA6B;IAC7D,MAAMC,QAAQD,MAAME,QAAQ,GAAG,gDAAgD;IAC/E,IAAIF,MAAMG,KAAK,CAACC,QAAQ,CAACR,yBAAyB;QAChD,OAAO,CAAC,mDAAmD,EAAEK,MAAM,uCAAuC,CAAC;IAC7G;IACA,OAAO,CAAC,8BAA8B,EAAEJ,aAAaG,MAAMF,IAAI,IAAIG,MAAM,YAAY,CAAC;AACxF;AAEA;;;;CAIC,GACD,OAAO,SAASI,mBAAmBL,KAAmD;IACpF,MAAMM,OAAOC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACT,MAAMU,WAAW;IACrD,IAAIJ,QAAQN,MAAMW,WAAW,EAAE,OAAO;IACtC,OAAO,CAAC,SAAS,EAAEL,KAAK,IAAI,EAAEA,SAAS,IAAI,WAAW,UAAU,yCAAyC,EAAEN,MAAMW,WAAW,CAAC,uDAAuD,CAAC;AACvL;AAEA,2DAA2D,GAC3D,OAAO,MAAMC,iCAAiC,+BAA8B;AAE5E;;;;;;CAMC,GACD,OAAO,SAASC,6BACdC,GAA4E;QAKpCA;IAHxC,IAAIA,IAAIhB,IAAI,KAAK,UAAU,CAACgB,IAAIC,MAAM,EAAE,OAAO;IAC/C,IAAID,IAAIE,OAAO,CAACC,MAAM,GAAG,GAAG,OAAO;IACnC,IAAIH,IAAII,MAAM,KAAK,YAAYJ,IAAII,MAAM,KAAK,YAAY,OAAO;IACjE,OAAOJ,IAAII,MAAM,KAAK,kBAAkBJ,EAAAA,cAAAA,IAAIK,MAAM,qBAAVL,YAAYM,MAAM,MAAK;AACjE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { AglynNotification } from '@aglyn/aglyn/app-utils/notifications';
|
|
18
|
+
import type { AiJobKind, AiJobOutput, AiJobReviewReason } from './ai-jobs.types';
|
|
19
|
+
/** The besigner segment each versioned resource lives under. */
|
|
20
|
+
export declare const AI_JOB_BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>>;
|
|
21
|
+
/**
|
|
22
|
+
* The query parameter a console address carries to open AI jobs on one job
|
|
23
|
+
* (AGL-3593): the Assist panel reads it on arrival.
|
|
24
|
+
*/
|
|
25
|
+
export declare const AI_JOB_LINK_PARAM = "aiJob";
|
|
26
|
+
/** Where a site's "Building your site" page lives, under the site (AGL-3594). */
|
|
27
|
+
export declare const AI_SITE_BUILD_HREF = "/ai-jobs";
|
|
28
|
+
/** A change the person who started a job is told about. */
|
|
29
|
+
export type AiJobNoticeTransition = 'needs-review' | 'done' | 'failed';
|
|
30
|
+
/** The fields of a stored job a notice is written from. */
|
|
31
|
+
export interface AiJobNoticeSource {
|
|
32
|
+
$id: string;
|
|
33
|
+
orgId: string;
|
|
34
|
+
hostId?: string | null;
|
|
35
|
+
kind: AiJobKind;
|
|
36
|
+
review?: {
|
|
37
|
+
reason: AiJobReviewReason;
|
|
38
|
+
message?: string | null;
|
|
39
|
+
} | null;
|
|
40
|
+
outputs?: readonly AiJobOutput[] | null;
|
|
41
|
+
error?: string | null;
|
|
42
|
+
}
|
|
43
|
+
export type AiJobNotice = Pick<AglynNotification, 'type' | 'title' | 'body' | 'link' | 'orgId' | 'hostId' | 'level'>;
|
|
44
|
+
/**
|
|
45
|
+
* Where a notice's link goes, in the stored form the console rewrites when it
|
|
46
|
+
* is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,
|
|
47
|
+
* `/org…` for a job of the workspace's.
|
|
48
|
+
*
|
|
49
|
+
* A job that is done leads to what it built (AGL-3593): several pages of the
|
|
50
|
+
* job's site open that site's Pages list, one versioned output opens in the
|
|
51
|
+
* editor on the version the job wrote. Everything else — a plan to confirm, a
|
|
52
|
+
* job that stopped, a done job with nothing of its own to open — opens AI
|
|
53
|
+
* jobs on the job.
|
|
54
|
+
*/
|
|
55
|
+
export declare function aiJobNoticeLink(job: AiJobNoticeSource, to: AiJobNoticeTransition): string;
|
|
56
|
+
/**
|
|
57
|
+
* The in-app notification for one change of one job (AGL-3593), for the
|
|
58
|
+
* person who created it. Customer-safe throughout: a stopped job's sentence is
|
|
59
|
+
* the job's own `error`, which the machine writes as a fixed sentence.
|
|
60
|
+
*/
|
|
61
|
+
export declare function aiJobNotice(job: AiJobNoticeSource, to: AiJobNoticeTransition): AiJobNotice;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { aiJobKindNoun } from "./ai-job-activity.js";
|
|
18
|
+
/** The besigner segment each versioned resource lives under. */ export const AI_JOB_BESIGNER_SEGMENT = {
|
|
19
|
+
screen: 'screens',
|
|
20
|
+
reusableComponent: 'components',
|
|
21
|
+
layout: 'layouts',
|
|
22
|
+
template: 'templates',
|
|
23
|
+
// An email design is a screen: it opens in the screen besigner, as the
|
|
24
|
+
// Emails page's own Edit design does.
|
|
25
|
+
emailScreen: 'screens'
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The query parameter a console address carries to open AI jobs on one job
|
|
29
|
+
* (AGL-3593): the Assist panel reads it on arrival.
|
|
30
|
+
*/ export const AI_JOB_LINK_PARAM = 'aiJob';
|
|
31
|
+
/** Where a site's "Building your site" page lives, under the site (AGL-3594). */ export const AI_SITE_BUILD_HREF = '/ai-jobs';
|
|
32
|
+
/**
|
|
33
|
+
* Where a notice's link goes, in the stored form the console rewrites when it
|
|
34
|
+
* is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,
|
|
35
|
+
* `/org…` for a job of the workspace's.
|
|
36
|
+
*
|
|
37
|
+
* A job that is done leads to what it built (AGL-3593): several pages of the
|
|
38
|
+
* job's site open that site's Pages list, one versioned output opens in the
|
|
39
|
+
* editor on the version the job wrote. Everything else — a plan to confirm, a
|
|
40
|
+
* job that stopped, a done job with nothing of its own to open — opens AI
|
|
41
|
+
* jobs on the job.
|
|
42
|
+
*/ export function aiJobNoticeLink(job, to) {
|
|
43
|
+
const base = job.hostId ? `/${job.hostId}` : '/org';
|
|
44
|
+
// A site job's every notice opens its "Building your site" page (AGL-3594),
|
|
45
|
+
// which leads with the next step whatever the job's state.
|
|
46
|
+
if (job.kind === 'site' && job.hostId) return `${base}${AI_SITE_BUILD_HREF}/${encodeURIComponent(job.$id)}`;
|
|
47
|
+
if (to === 'done' && job.hostId) {
|
|
48
|
+
var _job_outputs;
|
|
49
|
+
const own = ((_job_outputs = job.outputs) != null ? _job_outputs : []).filter((output)=>output.hostId === job.hostId);
|
|
50
|
+
const screens = own.filter((output)=>output.resource === 'screen');
|
|
51
|
+
if (screens.length > 1) return `${base}/screens`;
|
|
52
|
+
const versioned = own.find((output)=>AI_JOB_BESIGNER_SEGMENT[output.resource] && output.versionId);
|
|
53
|
+
if (own.length === 1 && versioned) {
|
|
54
|
+
const segment = AI_JOB_BESIGNER_SEGMENT[versioned.resource];
|
|
55
|
+
return `${base}/${segment}/${versioned.id}/versions/${versioned.versionId}/besigner`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return `${base}?${AI_JOB_LINK_PARAM}=${encodeURIComponent(job.$id)}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The in-app notification for one change of one job (AGL-3593), for the
|
|
62
|
+
* person who created it. Customer-safe throughout: a stopped job's sentence is
|
|
63
|
+
* the job's own `error`, which the machine writes as a fixed sentence.
|
|
64
|
+
*/ export function aiJobNotice(job, to) {
|
|
65
|
+
const noun = aiJobKindNoun(job.kind);
|
|
66
|
+
const scope = _extends({
|
|
67
|
+
orgId: job.orgId
|
|
68
|
+
}, job.hostId ? {
|
|
69
|
+
hostId: job.hostId
|
|
70
|
+
} : {});
|
|
71
|
+
const link = aiJobNoticeLink(job, to);
|
|
72
|
+
if (to === 'needs-review') {
|
|
73
|
+
var _job_review, _job_review1;
|
|
74
|
+
if (((_job_review = job.review) == null ? void 0 : _job_review.reason) === 'plan') {
|
|
75
|
+
return _extends({
|
|
76
|
+
type: 'content.aiJobNeedsYou',
|
|
77
|
+
level: 'warning',
|
|
78
|
+
title: noun === 'AI job' ? 'Your plan is ready: confirm it to build' : `Your ${noun} plan is ready: confirm it to build`,
|
|
79
|
+
body: 'Nothing is built until you confirm the plan. It waits in AI jobs.',
|
|
80
|
+
link
|
|
81
|
+
}, scope);
|
|
82
|
+
}
|
|
83
|
+
return _extends({
|
|
84
|
+
type: 'content.aiJobNeedsYou',
|
|
85
|
+
level: 'warning',
|
|
86
|
+
title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} needs you`,
|
|
87
|
+
body: ((_job_review1 = job.review) == null ? void 0 : _job_review1.message) || job.error || 'It stopped for your decision. It waits in AI jobs.',
|
|
88
|
+
link
|
|
89
|
+
}, scope);
|
|
90
|
+
}
|
|
91
|
+
if (to === 'done') {
|
|
92
|
+
var _job_outputs;
|
|
93
|
+
const pages = ((_job_outputs = job.outputs) != null ? _job_outputs : []).filter((output)=>output.resource === 'screen').length;
|
|
94
|
+
const title = job.kind === 'site' || pages > 1 ? 'Your site’s draft pages are ready' : job.kind === 'page' ? 'Your draft page is ready' : noun === 'AI job' ? 'Your AI job finished' : `Your ${noun} is ready`;
|
|
95
|
+
return _extends({
|
|
96
|
+
type: 'content.aiJobDone',
|
|
97
|
+
level: 'success',
|
|
98
|
+
title,
|
|
99
|
+
body: job.kind === 'insight' || job.kind === 'text' ? 'Open AI jobs to read it.' : 'Everything it built is an unpublished draft until you publish it.',
|
|
100
|
+
link
|
|
101
|
+
}, scope);
|
|
102
|
+
}
|
|
103
|
+
return _extends({
|
|
104
|
+
type: 'content.aiJobFailed',
|
|
105
|
+
level: 'warning',
|
|
106
|
+
title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} stopped`,
|
|
107
|
+
body: job.error || 'It stopped before it finished.',
|
|
108
|
+
link
|
|
109
|
+
}, scope);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=ai-job-notice.js.map
|