@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,346 @@
|
|
|
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
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { resolveStaffRoleGate } from "@aglyn/aglyn/app-utils/staff-role-gate";
|
|
20
|
+
import BlockedControl from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
21
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
22
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
23
|
+
import { Alert, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, FormLabel, Radio, RadioGroup, Stack, TextField, Typography } from "@mui/material";
|
|
24
|
+
import { useEffect, useState } from "react";
|
|
25
|
+
/**
|
|
26
|
+
* GIVE AI CREDITS BACK (AGL-3595): the staff dialog behind the AI cards'
|
|
27
|
+
* "Give back credits" and "Reset this month" header actions.
|
|
28
|
+
*
|
|
29
|
+
* Posts to `/api/ai/admin/credits`, which holds every rule — the role, the
|
|
30
|
+
* bound, the reason, the key. The dialog states the same rules before the
|
|
31
|
+
* request so staff are not refused for something the form could have said,
|
|
32
|
+
* and mints the idempotency key once per opening, so a double-click or a
|
|
33
|
+
* retry after a lost response returns the credits once.
|
|
34
|
+
*/ /** The roles the route admits (`AI_CREDIT_RETURN_ROLES`, server side). */ const RETURN_ROLES = [
|
|
35
|
+
'super',
|
|
36
|
+
'billing'
|
|
37
|
+
];
|
|
38
|
+
/** The most one give-back may return to the meter(s) chosen. */ export function staffCreditReturnable(meter, workspace, account) {
|
|
39
|
+
const figures = meter === 'both' ? [
|
|
40
|
+
workspace,
|
|
41
|
+
account
|
|
42
|
+
] : meter === 'workspace' ? [
|
|
43
|
+
workspace
|
|
44
|
+
] : [
|
|
45
|
+
account
|
|
46
|
+
];
|
|
47
|
+
if (figures.some((figure)=>!figure)) return 0;
|
|
48
|
+
return Math.max(0, Math.min(...figures.map((figure)=>{
|
|
49
|
+
var _ref;
|
|
50
|
+
return Math.floor((_ref = figure == null ? void 0 : figure.used) != null ? _ref : 0);
|
|
51
|
+
})));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What stops the form, or `null` when it may be sent — the route's own
|
|
55
|
+
* refusals, said before the request.
|
|
56
|
+
*/ export function staffCreditReturnProblem(input) {
|
|
57
|
+
if (!input.reason.trim()) return 'Say why — the reason is the audit row.';
|
|
58
|
+
if (input.returnable <= 0) return 'Nothing used this month to give back.';
|
|
59
|
+
if (input.mode === 'reset') return null;
|
|
60
|
+
const credits = Number(input.credits);
|
|
61
|
+
if (!Number.isInteger(credits) || credits < 1) {
|
|
62
|
+
return 'Enter a whole number of credits, 1 or more.';
|
|
63
|
+
}
|
|
64
|
+
if (credits > input.returnable) {
|
|
65
|
+
return `At most ${input.returnable.toLocaleString()} — what this month used.`;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const newKey = ()=>typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : `k${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
|
|
70
|
+
/** The signed-in staff member's role, `null` while the token is read. */ export function useStaffRoleClaim() {
|
|
71
|
+
const { data: user } = useUser();
|
|
72
|
+
const [role, setRole] = useState(null);
|
|
73
|
+
useEffect(()=>{
|
|
74
|
+
let active = true;
|
|
75
|
+
const account = user;
|
|
76
|
+
if (!(account == null ? void 0 : account.getIdTokenResult)) return undefined;
|
|
77
|
+
void account.getIdTokenResult().then((result)=>{
|
|
78
|
+
var _claims_staffRole;
|
|
79
|
+
if (!active) return;
|
|
80
|
+
const claims = result == null ? void 0 : result.claims;
|
|
81
|
+
// Missing is `support`, as every staff route reads it (AGL-495).
|
|
82
|
+
setRole((claims == null ? void 0 : claims['staff']) ? String((_claims_staffRole = claims['staffRole']) != null ? _claims_staffRole : 'support') : null);
|
|
83
|
+
}).catch(()=>undefined);
|
|
84
|
+
return ()=>{
|
|
85
|
+
active = false;
|
|
86
|
+
};
|
|
87
|
+
}, [
|
|
88
|
+
user
|
|
89
|
+
]);
|
|
90
|
+
return role;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The two header actions, gated on the role the route admits. A role that
|
|
94
|
+
* would be refused sees the buttons disabled with the reason, not hidden.
|
|
95
|
+
*/ export function StaffCreditReturnActions({ disabled, onGive, onReset }) {
|
|
96
|
+
var _gate_reason;
|
|
97
|
+
const gate = resolveStaffRoleGate(useStaffRoleClaim(), RETURN_ROLES);
|
|
98
|
+
const reason = (_gate_reason = gate.reason) != null ? _gate_reason : '';
|
|
99
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
100
|
+
direction: "row",
|
|
101
|
+
spacing: 1,
|
|
102
|
+
useFlexGap: true,
|
|
103
|
+
sx: {
|
|
104
|
+
flexWrap: 'wrap'
|
|
105
|
+
},
|
|
106
|
+
children: [
|
|
107
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
108
|
+
blocked: gate.blocked,
|
|
109
|
+
reason: reason,
|
|
110
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
111
|
+
size: "small",
|
|
112
|
+
disabled: disabled || !gate.admitted,
|
|
113
|
+
onClick: onGive,
|
|
114
|
+
children: 'Give back credits'
|
|
115
|
+
})
|
|
116
|
+
}),
|
|
117
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
118
|
+
blocked: gate.blocked,
|
|
119
|
+
reason: reason,
|
|
120
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
121
|
+
size: "small",
|
|
122
|
+
color: "warning",
|
|
123
|
+
disabled: disabled || !gate.admitted,
|
|
124
|
+
onClick: onReset,
|
|
125
|
+
children: 'Reset this month'
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const meterLabel = {
|
|
132
|
+
workspace: 'Workspace band',
|
|
133
|
+
account: 'Owner’s Free allowance',
|
|
134
|
+
both: 'Both'
|
|
135
|
+
};
|
|
136
|
+
export function StaffCreditReturnDialog(props) {
|
|
137
|
+
var _meters_, _ref, _ref1;
|
|
138
|
+
const { open, mode, workspace, account } = props;
|
|
139
|
+
const { data: user } = useUser();
|
|
140
|
+
const meters = [
|
|
141
|
+
'both',
|
|
142
|
+
'workspace',
|
|
143
|
+
'account'
|
|
144
|
+
].filter((meter)=>meter === 'both' ? Boolean(workspace && account) : meter === 'workspace' ? Boolean(workspace) : Boolean(account));
|
|
145
|
+
const [meter, setMeter] = useState((_meters_ = meters[0]) != null ? _meters_ : 'workspace');
|
|
146
|
+
const [credits, setCredits] = useState('');
|
|
147
|
+
const [reason, setReason] = useState('');
|
|
148
|
+
const [jobId, setJobId] = useState('');
|
|
149
|
+
const [key, setKey] = useState(newKey);
|
|
150
|
+
const [busy, setBusy] = useState(false);
|
|
151
|
+
const [touched, setTouched] = useState(false);
|
|
152
|
+
const [error, setError] = useState(null);
|
|
153
|
+
const returnable = staffCreditReturnable(meter, workspace, account);
|
|
154
|
+
// Each opening is one act: a fresh key, and the form reset to its
|
|
155
|
+
// defaults — the amount that puts the meter back to zero, or the job's
|
|
156
|
+
// spend when opened from a job, never more than the month used.
|
|
157
|
+
useEffect(()=>{
|
|
158
|
+
var _meters_, _props_jobId;
|
|
159
|
+
if (!open) return;
|
|
160
|
+
const initialMeter = (_meters_ = meters[0]) != null ? _meters_ : 'workspace';
|
|
161
|
+
const initialReturnable = staffCreditReturnable(initialMeter, workspace, account);
|
|
162
|
+
const suggested = props.jobCredits && props.jobCredits > 0 ? Math.min(Math.ceil(props.jobCredits), initialReturnable) : initialReturnable;
|
|
163
|
+
setMeter(initialMeter);
|
|
164
|
+
setCredits(suggested > 0 ? String(suggested) : '');
|
|
165
|
+
setReason('');
|
|
166
|
+
setJobId((_props_jobId = props.jobId) != null ? _props_jobId : '');
|
|
167
|
+
setKey(newKey());
|
|
168
|
+
setTouched(false);
|
|
169
|
+
setError(null);
|
|
170
|
+
// Opening is the trigger; the figures are read as they stand then, and
|
|
171
|
+
// a card re-read while the dialog is open must not wipe what was typed.
|
|
172
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
173
|
+
}, [
|
|
174
|
+
open
|
|
175
|
+
]);
|
|
176
|
+
const problem = staffCreditReturnProblem({
|
|
177
|
+
mode,
|
|
178
|
+
credits,
|
|
179
|
+
reason,
|
|
180
|
+
returnable
|
|
181
|
+
});
|
|
182
|
+
const submit = async ()=>{
|
|
183
|
+
setTouched(true);
|
|
184
|
+
if (problem || !user) return;
|
|
185
|
+
setBusy(true);
|
|
186
|
+
setError(null);
|
|
187
|
+
try {
|
|
188
|
+
var _ref;
|
|
189
|
+
const response = await authorizedFetch(user, '/api/ai/admin/credits', {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
headers: {
|
|
192
|
+
'content-type': 'application/json'
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify(_extends({}, props.target, {
|
|
195
|
+
action: mode,
|
|
196
|
+
meter
|
|
197
|
+
}, mode === 'give' ? {
|
|
198
|
+
credits: Number(credits)
|
|
199
|
+
} : {}, {
|
|
200
|
+
reason: reason.trim(),
|
|
201
|
+
jobId: jobId.trim() || null,
|
|
202
|
+
idempotencyKey: key
|
|
203
|
+
}))
|
|
204
|
+
});
|
|
205
|
+
const payload = await response.json().catch(()=>null);
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
var _ref1;
|
|
208
|
+
setError((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : 'Giving credits back failed');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const lines = (_ref = payload == null ? void 0 : payload.lines) != null ? _ref : [];
|
|
212
|
+
props.onReturned((payload == null ? void 0 : payload.duplicate) ? 'Already given back — nothing returned twice.' : `Returned ${lines.filter((line)=>line.credits > 0).map((line)=>`${line.credits.toLocaleString()} credits to the ${line.meter}`).join(' and ')}.`);
|
|
213
|
+
} catch (unused) {
|
|
214
|
+
setError('Giving credits back failed');
|
|
215
|
+
} finally{
|
|
216
|
+
setBusy(false);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const figure = (label, value)=>value ? /*#__PURE__*/ _jsx(Typography, {
|
|
220
|
+
variant: "body2",
|
|
221
|
+
children: `${label}: ${Math.floor(value.used).toLocaleString()} used` + (value.limit === null ? '' : ` of ${value.limit.toLocaleString()}`)
|
|
222
|
+
}) : null;
|
|
223
|
+
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
224
|
+
open: open,
|
|
225
|
+
onClose: busy ? undefined : props.onClose,
|
|
226
|
+
maxWidth: "xs",
|
|
227
|
+
fullWidth: true,
|
|
228
|
+
children: [
|
|
229
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
230
|
+
children: mode === 'reset' ? 'Reset this month’s AI credits' : 'Give back AI credits'
|
|
231
|
+
}),
|
|
232
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
233
|
+
sx: {
|
|
234
|
+
display: 'flex',
|
|
235
|
+
flexDirection: 'column',
|
|
236
|
+
gap: 2,
|
|
237
|
+
pt: 1
|
|
238
|
+
},
|
|
239
|
+
children: [
|
|
240
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
241
|
+
variant: "body2",
|
|
242
|
+
color: "text.secondary",
|
|
243
|
+
children: mode === 'reset' ? 'Returns everything used this month on the meter you choose. The spend stays on record; the meter reads it as given back.' : 'For a fault of ours. The spend stays on record; the meter reads it as given back, up to what this month used.'
|
|
244
|
+
}),
|
|
245
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
246
|
+
spacing: 0.5,
|
|
247
|
+
children: [
|
|
248
|
+
figure('Workspace band', workspace),
|
|
249
|
+
figure('Owner’s Free allowance', account)
|
|
250
|
+
]
|
|
251
|
+
}),
|
|
252
|
+
meters.length > 1 ? /*#__PURE__*/ _jsxs("div", {
|
|
253
|
+
children: [
|
|
254
|
+
/*#__PURE__*/ _jsx(FormLabel, {
|
|
255
|
+
id: "ai-credit-return-meter",
|
|
256
|
+
children: 'Give back to'
|
|
257
|
+
}),
|
|
258
|
+
/*#__PURE__*/ _jsx(RadioGroup, {
|
|
259
|
+
"aria-labelledby": "ai-credit-return-meter",
|
|
260
|
+
value: meter,
|
|
261
|
+
onChange: (event)=>setMeter(event.target.value),
|
|
262
|
+
children: meters.map((value)=>/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
263
|
+
value: value,
|
|
264
|
+
control: /*#__PURE__*/ _jsx(Radio, {
|
|
265
|
+
size: "small"
|
|
266
|
+
}),
|
|
267
|
+
disabled: busy,
|
|
268
|
+
label: meterLabel[value]
|
|
269
|
+
}, value))
|
|
270
|
+
})
|
|
271
|
+
]
|
|
272
|
+
}) : null,
|
|
273
|
+
mode === 'give' ? /*#__PURE__*/ _jsx(TextField, {
|
|
274
|
+
label: "Credits",
|
|
275
|
+
type: "number",
|
|
276
|
+
size: "small",
|
|
277
|
+
value: credits,
|
|
278
|
+
onChange: (event)=>setCredits(event.target.value),
|
|
279
|
+
disabled: busy,
|
|
280
|
+
slotProps: {
|
|
281
|
+
htmlInput: {
|
|
282
|
+
min: 1,
|
|
283
|
+
max: returnable,
|
|
284
|
+
step: 1
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
helperText: `At most ${returnable.toLocaleString()} — what this month used${meter === 'both' ? ' on the lower of the two' : ''}.`
|
|
288
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
289
|
+
variant: "body2",
|
|
290
|
+
children: meter === 'both' ? `Returns ${Math.floor((_ref = workspace == null ? void 0 : workspace.used) != null ? _ref : 0).toLocaleString()} to the workspace band and ${Math.floor((_ref1 = account == null ? void 0 : account.used) != null ? _ref1 : 0).toLocaleString()} to the owner’s allowance.` : `Returns ${returnable.toLocaleString()} credits.`
|
|
291
|
+
}),
|
|
292
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
293
|
+
label: "Reason",
|
|
294
|
+
required: true,
|
|
295
|
+
multiline: true,
|
|
296
|
+
minRows: 2,
|
|
297
|
+
size: "small",
|
|
298
|
+
value: reason,
|
|
299
|
+
onChange: (event)=>setReason(event.target.value),
|
|
300
|
+
disabled: busy,
|
|
301
|
+
error: touched && !reason.trim(),
|
|
302
|
+
helperText: "Recorded in the staff audit log with the amount.",
|
|
303
|
+
slotProps: {
|
|
304
|
+
htmlInput: {
|
|
305
|
+
maxLength: 500
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}),
|
|
309
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
310
|
+
label: "AI job id (optional)",
|
|
311
|
+
size: "small",
|
|
312
|
+
value: jobId,
|
|
313
|
+
onChange: (event)=>setJobId(event.target.value),
|
|
314
|
+
disabled: busy
|
|
315
|
+
}),
|
|
316
|
+
touched && problem || error ? /*#__PURE__*/ _jsx(Alert, {
|
|
317
|
+
severity: "error",
|
|
318
|
+
children: error != null ? error : problem
|
|
319
|
+
}) : null
|
|
320
|
+
]
|
|
321
|
+
}),
|
|
322
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
323
|
+
children: [
|
|
324
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
325
|
+
disabled: busy,
|
|
326
|
+
onClick: props.onClose,
|
|
327
|
+
children: 'Cancel'
|
|
328
|
+
}),
|
|
329
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
330
|
+
variant: "contained",
|
|
331
|
+
color: mode === 'reset' ? 'warning' : 'primary',
|
|
332
|
+
disabled: busy,
|
|
333
|
+
onClick: ()=>void submit(),
|
|
334
|
+
startIcon: busy ? /*#__PURE__*/ _jsx(CircularProgress, {
|
|
335
|
+
size: 16
|
|
336
|
+
}) : undefined,
|
|
337
|
+
children: mode === 'reset' ? 'Reset' : 'Give back'
|
|
338
|
+
})
|
|
339
|
+
]
|
|
340
|
+
})
|
|
341
|
+
]
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
StaffCreditReturnDialog.displayName = 'StaffCreditReturnDialog';
|
|
345
|
+
|
|
346
|
+
//# sourceMappingURL=staff-ai-credit-return.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-ai-credit-return.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { resolveStaffRoleGate } from '@aglyn/aglyn/app-utils/staff-role-gate'\nimport BlockedControl from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormLabel,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\n\n/**\n * GIVE AI CREDITS BACK (AGL-3595): the staff dialog behind the AI cards'\n * \"Give back credits\" and \"Reset this month\" header actions.\n *\n * Posts to `/api/ai/admin/credits`, which holds every rule — the role, the\n * bound, the reason, the key. The dialog states the same rules before the\n * request so staff are not refused for something the form could have said,\n * and mints the idempotency key once per opening, so a double-click or a\n * retry after a lost response returns the credits once.\n */\n\n/** The roles the route admits (`AI_CREDIT_RETURN_ROLES`, server side). */\nconst RETURN_ROLES = ['super', 'billing'] as const\n\nexport type StaffCreditReturnMeter = 'workspace' | 'account' | 'both'\n\n/** One meter's month, as the card already holds it. */\nexport interface StaffCreditMeterFigure {\n /** Credits used this month, net of earlier give-backs. */\n used: number\n /** The band or allowance, or null where none applies. */\n limit: number | null\n}\n\n/** The most one give-back may return to the meter(s) chosen. */\nexport function staffCreditReturnable(\n meter: StaffCreditReturnMeter,\n workspace: StaffCreditMeterFigure | null,\n account: StaffCreditMeterFigure | null,\n): number {\n const figures =\n meter === 'both'\n ? [workspace, account]\n : meter === 'workspace'\n ? [workspace]\n : [account]\n if (figures.some((figure) => !figure)) return 0\n return Math.max(\n 0,\n Math.min(...figures.map((figure) => Math.floor(figure?.used ?? 0))),\n )\n}\n\n/**\n * What stops the form, or `null` when it may be sent — the route's own\n * refusals, said before the request.\n */\nexport function staffCreditReturnProblem(input: {\n mode: 'give' | 'reset'\n credits: string\n reason: string\n returnable: number\n}): string | null {\n if (!input.reason.trim()) return 'Say why — the reason is the audit row.'\n if (input.returnable <= 0) return 'Nothing used this month to give back.'\n if (input.mode === 'reset') return null\n const credits = Number(input.credits)\n if (!Number.isInteger(credits) || credits < 1) {\n return 'Enter a whole number of credits, 1 or more.'\n }\n if (credits > input.returnable) {\n return `At most ${input.returnable.toLocaleString()} — what this month used.`\n }\n return null\n}\n\nconst newKey = (): string =>\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `k${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`\n\n/** The signed-in staff member's role, `null` while the token is read. */\nexport function useStaffRoleClaim(): string | null {\n const { data: user } = useUser()\n const [role, setRole] = useState<string | null>(null)\n useEffect(() => {\n let active = true\n const account = user as\n | { getIdTokenResult?: () => Promise<{ claims?: Record<string, unknown> }> }\n | null\n | undefined\n if (!account?.getIdTokenResult) return undefined\n void account\n .getIdTokenResult()\n .then((result) => {\n if (!active) return\n const claims = result?.claims\n // Missing is `support`, as every staff route reads it (AGL-495).\n setRole(claims?.['staff'] ? String(claims['staffRole'] ?? 'support') : null)\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [user])\n return role\n}\n\n/**\n * The two header actions, gated on the role the route admits. A role that\n * would be refused sees the buttons disabled with the reason, not hidden.\n */\nexport function StaffCreditReturnActions({\n disabled,\n onGive,\n onReset,\n}: {\n disabled: boolean\n onGive: () => void\n onReset: () => void\n}) {\n const gate = resolveStaffRoleGate(useStaffRoleClaim(), RETURN_ROLES)\n const reason = gate.reason ?? ''\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <BlockedControl blocked={gate.blocked} reason={reason}>\n <Button size=\"small\" disabled={disabled || !gate.admitted} onClick={onGive}>\n {'Give back credits'}\n </Button>\n </BlockedControl>\n <BlockedControl blocked={gate.blocked} reason={reason}>\n <Button\n size=\"small\"\n color=\"warning\"\n disabled={disabled || !gate.admitted}\n onClick={onReset}\n >\n {'Reset this month'}\n </Button>\n </BlockedControl>\n </Stack>\n )\n}\n\nexport interface StaffCreditReturnDialogProps {\n open: boolean\n mode: 'give' | 'reset'\n /** The workspace, or (from the staff user page) the account. */\n target: { orgId: string } | { uid: string }\n /** The workspace's band this month; null where it is not offered. */\n workspace: StaffCreditMeterFigure | null\n /** The owner's Free allowance this month; null where it does not apply. */\n account: StaffCreditMeterFigure | null\n /** Opened from a job: its id, and what it spent. */\n jobId?: string | null\n jobCredits?: number | null\n onClose: () => void\n /** After the route answered 200 — the card re-reads its figures. */\n onReturned: (message: string) => void\n}\n\nconst meterLabel: Record<StaffCreditReturnMeter, string> = {\n workspace: 'Workspace band',\n account: 'Owner’s Free allowance',\n both: 'Both',\n}\n\nexport function StaffCreditReturnDialog(props: StaffCreditReturnDialogProps) {\n const { open, mode, workspace, account } = props\n const { data: user } = useUser()\n const meters = (['both', 'workspace', 'account'] as const).filter((meter) =>\n meter === 'both' ? Boolean(workspace && account) : meter === 'workspace' ? Boolean(workspace) : Boolean(account),\n )\n const [meter, setMeter] = useState<StaffCreditReturnMeter>(meters[0] ?? 'workspace')\n const [credits, setCredits] = useState('')\n const [reason, setReason] = useState('')\n const [jobId, setJobId] = useState('')\n const [key, setKey] = useState(newKey)\n const [busy, setBusy] = useState(false)\n const [touched, setTouched] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const returnable = staffCreditReturnable(meter, workspace, account)\n\n // Each opening is one act: a fresh key, and the form reset to its\n // defaults — the amount that puts the meter back to zero, or the job's\n // spend when opened from a job, never more than the month used.\n useEffect(() => {\n if (!open) return\n const initialMeter = meters[0] ?? 'workspace'\n const initialReturnable = staffCreditReturnable(initialMeter, workspace, account)\n const suggested =\n props.jobCredits && props.jobCredits > 0\n ? Math.min(Math.ceil(props.jobCredits), initialReturnable)\n : initialReturnable\n setMeter(initialMeter)\n setCredits(suggested > 0 ? String(suggested) : '')\n setReason('')\n setJobId(props.jobId ?? '')\n setKey(newKey())\n setTouched(false)\n setError(null)\n // Opening is the trigger; the figures are read as they stand then, and\n // a card re-read while the dialog is open must not wipe what was typed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const problem = staffCreditReturnProblem({ mode, credits, reason, returnable })\n\n const submit = async () => {\n setTouched(true)\n if (problem || !user) return\n setBusy(true)\n setError(null)\n try {\n const response = await authorizedFetch(user, '/api/ai/admin/credits', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n ...props.target,\n action: mode,\n meter,\n ...(mode === 'give' ? { credits: Number(credits) } : {}),\n reason: reason.trim(),\n jobId: jobId.trim() || null,\n idempotencyKey: key,\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n setError(payload?.error ?? 'Giving credits back failed')\n return\n }\n const lines = (payload?.lines ?? []) as Array<{ meter: string; credits: number }>\n props.onReturned(\n payload?.duplicate\n ? 'Already given back — nothing returned twice.'\n : `Returned ${lines\n .filter((line) => line.credits > 0)\n .map((line) => `${line.credits.toLocaleString()} credits to the ${line.meter}`)\n .join(' and ')}.`,\n )\n } catch {\n setError('Giving credits back failed')\n } finally {\n setBusy(false)\n }\n }\n\n const figure = (label: string, value: StaffCreditMeterFigure | null) =>\n value ? (\n <Typography variant=\"body2\">\n {`${label}: ${Math.floor(value.used).toLocaleString()} used` +\n (value.limit === null ? '' : ` of ${value.limit.toLocaleString()}`)}\n </Typography>\n ) : null\n\n return (\n <Dialog open={open} onClose={busy ? undefined : props.onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{mode === 'reset' ? 'Reset this month’s AI credits' : 'Give back AI credits'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mode === 'reset'\n ? 'Returns everything used this month on the meter you choose. The spend stays on record; the meter reads it as given back.'\n : 'For a fault of ours. The spend stays on record; the meter reads it as given back, up to what this month used.'}\n </Typography>\n <Stack spacing={0.5}>\n {figure('Workspace band', workspace)}\n {figure('Owner’s Free allowance', account)}\n </Stack>\n {meters.length > 1 ? (\n <div>\n <FormLabel id=\"ai-credit-return-meter\">{'Give back to'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"ai-credit-return-meter\"\n value={meter}\n onChange={(event) => setMeter(event.target.value as StaffCreditReturnMeter)}\n >\n {meters.map((value) => (\n <FormControlLabel\n key={value}\n value={value}\n control={<Radio size=\"small\" />}\n disabled={busy}\n label={meterLabel[value]}\n />\n ))}\n </RadioGroup>\n </div>\n ) : null}\n {mode === 'give' ? (\n <TextField\n label=\"Credits\"\n type=\"number\"\n size=\"small\"\n value={credits}\n onChange={(event) => setCredits(event.target.value)}\n disabled={busy}\n slotProps={{ htmlInput: { min: 1, max: returnable, step: 1 } }}\n helperText={`At most ${returnable.toLocaleString()} — what this month used${meter === 'both' ? ' on the lower of the two' : ''}.`}\n />\n ) : (\n <Typography variant=\"body2\">\n {meter === 'both'\n ? `Returns ${Math.floor(workspace?.used ?? 0).toLocaleString()} to the workspace band and ${Math.floor(account?.used ?? 0).toLocaleString()} to the owner’s allowance.`\n : `Returns ${returnable.toLocaleString()} credits.`}\n </Typography>\n )}\n <TextField\n label=\"Reason\"\n required\n multiline\n minRows={2}\n size=\"small\"\n value={reason}\n onChange={(event) => setReason(event.target.value)}\n disabled={busy}\n error={touched && !reason.trim()}\n helperText=\"Recorded in the staff audit log with the amount.\"\n slotProps={{ htmlInput: { maxLength: 500 } }}\n />\n <TextField\n label=\"AI job id (optional)\"\n size=\"small\"\n value={jobId}\n onChange={(event) => setJobId(event.target.value)}\n disabled={busy}\n />\n {(touched && problem) || error ? (\n <Alert severity=\"error\">{error ?? problem}</Alert>\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={props.onClose}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color={mode === 'reset' ? 'warning' : 'primary'}\n disabled={busy}\n onClick={() => void submit()}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n >\n {mode === 'reset' ? 'Reset' : 'Give back'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nStaffCreditReturnDialog.displayName = 'StaffCreditReturnDialog'\n"],"names":["resolveStaffRoleGate","BlockedControl","authorizedFetch","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormLabel","Radio","RadioGroup","Stack","TextField","Typography","useEffect","useState","RETURN_ROLES","staffCreditReturnable","meter","workspace","account","figures","some","figure","Math","max","min","map","floor","used","staffCreditReturnProblem","input","reason","trim","returnable","mode","credits","Number","isInteger","toLocaleString","newKey","crypto","randomUUID","Date","now","toString","random","slice","useStaffRoleClaim","data","user","role","setRole","active","getIdTokenResult","undefined","then","result","claims","String","catch","StaffCreditReturnActions","disabled","onGive","onReset","gate","direction","spacing","useFlexGap","sx","flexWrap","blocked","size","admitted","onClick","color","meterLabel","both","StaffCreditReturnDialog","props","meters","open","filter","Boolean","setMeter","setCredits","setReason","jobId","setJobId","key","setKey","busy","setBusy","touched","setTouched","error","setError","initialMeter","initialReturnable","suggested","jobCredits","ceil","problem","submit","response","method","headers","body","JSON","stringify","target","action","idempotencyKey","payload","json","ok","lines","onReturned","duplicate","line","join","label","value","variant","limit","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","length","div","id","aria-labelledby","onChange","event","control","type","slotProps","htmlInput","step","helperText","required","multiline","minRows","maxLength","severity","startIcon","displayName"],"mappings":"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"}
|