@burdenoff/microfe-mybodyshield 2026.821.2 → 2026.827.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"AiControlsPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/ai/AiControlsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0WH,wBAAgB,cAAc,gCA2C7B"}
1
+ {"version":3,"file":"AiControlsPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/ai/AiControlsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgcH,wBAAgB,cAAc,gCA2C7B"}
@@ -3,225 +3,284 @@ import { MetricsCard as t } from "../../components/MetricsCard.js";
3
3
  import { formatDateTime as n, formatEnumLabel as r, formatRelativeTime as i } from "../../utils/formatters.js";
4
4
  import { ErrorView as a, LoadingView as o, Section as s } from "../../components/StateViews.js";
5
5
  import { DataTable as c } from "../../components/DataTable.js";
6
- import { useMybAiOperationLogs as l, useMybAiSettings as u, useMybUpdateAiSettings as d } from "../../hooks/useMyBodyShieldApi.js";
6
+ import { useMybAiOperationLogs as l, useMybAiProcessingConsent as u, useMybAiSettings as d, useMybSetAiProcessingConsent as f, useMybUpdateAiSettings as p } from "../../hooks/useMyBodyShieldApi.js";
7
7
  import "../../hooks/index.js";
8
8
  import "../../components/index.js";
9
- import { useMemo as f, useState as p } from "react";
10
- import { Activity as m, Bot as h, KeyRound as g, Save as _, Sparkles as v } from "lucide-react";
11
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
12
- import { Button as x, GlassCard as S, Input as C, Label as w, Select as T, SelectContent as E, SelectItem as D, SelectTrigger as O, SelectValue as k, Spinner as A } from "@burdenoff/fe-libs/ui";
9
+ import { IconChip as m } from "../_shared/visuals.js";
10
+ import { useMemo as h, useState as g } from "react";
11
+ import { Activity as _, Bot as v, KeyRound as y, Save as b, ShieldCheck as x, ShieldOff as S, Sparkles as C } from "lucide-react";
12
+ import { jsx as w, jsxs as T } from "react/jsx-runtime";
13
+ import { Button as E, GlassCard as D, Input as O, Label as k, Select as A, SelectContent as j, SelectItem as M, SelectTrigger as N, SelectValue as P, Spinner as F, Switch as I } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/mybodyshield/pages/ai/AiControlsPage.tsx
14
- var j = [
15
+ var L = [
15
16
  "GEMINI",
16
17
  "OPENAI",
17
18
  "ANTHROPIC"
18
- ], M = {
19
+ ], R = {
19
20
  GEMINI: "Google Gemini",
20
21
  OPENAI: "OpenAI",
21
22
  ANTHROPIC: "Anthropic"
22
- }, N = {
23
+ }, z = {
23
24
  GEMINI: "gemini-2.5-flash",
24
25
  OPENAI: "gpt-4o-mini",
25
26
  ANTHROPIC: "claude-3-5-haiku-latest"
26
- }, P = (e) => ({
27
+ }, B = (e) => ({
27
28
  provider: e.provider,
28
29
  modelName: e.modelName,
29
30
  dailyAllowance: String(e.dailyAllowance),
30
31
  providerKey: "",
31
32
  clearKey: !1
32
33
  });
33
- function F({ settings: l, logsQuery: u }) {
34
- let F = d(), [I, L] = p(() => P(l)), [R, z] = p(null), [B, V] = p(!1), H = f(() => {
34
+ function V() {
35
+ let e = u(), t = f();
36
+ if (e.isLoading) return /* @__PURE__ */ w(o, { label: "Loading AI-processing consent…" });
37
+ if (e.isError) return /* @__PURE__ */ w(a, {
38
+ message: "We couldn't load your AI-processing consent.",
39
+ onRetry: () => e.refetch()
40
+ });
41
+ let n = e.data?.enabled ?? !1;
42
+ return /* @__PURE__ */ w(D, {
43
+ treatment: "plain",
44
+ className: "p-5",
45
+ children: /* @__PURE__ */ T("div", {
46
+ className: "flex flex-wrap items-start justify-between gap-4",
47
+ children: [/* @__PURE__ */ T("div", {
48
+ className: "flex min-w-0 items-start gap-3",
49
+ children: [/* @__PURE__ */ w(m, { icon: w(n ? x : S, { className: "h-5 w-5" }) }), /* @__PURE__ */ T("div", {
50
+ className: "min-w-0",
51
+ children: [
52
+ /* @__PURE__ */ w("p", {
53
+ className: "text-sm font-medium text-text-primary",
54
+ children: n ? "AI may read your health data" : "AI processing is withdrawn — summaries will not run"
55
+ }),
56
+ /* @__PURE__ */ w("p", {
57
+ className: "mt-0.5 text-sm text-text-secondary",
58
+ children: n ? "The provider and model below are used to turn your logged training, nutrition, recovery and body-metric data into plain-language summaries." : "The provider and model below are saved, but no AI summary is generated while consent is withdrawn. Rule-based guidance and every manual logging path keep working."
59
+ }),
60
+ e.data?.withdrawnAt && !n && /* @__PURE__ */ T("p", {
61
+ className: "mt-1 text-xs text-text-tertiary",
62
+ children: [
63
+ "Withdrawn ",
64
+ i(e.data.withdrawnAt),
65
+ "."
66
+ ]
67
+ }),
68
+ e.data?.acceptedAt && n && /* @__PURE__ */ T("p", {
69
+ className: "mt-1 text-xs text-text-tertiary",
70
+ children: [
71
+ "Consent given ",
72
+ i(e.data.acceptedAt),
73
+ "."
74
+ ]
75
+ })
76
+ ]
77
+ })]
78
+ }), /* @__PURE__ */ w(I, {
79
+ checked: n,
80
+ onCheckedChange: (e) => t.mutate(e),
81
+ disabled: t.isPending,
82
+ "aria-label": "Allow AI to read my health data"
83
+ })]
84
+ })
85
+ });
86
+ }
87
+ function H({ settings: l, logsQuery: u }) {
88
+ let d = p(), [f, m] = g(() => B(l)), [x, S] = g(null), [I, H] = g(!1), U = h(() => {
35
89
  let { dailyAllowance: e, dailyAllowanceUsed: t } = l;
36
90
  return e <= 0 ? 0 : Math.min(100, Math.round(t / e * 100));
37
- }, [l]), U = l.dailyAllowanceUsed >= l.dailyAllowance, W = (e) => {
38
- L((t) => ({
91
+ }, [l]), W = l.dailyAllowanceUsed >= l.dailyAllowance, G = (e) => {
92
+ m((t) => ({
39
93
  ...t,
40
94
  provider: e,
41
- modelName: t.modelName === N[t.provider] ? N[e] : t.modelName
95
+ modelName: t.modelName === z[t.provider] ? z[e] : t.modelName
42
96
  }));
43
97
  };
44
- return /* @__PURE__ */ b(e, {
98
+ return /* @__PURE__ */ T(e, {
45
99
  title: "AI controls",
46
100
  description: "Choose your AI provider, model and daily allowance.",
47
- actions: /* @__PURE__ */ b(x, {
101
+ actions: /* @__PURE__ */ T(E, {
48
102
  className: "gap-2",
49
103
  onClick: async () => {
50
- z(null), V(!1);
51
- let e = Number(I.dailyAllowance);
104
+ S(null), H(!1);
105
+ let e = Number(f.dailyAllowance);
52
106
  if (!Number.isInteger(e) || e < 0 || e > 1e4) {
53
- z("Daily allowance must be an integer between 0 and 10,000.");
107
+ S("Daily allowance must be an integer between 0 and 10,000.");
54
108
  return;
55
109
  }
56
- if (!I.modelName.trim()) {
57
- z("Model name is required.");
110
+ if (!f.modelName.trim()) {
111
+ S("Model name is required.");
58
112
  return;
59
113
  }
60
114
  let t = {
61
- provider: I.provider,
62
- modelName: I.modelName.trim(),
115
+ provider: f.provider,
116
+ modelName: f.modelName.trim(),
63
117
  dailyAllowance: e
64
118
  };
65
- I.clearKey ? t.providerKey = "" : I.providerKey.trim() && (t.providerKey = I.providerKey.trim());
119
+ f.clearKey ? t.providerKey = "" : f.providerKey.trim() && (t.providerKey = f.providerKey.trim());
66
120
  try {
67
- await F.mutateAsync(t), V(!0), L((e) => ({
121
+ await d.mutateAsync(t), H(!0), m((e) => ({
68
122
  ...e,
69
123
  providerKey: "",
70
124
  clearKey: !1
71
125
  }));
72
126
  } catch (e) {
73
- z(e instanceof Error ? e.message : "Failed to save AI settings.");
127
+ S(e instanceof Error ? e.message : "Failed to save AI settings.");
74
128
  }
75
129
  },
76
- disabled: F.isPending,
77
- children: [F.isPending ? /* @__PURE__ */ y(A, { size: "sm" }) : /* @__PURE__ */ y(_, { className: "h-4 w-4" }), "Save settings"]
130
+ disabled: d.isPending,
131
+ children: [d.isPending ? /* @__PURE__ */ w(F, { size: "sm" }) : /* @__PURE__ */ w(b, { className: "h-4 w-4" }), "Save settings"]
78
132
  }),
79
133
  children: [
80
- /* @__PURE__ */ b("div", {
134
+ /* @__PURE__ */ w(s, {
135
+ title: "AI-processing consent",
136
+ description: "AI summaries only run while you allow AI to read your health data.",
137
+ children: /* @__PURE__ */ w(V, {})
138
+ }),
139
+ /* @__PURE__ */ T("div", {
81
140
  className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
82
141
  children: [
83
- /* @__PURE__ */ y(t, {
84
- icon: /* @__PURE__ */ y(v, { className: "h-5 w-5" }),
142
+ /* @__PURE__ */ w(t, {
143
+ icon: /* @__PURE__ */ w(C, { className: "h-5 w-5" }),
85
144
  value: l.provider,
86
145
  label: "Current provider"
87
146
  }),
88
- /* @__PURE__ */ y(t, {
89
- icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
147
+ /* @__PURE__ */ w(t, {
148
+ icon: /* @__PURE__ */ w(v, { className: "h-5 w-5" }),
90
149
  value: l.modelName,
91
150
  label: "Current model"
92
151
  }),
93
- /* @__PURE__ */ y(t, {
94
- icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" }),
152
+ /* @__PURE__ */ w(t, {
153
+ icon: /* @__PURE__ */ w(_, { className: "h-5 w-5" }),
95
154
  value: `${l.dailyAllowanceUsed} / ${l.dailyAllowance}`,
96
155
  label: "Daily allowance used",
97
- accent: U ? "error" : H >= 80 ? "warning" : "success",
98
- footer: /* @__PURE__ */ b("p", {
156
+ accent: W ? "error" : U >= 80 ? "warning" : "success",
157
+ footer: /* @__PURE__ */ T("p", {
99
158
  className: "text-xs text-text-tertiary",
100
159
  children: ["Resets ", i(l.allowanceResetAt)]
101
160
  })
102
161
  })
103
162
  ]
104
163
  }),
105
- /* @__PURE__ */ y(s, {
164
+ /* @__PURE__ */ w(s, {
106
165
  title: "Configuration",
107
166
  description: "Changes apply to your account only.",
108
- children: /* @__PURE__ */ b(S, {
167
+ children: /* @__PURE__ */ T(D, {
109
168
  treatment: "plain",
110
169
  className: "p-6",
111
170
  children: [
112
- /* @__PURE__ */ b("div", {
171
+ /* @__PURE__ */ T("div", {
113
172
  className: "grid gap-6 md:grid-cols-2",
114
173
  children: [
115
- /* @__PURE__ */ b("div", {
174
+ /* @__PURE__ */ T("div", {
116
175
  className: "space-y-1.5",
117
176
  children: [
118
- /* @__PURE__ */ y(w, {
177
+ /* @__PURE__ */ w(k, {
119
178
  htmlFor: "myb-ai-provider",
120
179
  children: "AI provider"
121
180
  }),
122
- /* @__PURE__ */ b(T, {
123
- value: I.provider,
124
- onValueChange: (e) => W(e),
125
- children: [/* @__PURE__ */ y(O, {
181
+ /* @__PURE__ */ T(A, {
182
+ value: f.provider,
183
+ onValueChange: (e) => G(e),
184
+ children: [/* @__PURE__ */ w(N, {
126
185
  id: "myb-ai-provider",
127
- children: /* @__PURE__ */ y(k, {})
128
- }), /* @__PURE__ */ y(E, { children: j.map((e) => /* @__PURE__ */ y(D, {
186
+ children: /* @__PURE__ */ w(P, {})
187
+ }), /* @__PURE__ */ w(j, { children: L.map((e) => /* @__PURE__ */ w(M, {
129
188
  value: e,
130
- children: M[e]
189
+ children: R[e]
131
190
  }, e)) })]
132
191
  }),
133
- /* @__PURE__ */ y("p", {
192
+ /* @__PURE__ */ w("p", {
134
193
  className: "text-xs text-text-tertiary",
135
194
  children: "The provider used for AI-generated guidance summaries."
136
195
  })
137
196
  ]
138
197
  }),
139
- /* @__PURE__ */ b("div", {
198
+ /* @__PURE__ */ T("div", {
140
199
  className: "space-y-1.5",
141
200
  children: [
142
- /* @__PURE__ */ y(w, {
201
+ /* @__PURE__ */ w(k, {
143
202
  htmlFor: "myb-ai-model",
144
203
  children: "Model name"
145
204
  }),
146
- /* @__PURE__ */ y(C, {
205
+ /* @__PURE__ */ w(O, {
147
206
  id: "myb-ai-model",
148
- value: I.modelName,
149
- onChange: (e) => L((t) => ({
207
+ value: f.modelName,
208
+ onChange: (e) => m((t) => ({
150
209
  ...t,
151
210
  modelName: e.target.value
152
211
  })),
153
- placeholder: N[I.provider]
212
+ placeholder: z[f.provider]
154
213
  }),
155
- /* @__PURE__ */ b("p", {
214
+ /* @__PURE__ */ T("p", {
156
215
  className: "text-xs text-text-tertiary",
157
216
  children: [
158
217
  "e.g. ",
159
- N[I.provider],
218
+ z[f.provider],
160
219
  ". Must be valid for the chosen provider."
161
220
  ]
162
221
  })
163
222
  ]
164
223
  }),
165
- /* @__PURE__ */ b("div", {
224
+ /* @__PURE__ */ T("div", {
166
225
  className: "space-y-1.5",
167
226
  children: [
168
- /* @__PURE__ */ y(w, {
227
+ /* @__PURE__ */ w(k, {
169
228
  htmlFor: "myb-ai-allowance",
170
229
  children: "Daily allowance"
171
230
  }),
172
- /* @__PURE__ */ y(C, {
231
+ /* @__PURE__ */ w(O, {
173
232
  id: "myb-ai-allowance",
174
233
  type: "number",
175
234
  min: 0,
176
235
  max: 1e4,
177
236
  step: 1,
178
- value: I.dailyAllowance,
179
- onChange: (e) => L((t) => ({
237
+ value: f.dailyAllowance,
238
+ onChange: (e) => m((t) => ({
180
239
  ...t,
181
240
  dailyAllowance: e.target.value
182
241
  }))
183
242
  }),
184
- /* @__PURE__ */ y("p", {
243
+ /* @__PURE__ */ w("p", {
185
244
  className: "text-xs text-text-tertiary",
186
245
  children: "Maximum AI operations counted against your account per UTC day."
187
246
  })
188
247
  ]
189
248
  }),
190
- /* @__PURE__ */ b("div", {
249
+ /* @__PURE__ */ T("div", {
191
250
  className: "space-y-1.5",
192
251
  children: [
193
- /* @__PURE__ */ y(w, {
252
+ /* @__PURE__ */ w(k, {
194
253
  htmlFor: "myb-ai-key",
195
- children: /* @__PURE__ */ b("span", {
254
+ children: /* @__PURE__ */ T("span", {
196
255
  className: "inline-flex items-center gap-1.5",
197
- children: [/* @__PURE__ */ y(g, { className: "h-3.5 w-3.5" }), "Provider API key"]
256
+ children: [/* @__PURE__ */ w(y, { className: "h-3.5 w-3.5" }), "Provider API key"]
198
257
  })
199
258
  }),
200
- /* @__PURE__ */ y(C, {
259
+ /* @__PURE__ */ w(O, {
201
260
  id: "myb-ai-key",
202
261
  type: "password",
203
- value: I.providerKey,
204
- onChange: (e) => L((t) => ({
262
+ value: f.providerKey,
263
+ onChange: (e) => m((t) => ({
205
264
  ...t,
206
265
  providerKey: e.target.value,
207
266
  clearKey: !1
208
267
  })),
209
268
  placeholder: l.hasProviderKey ? "•••••••• (saved)" : "Optional",
210
- disabled: I.clearKey
269
+ disabled: f.clearKey
211
270
  }),
212
- /* @__PURE__ */ b("div", {
271
+ /* @__PURE__ */ T("div", {
213
272
  className: "flex items-center gap-2",
214
- children: [/* @__PURE__ */ y("input", {
273
+ children: [/* @__PURE__ */ w("input", {
215
274
  id: "myb-ai-clear-key",
216
275
  type: "checkbox",
217
- checked: I.clearKey,
218
- onChange: (e) => L((t) => ({
276
+ checked: f.clearKey,
277
+ onChange: (e) => m((t) => ({
219
278
  ...t,
220
279
  clearKey: e.target.checked,
221
280
  providerKey: ""
222
281
  })),
223
282
  className: "h-4 w-4 rounded border-border-seam text-accent focus:ring-accent"
224
- }), /* @__PURE__ */ y(w, {
283
+ }), /* @__PURE__ */ w(k, {
225
284
  htmlFor: "myb-ai-clear-key",
226
285
  className: "text-xs font-normal text-text-secondary",
227
286
  children: "Clear saved key and fall back to the workspace default"
@@ -231,49 +290,49 @@ function F({ settings: l, logsQuery: u }) {
231
290
  })
232
291
  ]
233
292
  }),
234
- B && /* @__PURE__ */ y("p", {
293
+ I && /* @__PURE__ */ w("p", {
235
294
  className: "mt-4 text-sm text-status-success-text",
236
295
  children: "AI settings saved."
237
296
  }),
238
- R && /* @__PURE__ */ y("p", {
297
+ x && /* @__PURE__ */ w("p", {
239
298
  className: "mt-4 text-sm text-status-error-text",
240
- children: R
299
+ children: x
241
300
  })
242
301
  ]
243
302
  })
244
303
  }),
245
- /* @__PURE__ */ y(s, {
304
+ /* @__PURE__ */ w(s, {
246
305
  title: "Operation history",
247
306
  description: "Redacted audit log of AI operations for your account.",
248
- children: u.isLoading ? /* @__PURE__ */ y(o, { label: "Loading operation history…" }) : u.isError ? /* @__PURE__ */ y(a, {
307
+ children: u.isLoading ? /* @__PURE__ */ w(o, { label: "Loading operation history…" }) : u.isError ? /* @__PURE__ */ w(a, {
249
308
  message: "We couldn't load the operation log.",
250
309
  onRetry: () => u.refetch()
251
- }) : u.data?.items.length === 0 ? /* @__PURE__ */ b(S, {
310
+ }) : u.data?.items.length === 0 ? /* @__PURE__ */ T(D, {
252
311
  treatment: "plain",
253
312
  className: "p-8 text-center",
254
313
  children: [
255
- /* @__PURE__ */ y(v, { className: "mx-auto h-8 w-8 text-text-tertiary" }),
256
- /* @__PURE__ */ y("p", {
314
+ /* @__PURE__ */ w(C, { className: "mx-auto h-8 w-8 text-text-tertiary" }),
315
+ /* @__PURE__ */ w("p", {
257
316
  className: "mt-3 text-sm font-medium text-text-primary",
258
317
  children: "No AI operations yet"
259
318
  }),
260
- /* @__PURE__ */ y("p", {
319
+ /* @__PURE__ */ w("p", {
261
320
  className: "mt-1 text-xs text-text-tertiary",
262
321
  children: "Operations appear here once AI-generated guidance runs."
263
322
  })
264
323
  ]
265
- }) : /* @__PURE__ */ y(c, {
324
+ }) : /* @__PURE__ */ w(c, {
266
325
  columns: [
267
326
  {
268
327
  key: "createdAt",
269
328
  header: "Time",
270
329
  sortValue: (e) => new Date(e.createdAt).getTime(),
271
- render: (e) => /* @__PURE__ */ b("div", {
330
+ render: (e) => /* @__PURE__ */ T("div", {
272
331
  className: "text-text-primary",
273
- children: [/* @__PURE__ */ y("span", {
332
+ children: [/* @__PURE__ */ w("span", {
274
333
  className: "block text-sm",
275
334
  children: n(e.createdAt)
276
- }), /* @__PURE__ */ y("span", {
335
+ }), /* @__PURE__ */ w("span", {
277
336
  className: "text-xs text-text-tertiary",
278
337
  children: i(e.createdAt)
279
338
  })]
@@ -282,7 +341,7 @@ function F({ settings: l, logsQuery: u }) {
282
341
  {
283
342
  key: "operationType",
284
343
  header: "Operation",
285
- render: (e) => /* @__PURE__ */ y("span", {
344
+ render: (e) => /* @__PURE__ */ w("span", {
286
345
  className: "text-text-secondary",
287
346
  children: r(e.operationType)
288
347
  })
@@ -290,10 +349,10 @@ function F({ settings: l, logsQuery: u }) {
290
349
  {
291
350
  key: "provider",
292
351
  header: "Provider / Model",
293
- render: (e) => /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("span", {
352
+ render: (e) => /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("span", {
294
353
  className: "block text-sm text-text-primary",
295
- children: M[e.provider]
296
- }), /* @__PURE__ */ y("span", {
354
+ children: R[e.provider]
355
+ }), /* @__PURE__ */ w("span", {
297
356
  className: "text-xs text-text-tertiary",
298
357
  children: e.modelName
299
358
  })] })
@@ -302,7 +361,7 @@ function F({ settings: l, logsQuery: u }) {
302
361
  key: "tokens",
303
362
  header: "Tokens",
304
363
  align: "right",
305
- render: (e) => /* @__PURE__ */ b("span", {
364
+ render: (e) => /* @__PURE__ */ T("span", {
306
365
  className: "tabular-nums text-text-secondary",
307
366
  children: [
308
367
  e.inputTokens ?? "—",
@@ -314,7 +373,7 @@ function F({ settings: l, logsQuery: u }) {
314
373
  {
315
374
  key: "status",
316
375
  header: "Status",
317
- render: (e) => /* @__PURE__ */ y("span", {
376
+ render: (e) => /* @__PURE__ */ w("span", {
318
377
  className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${e.status === "SUCCESS" ? "bg-status-success-bg text-status-success-text" : "bg-status-error-bg text-status-error-text"}`,
319
378
  children: r(e.status)
320
379
  })
@@ -323,10 +382,10 @@ function F({ settings: l, logsQuery: u }) {
323
382
  key: "error",
324
383
  header: "Error",
325
384
  hideOnMobile: !0,
326
- render: (e) => e.errorMessage ? /* @__PURE__ */ y("span", {
385
+ render: (e) => e.errorMessage ? /* @__PURE__ */ w("span", {
327
386
  className: "text-xs text-status-error-text",
328
387
  children: e.errorMessage
329
- }) : /* @__PURE__ */ y("span", {
388
+ }) : /* @__PURE__ */ w("span", {
330
389
  className: "text-text-tertiary",
331
390
  children: "—"
332
391
  })
@@ -343,36 +402,36 @@ function F({ settings: l, logsQuery: u }) {
343
402
  ]
344
403
  });
345
404
  }
346
- function I() {
347
- let t = u(), n = l();
348
- if (t.isLoading) return /* @__PURE__ */ y(e, {
405
+ function U() {
406
+ let t = d(), n = l();
407
+ if (t.isLoading) return /* @__PURE__ */ w(e, {
349
408
  title: "AI controls",
350
- children: /* @__PURE__ */ y(o, { label: "Loading AI settings…" })
409
+ children: /* @__PURE__ */ w(o, { label: "Loading AI settings…" })
351
410
  });
352
- if (t.isError) return /* @__PURE__ */ y(e, {
411
+ if (t.isError) return /* @__PURE__ */ w(e, {
353
412
  title: "AI controls",
354
- children: /* @__PURE__ */ y(a, {
413
+ children: /* @__PURE__ */ w(a, {
355
414
  title: "Could not load AI settings",
356
415
  message: "We couldn't fetch your AI configuration. Try again in a moment.",
357
416
  onRetry: () => t.refetch()
358
417
  })
359
418
  });
360
419
  let r = t.data;
361
- if (!r) return /* @__PURE__ */ y(e, {
420
+ if (!r) return /* @__PURE__ */ w(e, {
362
421
  title: "AI controls",
363
- children: /* @__PURE__ */ y(a, {
422
+ children: /* @__PURE__ */ w(a, {
364
423
  title: "Could not load AI settings",
365
424
  message: "We couldn't fetch your AI configuration. Try again in a moment.",
366
425
  onRetry: () => t.refetch()
367
426
  })
368
427
  });
369
428
  let i = `${r.id}:${r.updatedAt}`;
370
- return /* @__PURE__ */ y(F, {
429
+ return /* @__PURE__ */ w(H, {
371
430
  settings: r,
372
431
  logsQuery: n
373
432
  }, i);
374
433
  }
375
434
  //#endregion
376
- export { I as AiControlsPage };
435
+ export { U as AiControlsPage };
377
436
 
378
437
  //# sourceMappingURL=AiControlsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiControlsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/ai/AiControlsPage.tsx"],"sourcesContent":["/**\n * AI controls — provider, model, daily allowance and operation history.\n * @module mybodyshield/pages/ai\n *\n * BOFF-5949: per-member AI settings surface. Lets the member choose a provider\n * (Gemini / OpenAI / Anthropic), the model name, a daily operation allowance,\n * and an optional provider API key. Shows the redacted audit log of AI ops.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Sparkles, KeyRound, Bot, Activity, Save } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport { useMybAiSettings, useMybUpdateAiSettings, useMybAiOperationLogs } from '../../hooks';\nimport type {\n MybAiProvider,\n MybAiSettings,\n MybAiOperationLog,\n MybAiOperationLogConnection,\n MybUpdateAiSettingsInput,\n} from '../../types';\nimport { formatDateTime, formatRelativeTime, formatEnumLabel } from '../../utils/formatters';\nimport type { UseQueryResult } from '@tanstack/react-query';\n\nconst AI_PROVIDERS: MybAiProvider[] = ['GEMINI', 'OPENAI', 'ANTHROPIC'];\n\nconst PROVIDER_LABELS: Record<MybAiProvider, string> = {\n GEMINI: 'Google Gemini',\n OPENAI: 'OpenAI',\n ANTHROPIC: 'Anthropic',\n};\n\nconst DEFAULT_MODELS: Record<MybAiProvider, string> = {\n GEMINI: 'gemini-2.5-flash',\n OPENAI: 'gpt-4o-mini',\n ANTHROPIC: 'claude-3-5-haiku-latest',\n};\n\ninterface AiSettingsForm {\n provider: MybAiProvider;\n modelName: string;\n dailyAllowance: string;\n providerKey: string;\n clearKey: boolean;\n}\n\nconst makeFormFromSettings = (settings: MybAiSettings): AiSettingsForm => ({\n provider: settings.provider,\n modelName: settings.modelName,\n dailyAllowance: String(settings.dailyAllowance),\n providerKey: '',\n clearKey: false,\n});\n\ninterface AiControlsPageInnerProps {\n settings: MybAiSettings;\n logsQuery: UseQueryResult<MybAiOperationLogConnection, Error>;\n}\n\nfunction AiControlsPageInner({ settings, logsQuery }: AiControlsPageInnerProps) {\n const updateMutation = useMybUpdateAiSettings();\n const [form, setForm] = useState<AiSettingsForm>(() => makeFormFromSettings(settings));\n const [err, setErr] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const usagePercent = useMemo(() => {\n const { dailyAllowance, dailyAllowanceUsed } = settings;\n if (dailyAllowance <= 0) return 0;\n return Math.min(100, Math.round((dailyAllowanceUsed / dailyAllowance) * 100));\n }, [settings]);\n\n const isExhausted = settings.dailyAllowanceUsed >= settings.dailyAllowance;\n\n const handleProviderChange = (provider: MybAiProvider) => {\n setForm((f) => ({\n ...f,\n provider,\n modelName:\n f.modelName === DEFAULT_MODELS[f.provider] ? DEFAULT_MODELS[provider] : f.modelName,\n }));\n };\n\n const submit = async () => {\n setErr(null);\n setSaved(false);\n\n const allowance = Number(form.dailyAllowance);\n if (!Number.isInteger(allowance) || allowance < 0 || allowance > 10_000) {\n setErr('Daily allowance must be an integer between 0 and 10,000.');\n return;\n }\n if (!form.modelName.trim()) {\n setErr('Model name is required.');\n return;\n }\n\n const input: MybUpdateAiSettingsInput = {\n provider: form.provider,\n modelName: form.modelName.trim(),\n dailyAllowance: allowance,\n };\n\n if (form.clearKey) {\n input.providerKey = '';\n } else if (form.providerKey.trim()) {\n input.providerKey = form.providerKey.trim();\n }\n\n try {\n await updateMutation.mutateAsync(input);\n setSaved(true);\n setForm((f) => ({ ...f, providerKey: '', clearKey: false }));\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to save AI settings.');\n }\n };\n\n const logColumns: Column<MybAiOperationLog>[] = [\n {\n key: 'createdAt',\n header: 'Time',\n sortValue: (r) => new Date(r.createdAt).getTime(),\n render: (r) => (\n <div className=\"text-text-primary\">\n <span className=\"block text-sm\">{formatDateTime(r.createdAt)}</span>\n <span className=\"text-xs text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>\n </div>\n ),\n },\n {\n key: 'operationType',\n header: 'Operation',\n render: (r) => (\n <span className=\"text-text-secondary\">{formatEnumLabel(r.operationType)}</span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider / Model',\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{PROVIDER_LABELS[r.provider]}</span>\n <span className=\"text-xs text-text-tertiary\">{r.modelName}</span>\n </div>\n ),\n },\n {\n key: 'tokens',\n header: 'Tokens',\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.inputTokens ?? '—'} / {r.outputTokens ?? '—'}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'SUCCESS'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-error-bg text-status-error-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n hideOnMobile: true,\n render: (r) =>\n r.errorMessage ? (\n <span className=\"text-xs text-status-error-text\">{r.errorMessage}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AI controls\"\n description=\"Choose your AI provider, model and daily allowance.\"\n actions={\n <Button className=\"gap-2\" onClick={submit} disabled={updateMutation.isPending}>\n {updateMutation.isPending ? <Spinner size=\"sm\" /> : <Save className=\"h-4 w-4\" />}\n Save settings\n </Button>\n }\n >\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Sparkles className=\"h-5 w-5\" />}\n value={settings.provider}\n label=\"Current provider\"\n />\n <MetricsCard\n icon={<Bot className=\"h-5 w-5\" />}\n value={settings.modelName}\n label=\"Current model\"\n />\n <MetricsCard\n icon={<Activity className=\"h-5 w-5\" />}\n value={`${settings.dailyAllowanceUsed} / ${settings.dailyAllowance}`}\n label=\"Daily allowance used\"\n accent={isExhausted ? 'error' : usagePercent >= 80 ? 'warning' : 'success'}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n Resets {formatRelativeTime(settings.allowanceResetAt)}\n </p>\n }\n />\n </div>\n\n {/* Settings form */}\n <Section title=\"Configuration\" description=\"Changes apply to your account only.\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-provider\">AI provider</Label>\n <Select\n value={form.provider}\n onValueChange={(v) => handleProviderChange(v as MybAiProvider)}\n >\n <SelectTrigger id=\"myb-ai-provider\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {AI_PROVIDERS.map((p) => (\n <SelectItem key={p} value={p}>\n {PROVIDER_LABELS[p]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n The provider used for AI-generated guidance summaries.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-model\">Model name</Label>\n <Input\n id=\"myb-ai-model\"\n value={form.modelName}\n onChange={(e) => setForm((f) => ({ ...f, modelName: e.target.value }))}\n placeholder={DEFAULT_MODELS[form.provider]}\n />\n <p className=\"text-xs text-text-tertiary\">\n e.g. {DEFAULT_MODELS[form.provider]}. Must be valid for the chosen provider.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-allowance\">Daily allowance</Label>\n <Input\n id=\"myb-ai-allowance\"\n type=\"number\"\n min={0}\n max={10000}\n step={1}\n value={form.dailyAllowance}\n onChange={(e) => setForm((f) => ({ ...f, dailyAllowance: e.target.value }))}\n />\n <p className=\"text-xs text-text-tertiary\">\n Maximum AI operations counted against your account per UTC day.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-key\">\n <span className=\"inline-flex items-center gap-1.5\">\n <KeyRound className=\"h-3.5 w-3.5\" />\n Provider API key\n </span>\n </Label>\n <Input\n id=\"myb-ai-key\"\n type=\"password\"\n value={form.providerKey}\n onChange={(e) =>\n setForm((f) => ({ ...f, providerKey: e.target.value, clearKey: false }))\n }\n placeholder={settings.hasProviderKey ? '•••••••• (saved)' : 'Optional'}\n disabled={form.clearKey}\n />\n <div className=\"flex items-center gap-2\">\n <input\n id=\"myb-ai-clear-key\"\n type=\"checkbox\"\n checked={form.clearKey}\n onChange={(e) =>\n setForm((f) => ({ ...f, clearKey: e.target.checked, providerKey: '' }))\n }\n className=\"h-4 w-4 rounded border-border-seam text-accent focus:ring-accent\"\n />\n <Label\n htmlFor=\"myb-ai-clear-key\"\n className=\"text-xs font-normal text-text-secondary\"\n >\n Clear saved key and fall back to the workspace default\n </Label>\n </div>\n </div>\n </div>\n\n {saved && <p className=\"mt-4 text-sm text-status-success-text\">AI settings saved.</p>}\n {err && <p className=\"mt-4 text-sm text-status-error-text\">{err}</p>}\n </GlassCard>\n </Section>\n\n {/* Operation log */}\n <Section\n title=\"Operation history\"\n description=\"Redacted audit log of AI operations for your account.\"\n >\n {logsQuery.isLoading ? (\n <LoadingView label=\"Loading operation history…\" />\n ) : logsQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the operation log.\"\n onRetry={() => logsQuery.refetch()}\n />\n ) : logsQuery.data?.items.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Sparkles className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">No AI operations yet</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n Operations appear here once AI-generated guidance runs.\n </p>\n </GlassCard>\n ) : (\n <DataTable\n columns={logColumns}\n rows={logsQuery.data?.items ?? []}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'createdAt', dir: 'desc' }}\n />\n )}\n </Section>\n </PageLayout>\n );\n}\n\nexport function AiControlsPage() {\n const settingsQuery = useMybAiSettings();\n const logsQuery = useMybAiOperationLogs();\n\n if (settingsQuery.isLoading) {\n return (\n <PageLayout title=\"AI controls\">\n <LoadingView label=\"Loading AI settings…\" />\n </PageLayout>\n );\n }\n\n if (settingsQuery.isError) {\n return (\n <PageLayout title=\"AI controls\">\n <ErrorView\n title=\"Could not load AI settings\"\n message=\"We couldn't fetch your AI configuration. Try again in a moment.\"\n onRetry={() => settingsQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const settings = settingsQuery.data;\n if (!settings) {\n return (\n <PageLayout title=\"AI controls\">\n <ErrorView\n title=\"Could not load AI settings\"\n message=\"We couldn't fetch your AI configuration. Try again in a moment.\"\n onRetry={() => settingsQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n // Key the inner component on the settings revision so form state re-seeds\n // when the source settings change, without synchronously setting state in\n // an effect (which triggers cascading renders).\n const settingsKey = `${settings.id}:${settings.updatedAt}`;\n\n return <AiControlsPageInner key={settingsKey} settings={settings} logsQuery={logsQuery} />;\n}\n"],"mappings":";;;;;;;;;;;;;AA2CA,IAAM,IAAgC;CAAC;CAAU;CAAU;CAAY,EAEjE,IAAiD;CACrD,QAAQ;CACR,QAAQ;CACR,WAAW;CACZ,EAEK,IAAgD;CACpD,QAAQ;CACR,QAAQ;CACR,WAAW;CACZ,EAUK,KAAwB,OAA6C;CACzE,UAAU,EAAS;CACnB,WAAW,EAAS;CACpB,gBAAgB,OAAO,EAAS,eAAe;CAC/C,aAAa;CACb,UAAU;CACX;AAOD,SAAS,EAAoB,EAAE,aAAU,gBAAuC;CAC9E,IAAM,IAAiB,GAAwB,EACzC,CAAC,GAAM,KAAW,QAA+B,EAAqB,EAAS,CAAC,EAChF,CAAC,GAAK,KAAU,EAAwB,KAAK,EAC7C,CAAC,GAAO,KAAY,EAAS,GAAM,EAEnC,IAAe,QAAc;EACjC,IAAM,EAAE,mBAAgB,0BAAuB;AAE/C,SADI,KAAkB,IAAU,IACzB,KAAK,IAAI,KAAK,KAAK,MAAO,IAAqB,IAAkB,IAAI,CAAC;IAC5E,CAAC,EAAS,CAAC,EAER,IAAc,EAAS,sBAAsB,EAAS,gBAEtD,KAAwB,MAA4B;AACxD,KAAS,OAAO;GACd,GAAG;GACH;GACA,WACE,EAAE,cAAc,EAAe,EAAE,YAAY,EAAe,KAAY,EAAE;GAC7E,EAAE;;AAyGL,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,WAAU;GAAQ,SA3GjB,YAAY;AAEzB,IADA,EAAO,KAAK,EACZ,EAAS,GAAM;IAEf,IAAM,IAAY,OAAO,EAAK,eAAe;AAC7C,QAAI,CAAC,OAAO,UAAU,EAAU,IAAI,IAAY,KAAK,IAAY,KAAQ;AACvE,OAAO,2DAA2D;AAClE;;AAEF,QAAI,CAAC,EAAK,UAAU,MAAM,EAAE;AAC1B,OAAO,0BAA0B;AACjC;;IAGF,IAAM,IAAkC;KACtC,UAAU,EAAK;KACf,WAAW,EAAK,UAAU,MAAM;KAChC,gBAAgB;KACjB;AAED,IAAI,EAAK,WACP,EAAM,cAAc,KACX,EAAK,YAAY,MAAM,KAChC,EAAM,cAAc,EAAK,YAAY,MAAM;AAG7C,QAAI;AAGF,KAFA,MAAM,EAAe,YAAY,EAAM,EACvC,EAAS,GAAK,EACd,GAAS,OAAO;MAAE,GAAG;MAAG,aAAa;MAAI,UAAU;MAAO,EAAE;aACrD,GAAG;AACV,OAAO,aAAa,QAAQ,EAAE,UAAU,8BAA8B;;;GA4EzB,UAAU,EAAe;aAApE,CACG,EAAe,YAAY,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA,GAAG,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAC,gBAE1E;;YAPb;GAWE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;MACtC,OAAO,EAAS;MAChB,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,OAAO,EAAS;MAChB,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;MACtC,OAAO,GAAG,EAAS,mBAAmB,KAAK,EAAS;MACpD,OAAM;MACN,QAAQ,IAAc,UAAU,KAAgB,KAAK,YAAY;MACjE,QACE,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA0C,WAChC,EAAmB,EAAS,iBAAiB,CACnD;;MAEN,CAAA;KACE;;GAGN,kBAAC,GAAD;IAAS,OAAM;IAAgB,aAAY;cACzC,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WAAO,SAAQ;qBAAkB;WAAmB,CAAA;UACpD,kBAAC,GAAD;WACE,OAAO,EAAK;WACZ,gBAAgB,MAAM,EAAqB,EAAmB;qBAFhE,CAIE,kBAAC,GAAD;YAAe,IAAG;sBAChB,kBAAC,GAAD,EAAe,CAAA;YACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;YAAoB,OAAO;sBACxB,EAAgB;YACN,EAFI,EAEJ,CACb,EACY,CAAA,CACT;;UACT,kBAAC,KAAD;WAAG,WAAU;qBAA6B;WAEtC,CAAA;UACA;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WAAO,SAAQ;qBAAe;WAAkB,CAAA;UAChD,kBAAC,GAAD;WACE,IAAG;WACH,OAAO,EAAK;WACZ,WAAW,MAAM,GAAS,OAAO;YAAE,GAAG;YAAG,WAAW,EAAE,OAAO;YAAO,EAAE;WACtE,aAAa,EAAe,EAAK;WACjC,CAAA;UACF,kBAAC,KAAD;WAAG,WAAU;qBAAb;YAA0C;YAClC,EAAe,EAAK;YAAU;YAClC;;UACA;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WAAO,SAAQ;qBAAmB;WAAuB,CAAA;UACzD,kBAAC,GAAD;WACE,IAAG;WACH,MAAK;WACL,KAAK;WACL,KAAK;WACL,MAAM;WACN,OAAO,EAAK;WACZ,WAAW,MAAM,GAAS,OAAO;YAAE,GAAG;YAAG,gBAAgB,EAAE,OAAO;YAAO,EAAE;WAC3E,CAAA;UACF,kBAAC,KAAD;WAAG,WAAU;qBAA6B;WAEtC,CAAA;UACA;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WAAO,SAAQ;qBACb,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EAAA,mBAE/B;;WACD,CAAA;UACR,kBAAC,GAAD;WACE,IAAG;WACH,MAAK;WACL,OAAO,EAAK;WACZ,WAAW,MACT,GAAS,OAAO;YAAE,GAAG;YAAG,aAAa,EAAE,OAAO;YAAO,UAAU;YAAO,EAAE;WAE1E,aAAa,EAAS,iBAAiB,qBAAqB;WAC5D,UAAU,EAAK;WACf,CAAA;UACF,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,SAAD;YACE,IAAG;YACH,MAAK;YACL,SAAS,EAAK;YACd,WAAW,MACT,GAAS,OAAO;aAAE,GAAG;aAAG,UAAU,EAAE,OAAO;aAAS,aAAa;aAAI,EAAE;YAEzE,WAAU;YACV,CAAA,EACF,kBAAC,GAAD;YACE,SAAQ;YACR,WAAU;sBACX;YAEO,CAAA,CACJ;;UACF;;QACF;;MAEL,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAwC;OAAsB,CAAA;MACpF,KAAO,kBAAC,KAAD;OAAG,WAAU;iBAAuC;OAAQ,CAAA;MAC1D;;IACJ,CAAA;GAGV,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;cAEX,EAAU,YACT,kBAAC,GAAD,EAAa,OAAM,8BAA+B,CAAA,GAChD,EAAU,UACZ,kBAAC,GAAD;KACE,SAAQ;KACR,eAAe,EAAU,SAAS;KAClC,CAAA,GACA,EAAU,MAAM,MAAM,WAAW,IACnC,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,GAAD,EAAU,WAAU,sCAAuC,CAAA;MAC3D,kBAAC,KAAD;OAAG,WAAU;iBAA6C;OAAwB,CAAA;MAClF,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAE3C,CAAA;MACM;SAEZ,kBAAC,GAAD;KACE,SA9NsC;MAC9C;OACE,KAAK;OACL,QAAQ;OACR,YAAY,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS;OACjD,SAAS,MACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAiB,EAAe,EAAE,UAAU;SAAQ,CAAA,EACpE,kBAAC,QAAD;SAAM,WAAU;mBAA8B,EAAmB,EAAE,UAAU;SAAQ,CAAA,CACjF;;OAET;MACD;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAAuB,EAAgB,EAAE,cAAc;QAAQ,CAAA;OAElF;MACD;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAmC,EAAgB,EAAE;QAAiB,CAAA,EACtF,kBAAC,QAAD;QAAM,WAAU;kBAA8B,EAAE;QAAiB,CAAA,CAC7D,EAAA,CAAA;OAET;MACD;OACE,KAAK;OACL,QAAQ;OACR,OAAO;OACP,SAAS,MACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAE,eAAe;SAAI;SAAI,EAAE,gBAAgB;SACvC;;OAEV;MACD;OACE,KAAK;OACL,QAAQ;OACR,SAAS,MACP,kBAAC,QAAD;QACE,WAAW,yEACT,EAAE,WAAW,YACT,kDACA;kBAGL,EAAgB,EAAE,OAAO;QACrB,CAAA;OAEV;MACD;OACE,KAAK;OACL,QAAQ;OACR,cAAc;OACd,SAAS,MACP,EAAE,eACA,kBAAC,QAAD;QAAM,WAAU;kBAAkC,EAAE;QAAoB,CAAA,GAExE,kBAAC,QAAD;QAAM,WAAU;kBAAqB;QAAQ,CAAA;OAElD;MACF;KA8JS,MAAM,EAAU,MAAM,SAAS,EAAE;KACjC,SAAS,MAAM,EAAE;KACjB,aAAa;MAAE,KAAK;MAAa,KAAK;MAAQ;KAC9C,CAAA;IAEI,CAAA;GACC;;;AAIjB,SAAgB,IAAiB;CAC/B,IAAM,IAAgB,GAAkB,EAClC,IAAY,GAAuB;AAEzC,KAAI,EAAc,UAChB,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,wBAAyB,CAAA;EACjC,CAAA;AAIjB,KAAI,EAAc,QAChB,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAc,SAAS;GACtC,CAAA;EACS,CAAA;CAIjB,IAAM,IAAW,EAAc;AAC/B,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAc,SAAS;GACtC,CAAA;EACS,CAAA;CAOjB,IAAM,IAAc,GAAG,EAAS,GAAG,GAAG,EAAS;AAE/C,QAAO,kBAAC,GAAD;EAAiD;EAAqB;EAAa,EAAzD,EAAyD"}
1
+ {"version":3,"file":"AiControlsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/ai/AiControlsPage.tsx"],"sourcesContent":["/**\n * AI controls — provider, model, daily allowance and operation history.\n * @module mybodyshield/pages/ai\n *\n * BOFF-5949: per-member AI settings surface. Lets the member choose a provider\n * (Gemini / OpenAI / Anthropic), the model name, a daily operation allowance,\n * and an optional provider API key. Shows the redacted audit log of AI ops.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Sparkles, KeyRound, Bot, Activity, Save, ShieldCheck, ShieldOff } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n Switch,\n GlassCard,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport {\n useMybAiSettings,\n useMybUpdateAiSettings,\n useMybAiOperationLogs,\n useMybAiProcessingConsent,\n useMybSetAiProcessingConsent,\n} from '../../hooks';\nimport type {\n MybAiProvider,\n MybAiSettings,\n MybAiOperationLog,\n MybAiOperationLogConnection,\n MybUpdateAiSettingsInput,\n} from '../../types';\nimport { formatDateTime, formatRelativeTime, formatEnumLabel } from '../../utils/formatters';\nimport { IconChip } from '../_shared/visuals';\nimport type { UseQueryResult } from '@tanstack/react-query';\n\nconst AI_PROVIDERS: MybAiProvider[] = ['GEMINI', 'OPENAI', 'ANTHROPIC'];\n\nconst PROVIDER_LABELS: Record<MybAiProvider, string> = {\n GEMINI: 'Google Gemini',\n OPENAI: 'OpenAI',\n ANTHROPIC: 'Anthropic',\n};\n\nconst DEFAULT_MODELS: Record<MybAiProvider, string> = {\n GEMINI: 'gemini-2.5-flash',\n OPENAI: 'gpt-4o-mini',\n ANTHROPIC: 'claude-3-5-haiku-latest',\n};\n\ninterface AiSettingsForm {\n provider: MybAiProvider;\n modelName: string;\n dailyAllowance: string;\n providerKey: string;\n clearKey: boolean;\n}\n\nconst makeFormFromSettings = (settings: MybAiSettings): AiSettingsForm => ({\n provider: settings.provider,\n modelName: settings.modelName,\n dailyAllowance: String(settings.dailyAllowance),\n providerKey: '',\n clearKey: false,\n});\n\n/**\n * AI-processing consent gate (BOFF-3838).\n *\n * The settings on this page only take effect while consent is granted: the\n * service checks `mybMyAiProcessingConsent` before it will generate a guidance\n * summary, and returns a null summary without it. Surfacing the state here (and\n * not only under Settings) stops a member from configuring a provider and model\n * that can never run. Reads and writes the same query key as the Settings page,\n * so the two stay in sync.\n */\nfunction AiConsentGate() {\n const consent = useMybAiProcessingConsent();\n const setConsent = useMybSetAiProcessingConsent();\n\n if (consent.isLoading) {\n return <LoadingView label=\"Loading AI-processing consent…\" />;\n }\n\n if (consent.isError) {\n return (\n <ErrorView\n message=\"We couldn't load your AI-processing consent.\"\n onRetry={() => consent.refetch()}\n />\n );\n }\n\n const enabled = consent.data?.enabled ?? false;\n\n return (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip\n icon={enabled ? <ShieldCheck className=\"h-5 w-5\" /> : <ShieldOff className=\"h-5 w-5\" />}\n />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {enabled\n ? 'AI may read your health data'\n : 'AI processing is withdrawn — summaries will not run'}\n </p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {enabled\n ? 'The provider and model below are used to turn your logged training, nutrition, recovery and body-metric data into plain-language summaries.'\n : 'The provider and model below are saved, but no AI summary is generated while consent is withdrawn. Rule-based guidance and every manual logging path keep working.'}\n </p>\n {consent.data?.withdrawnAt && !enabled && (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n Withdrawn {formatRelativeTime(consent.data.withdrawnAt)}.\n </p>\n )}\n {consent.data?.acceptedAt && enabled && (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n Consent given {formatRelativeTime(consent.data.acceptedAt)}.\n </p>\n )}\n </div>\n </div>\n <Switch\n checked={enabled}\n onCheckedChange={(checked) => setConsent.mutate(checked)}\n disabled={setConsent.isPending}\n aria-label=\"Allow AI to read my health data\"\n />\n </div>\n </GlassCard>\n );\n}\n\ninterface AiControlsPageInnerProps {\n settings: MybAiSettings;\n logsQuery: UseQueryResult<MybAiOperationLogConnection, Error>;\n}\n\nfunction AiControlsPageInner({ settings, logsQuery }: AiControlsPageInnerProps) {\n const updateMutation = useMybUpdateAiSettings();\n const [form, setForm] = useState<AiSettingsForm>(() => makeFormFromSettings(settings));\n const [err, setErr] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const usagePercent = useMemo(() => {\n const { dailyAllowance, dailyAllowanceUsed } = settings;\n if (dailyAllowance <= 0) return 0;\n return Math.min(100, Math.round((dailyAllowanceUsed / dailyAllowance) * 100));\n }, [settings]);\n\n const isExhausted = settings.dailyAllowanceUsed >= settings.dailyAllowance;\n\n const handleProviderChange = (provider: MybAiProvider) => {\n setForm((f) => ({\n ...f,\n provider,\n modelName:\n f.modelName === DEFAULT_MODELS[f.provider] ? DEFAULT_MODELS[provider] : f.modelName,\n }));\n };\n\n const submit = async () => {\n setErr(null);\n setSaved(false);\n\n const allowance = Number(form.dailyAllowance);\n if (!Number.isInteger(allowance) || allowance < 0 || allowance > 10_000) {\n setErr('Daily allowance must be an integer between 0 and 10,000.');\n return;\n }\n if (!form.modelName.trim()) {\n setErr('Model name is required.');\n return;\n }\n\n const input: MybUpdateAiSettingsInput = {\n provider: form.provider,\n modelName: form.modelName.trim(),\n dailyAllowance: allowance,\n };\n\n if (form.clearKey) {\n input.providerKey = '';\n } else if (form.providerKey.trim()) {\n input.providerKey = form.providerKey.trim();\n }\n\n try {\n await updateMutation.mutateAsync(input);\n setSaved(true);\n setForm((f) => ({ ...f, providerKey: '', clearKey: false }));\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to save AI settings.');\n }\n };\n\n const logColumns: Column<MybAiOperationLog>[] = [\n {\n key: 'createdAt',\n header: 'Time',\n sortValue: (r) => new Date(r.createdAt).getTime(),\n render: (r) => (\n <div className=\"text-text-primary\">\n <span className=\"block text-sm\">{formatDateTime(r.createdAt)}</span>\n <span className=\"text-xs text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>\n </div>\n ),\n },\n {\n key: 'operationType',\n header: 'Operation',\n render: (r) => (\n <span className=\"text-text-secondary\">{formatEnumLabel(r.operationType)}</span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider / Model',\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{PROVIDER_LABELS[r.provider]}</span>\n <span className=\"text-xs text-text-tertiary\">{r.modelName}</span>\n </div>\n ),\n },\n {\n key: 'tokens',\n header: 'Tokens',\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.inputTokens ?? '—'} / {r.outputTokens ?? '—'}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'SUCCESS'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-error-bg text-status-error-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n hideOnMobile: true,\n render: (r) =>\n r.errorMessage ? (\n <span className=\"text-xs text-status-error-text\">{r.errorMessage}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"AI controls\"\n description=\"Choose your AI provider, model and daily allowance.\"\n actions={\n <Button className=\"gap-2\" onClick={submit} disabled={updateMutation.isPending}>\n {updateMutation.isPending ? <Spinner size=\"sm\" /> : <Save className=\"h-4 w-4\" />}\n Save settings\n </Button>\n }\n >\n {/* Consent gate — settings below only take effect while this is on. */}\n <Section\n title=\"AI-processing consent\"\n description=\"AI summaries only run while you allow AI to read your health data.\"\n >\n <AiConsentGate />\n </Section>\n\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Sparkles className=\"h-5 w-5\" />}\n value={settings.provider}\n label=\"Current provider\"\n />\n <MetricsCard\n icon={<Bot className=\"h-5 w-5\" />}\n value={settings.modelName}\n label=\"Current model\"\n />\n <MetricsCard\n icon={<Activity className=\"h-5 w-5\" />}\n value={`${settings.dailyAllowanceUsed} / ${settings.dailyAllowance}`}\n label=\"Daily allowance used\"\n accent={isExhausted ? 'error' : usagePercent >= 80 ? 'warning' : 'success'}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n Resets {formatRelativeTime(settings.allowanceResetAt)}\n </p>\n }\n />\n </div>\n\n {/* Settings form */}\n <Section title=\"Configuration\" description=\"Changes apply to your account only.\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-provider\">AI provider</Label>\n <Select\n value={form.provider}\n onValueChange={(v) => handleProviderChange(v as MybAiProvider)}\n >\n <SelectTrigger id=\"myb-ai-provider\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {AI_PROVIDERS.map((p) => (\n <SelectItem key={p} value={p}>\n {PROVIDER_LABELS[p]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n The provider used for AI-generated guidance summaries.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-model\">Model name</Label>\n <Input\n id=\"myb-ai-model\"\n value={form.modelName}\n onChange={(e) => setForm((f) => ({ ...f, modelName: e.target.value }))}\n placeholder={DEFAULT_MODELS[form.provider]}\n />\n <p className=\"text-xs text-text-tertiary\">\n e.g. {DEFAULT_MODELS[form.provider]}. Must be valid for the chosen provider.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-allowance\">Daily allowance</Label>\n <Input\n id=\"myb-ai-allowance\"\n type=\"number\"\n min={0}\n max={10000}\n step={1}\n value={form.dailyAllowance}\n onChange={(e) => setForm((f) => ({ ...f, dailyAllowance: e.target.value }))}\n />\n <p className=\"text-xs text-text-tertiary\">\n Maximum AI operations counted against your account per UTC day.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-ai-key\">\n <span className=\"inline-flex items-center gap-1.5\">\n <KeyRound className=\"h-3.5 w-3.5\" />\n Provider API key\n </span>\n </Label>\n <Input\n id=\"myb-ai-key\"\n type=\"password\"\n value={form.providerKey}\n onChange={(e) =>\n setForm((f) => ({ ...f, providerKey: e.target.value, clearKey: false }))\n }\n placeholder={settings.hasProviderKey ? '•••••••• (saved)' : 'Optional'}\n disabled={form.clearKey}\n />\n <div className=\"flex items-center gap-2\">\n <input\n id=\"myb-ai-clear-key\"\n type=\"checkbox\"\n checked={form.clearKey}\n onChange={(e) =>\n setForm((f) => ({ ...f, clearKey: e.target.checked, providerKey: '' }))\n }\n className=\"h-4 w-4 rounded border-border-seam text-accent focus:ring-accent\"\n />\n <Label\n htmlFor=\"myb-ai-clear-key\"\n className=\"text-xs font-normal text-text-secondary\"\n >\n Clear saved key and fall back to the workspace default\n </Label>\n </div>\n </div>\n </div>\n\n {saved && <p className=\"mt-4 text-sm text-status-success-text\">AI settings saved.</p>}\n {err && <p className=\"mt-4 text-sm text-status-error-text\">{err}</p>}\n </GlassCard>\n </Section>\n\n {/* Operation log */}\n <Section\n title=\"Operation history\"\n description=\"Redacted audit log of AI operations for your account.\"\n >\n {logsQuery.isLoading ? (\n <LoadingView label=\"Loading operation history…\" />\n ) : logsQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the operation log.\"\n onRetry={() => logsQuery.refetch()}\n />\n ) : logsQuery.data?.items.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Sparkles className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">No AI operations yet</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n Operations appear here once AI-generated guidance runs.\n </p>\n </GlassCard>\n ) : (\n <DataTable\n columns={logColumns}\n rows={logsQuery.data?.items ?? []}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'createdAt', dir: 'desc' }}\n />\n )}\n </Section>\n </PageLayout>\n );\n}\n\nexport function AiControlsPage() {\n const settingsQuery = useMybAiSettings();\n const logsQuery = useMybAiOperationLogs();\n\n if (settingsQuery.isLoading) {\n return (\n <PageLayout title=\"AI controls\">\n <LoadingView label=\"Loading AI settings…\" />\n </PageLayout>\n );\n }\n\n if (settingsQuery.isError) {\n return (\n <PageLayout title=\"AI controls\">\n <ErrorView\n title=\"Could not load AI settings\"\n message=\"We couldn't fetch your AI configuration. Try again in a moment.\"\n onRetry={() => settingsQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const settings = settingsQuery.data;\n if (!settings) {\n return (\n <PageLayout title=\"AI controls\">\n <ErrorView\n title=\"Could not load AI settings\"\n message=\"We couldn't fetch your AI configuration. Try again in a moment.\"\n onRetry={() => settingsQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n // Key the inner component on the settings revision so form state re-seeds\n // when the source settings change, without synchronously setting state in\n // an effect (which triggers cascading renders).\n const settingsKey = `${settings.id}:${settings.updatedAt}`;\n\n return <AiControlsPageInner key={settingsKey} settings={settings} logsQuery={logsQuery} />;\n}\n"],"mappings":"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"}