@burdenoff/microfe-mybodyshield 2026.820.1 → 2026.821.2

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.
Files changed (48) hide show
  1. package/dist/mybodyshield/MyBodyShieldRoot.d.ts.map +1 -1
  2. package/dist/mybodyshield/MyBodyShieldRoot.js +68 -44
  3. package/dist/mybodyshield/MyBodyShieldRoot.js.map +1 -1
  4. package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -1
  5. package/dist/mybodyshield/MyBodyShieldRoutes.js +37 -16
  6. package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
  7. package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts +11 -1
  8. package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts.map +1 -1
  9. package/dist/mybodyshield/hooks/useMyBodyShieldApi.js +113 -46
  10. package/dist/mybodyshield/hooks/useMyBodyShieldApi.js.map +1 -1
  11. package/dist/mybodyshield/pages/SettingsPage.d.ts +10 -0
  12. package/dist/mybodyshield/pages/SettingsPage.d.ts.map +1 -0
  13. package/dist/mybodyshield/pages/SettingsPage.js +104 -0
  14. package/dist/mybodyshield/pages/SettingsPage.js.map +1 -0
  15. package/dist/mybodyshield/pages/ai/AiControlsPage.d.ts +10 -0
  16. package/dist/mybodyshield/pages/ai/AiControlsPage.d.ts.map +1 -0
  17. package/dist/mybodyshield/pages/ai/AiControlsPage.js +378 -0
  18. package/dist/mybodyshield/pages/ai/AiControlsPage.js.map +1 -0
  19. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts +18 -0
  20. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -0
  21. package/dist/mybodyshield/pages/insights/InsightsCharts.js +549 -0
  22. package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -0
  23. package/dist/mybodyshield/pages/insights/InsightsPage.d.ts +12 -0
  24. package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -0
  25. package/dist/mybodyshield/pages/insights/InsightsPage.js +317 -0
  26. package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -0
  27. package/dist/mybodyshield/types/index.d.ts +75 -0
  28. package/dist/mybodyshield/types/index.d.ts.map +1 -1
  29. package/dist/mybodyshield/utils/pageContext.d.ts.map +1 -1
  30. package/dist/mybodyshield/utils/pageContext.js +4 -1
  31. package/dist/mybodyshield/utils/pageContext.js.map +1 -1
  32. package/dist/node_modules/recharts/es6/cartesian/Line.js +473 -0
  33. package/dist/node_modules/recharts/es6/cartesian/Line.js.map +1 -0
  34. package/dist/node_modules/recharts/es6/cartesian/Scatter.js +368 -0
  35. package/dist/node_modules/recharts/es6/cartesian/Scatter.js.map +1 -0
  36. package/dist/node_modules/recharts/es6/cartesian/ZAxis.js +103 -0
  37. package/dist/node_modules/recharts/es6/cartesian/ZAxis.js.map +1 -0
  38. package/dist/node_modules/recharts/es6/chart/BarChart.js +24 -0
  39. package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -0
  40. package/dist/node_modules/recharts/es6/chart/ComposedChart.js +38 -0
  41. package/dist/node_modules/recharts/es6/chart/ComposedChart.js.map +1 -0
  42. package/dist/node_modules/recharts/es6/util/DataUtils.js +12 -2
  43. package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
  44. package/dist/node_modules/recharts/es6/util/ScatterUtils.js +48 -0
  45. package/dist/node_modules/recharts/es6/util/ScatterUtils.js.map +1 -0
  46. package/dist/stories/InsightsCharts.stories.d.ts +15 -0
  47. package/dist/stories/InsightsCharts.stories.d.ts.map +1 -0
  48. package/package.json +1 -1
@@ -0,0 +1,378 @@
1
+ import { PageLayout as e } from "../../components/PageLayout.js";
2
+ import { MetricsCard as t } from "../../components/MetricsCard.js";
3
+ import { formatDateTime as n, formatEnumLabel as r, formatRelativeTime as i } from "../../utils/formatters.js";
4
+ import { ErrorView as a, LoadingView as o, Section as s } from "../../components/StateViews.js";
5
+ import { DataTable as c } from "../../components/DataTable.js";
6
+ import { useMybAiOperationLogs as l, useMybAiSettings as u, useMybUpdateAiSettings as d } from "../../hooks/useMyBodyShieldApi.js";
7
+ import "../../hooks/index.js";
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";
13
+ //#region src/mybodyshield/pages/ai/AiControlsPage.tsx
14
+ var j = [
15
+ "GEMINI",
16
+ "OPENAI",
17
+ "ANTHROPIC"
18
+ ], M = {
19
+ GEMINI: "Google Gemini",
20
+ OPENAI: "OpenAI",
21
+ ANTHROPIC: "Anthropic"
22
+ }, N = {
23
+ GEMINI: "gemini-2.5-flash",
24
+ OPENAI: "gpt-4o-mini",
25
+ ANTHROPIC: "claude-3-5-haiku-latest"
26
+ }, P = (e) => ({
27
+ provider: e.provider,
28
+ modelName: e.modelName,
29
+ dailyAllowance: String(e.dailyAllowance),
30
+ providerKey: "",
31
+ clearKey: !1
32
+ });
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(() => {
35
+ let { dailyAllowance: e, dailyAllowanceUsed: t } = l;
36
+ return e <= 0 ? 0 : Math.min(100, Math.round(t / e * 100));
37
+ }, [l]), U = l.dailyAllowanceUsed >= l.dailyAllowance, W = (e) => {
38
+ L((t) => ({
39
+ ...t,
40
+ provider: e,
41
+ modelName: t.modelName === N[t.provider] ? N[e] : t.modelName
42
+ }));
43
+ };
44
+ return /* @__PURE__ */ b(e, {
45
+ title: "AI controls",
46
+ description: "Choose your AI provider, model and daily allowance.",
47
+ actions: /* @__PURE__ */ b(x, {
48
+ className: "gap-2",
49
+ onClick: async () => {
50
+ z(null), V(!1);
51
+ let e = Number(I.dailyAllowance);
52
+ if (!Number.isInteger(e) || e < 0 || e > 1e4) {
53
+ z("Daily allowance must be an integer between 0 and 10,000.");
54
+ return;
55
+ }
56
+ if (!I.modelName.trim()) {
57
+ z("Model name is required.");
58
+ return;
59
+ }
60
+ let t = {
61
+ provider: I.provider,
62
+ modelName: I.modelName.trim(),
63
+ dailyAllowance: e
64
+ };
65
+ I.clearKey ? t.providerKey = "" : I.providerKey.trim() && (t.providerKey = I.providerKey.trim());
66
+ try {
67
+ await F.mutateAsync(t), V(!0), L((e) => ({
68
+ ...e,
69
+ providerKey: "",
70
+ clearKey: !1
71
+ }));
72
+ } catch (e) {
73
+ z(e instanceof Error ? e.message : "Failed to save AI settings.");
74
+ }
75
+ },
76
+ disabled: F.isPending,
77
+ children: [F.isPending ? /* @__PURE__ */ y(A, { size: "sm" }) : /* @__PURE__ */ y(_, { className: "h-4 w-4" }), "Save settings"]
78
+ }),
79
+ children: [
80
+ /* @__PURE__ */ b("div", {
81
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
82
+ children: [
83
+ /* @__PURE__ */ y(t, {
84
+ icon: /* @__PURE__ */ y(v, { className: "h-5 w-5" }),
85
+ value: l.provider,
86
+ label: "Current provider"
87
+ }),
88
+ /* @__PURE__ */ y(t, {
89
+ icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
90
+ value: l.modelName,
91
+ label: "Current model"
92
+ }),
93
+ /* @__PURE__ */ y(t, {
94
+ icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" }),
95
+ value: `${l.dailyAllowanceUsed} / ${l.dailyAllowance}`,
96
+ label: "Daily allowance used",
97
+ accent: U ? "error" : H >= 80 ? "warning" : "success",
98
+ footer: /* @__PURE__ */ b("p", {
99
+ className: "text-xs text-text-tertiary",
100
+ children: ["Resets ", i(l.allowanceResetAt)]
101
+ })
102
+ })
103
+ ]
104
+ }),
105
+ /* @__PURE__ */ y(s, {
106
+ title: "Configuration",
107
+ description: "Changes apply to your account only.",
108
+ children: /* @__PURE__ */ b(S, {
109
+ treatment: "plain",
110
+ className: "p-6",
111
+ children: [
112
+ /* @__PURE__ */ b("div", {
113
+ className: "grid gap-6 md:grid-cols-2",
114
+ children: [
115
+ /* @__PURE__ */ b("div", {
116
+ className: "space-y-1.5",
117
+ children: [
118
+ /* @__PURE__ */ y(w, {
119
+ htmlFor: "myb-ai-provider",
120
+ children: "AI provider"
121
+ }),
122
+ /* @__PURE__ */ b(T, {
123
+ value: I.provider,
124
+ onValueChange: (e) => W(e),
125
+ children: [/* @__PURE__ */ y(O, {
126
+ id: "myb-ai-provider",
127
+ children: /* @__PURE__ */ y(k, {})
128
+ }), /* @__PURE__ */ y(E, { children: j.map((e) => /* @__PURE__ */ y(D, {
129
+ value: e,
130
+ children: M[e]
131
+ }, e)) })]
132
+ }),
133
+ /* @__PURE__ */ y("p", {
134
+ className: "text-xs text-text-tertiary",
135
+ children: "The provider used for AI-generated guidance summaries."
136
+ })
137
+ ]
138
+ }),
139
+ /* @__PURE__ */ b("div", {
140
+ className: "space-y-1.5",
141
+ children: [
142
+ /* @__PURE__ */ y(w, {
143
+ htmlFor: "myb-ai-model",
144
+ children: "Model name"
145
+ }),
146
+ /* @__PURE__ */ y(C, {
147
+ id: "myb-ai-model",
148
+ value: I.modelName,
149
+ onChange: (e) => L((t) => ({
150
+ ...t,
151
+ modelName: e.target.value
152
+ })),
153
+ placeholder: N[I.provider]
154
+ }),
155
+ /* @__PURE__ */ b("p", {
156
+ className: "text-xs text-text-tertiary",
157
+ children: [
158
+ "e.g. ",
159
+ N[I.provider],
160
+ ". Must be valid for the chosen provider."
161
+ ]
162
+ })
163
+ ]
164
+ }),
165
+ /* @__PURE__ */ b("div", {
166
+ className: "space-y-1.5",
167
+ children: [
168
+ /* @__PURE__ */ y(w, {
169
+ htmlFor: "myb-ai-allowance",
170
+ children: "Daily allowance"
171
+ }),
172
+ /* @__PURE__ */ y(C, {
173
+ id: "myb-ai-allowance",
174
+ type: "number",
175
+ min: 0,
176
+ max: 1e4,
177
+ step: 1,
178
+ value: I.dailyAllowance,
179
+ onChange: (e) => L((t) => ({
180
+ ...t,
181
+ dailyAllowance: e.target.value
182
+ }))
183
+ }),
184
+ /* @__PURE__ */ y("p", {
185
+ className: "text-xs text-text-tertiary",
186
+ children: "Maximum AI operations counted against your account per UTC day."
187
+ })
188
+ ]
189
+ }),
190
+ /* @__PURE__ */ b("div", {
191
+ className: "space-y-1.5",
192
+ children: [
193
+ /* @__PURE__ */ y(w, {
194
+ htmlFor: "myb-ai-key",
195
+ children: /* @__PURE__ */ b("span", {
196
+ className: "inline-flex items-center gap-1.5",
197
+ children: [/* @__PURE__ */ y(g, { className: "h-3.5 w-3.5" }), "Provider API key"]
198
+ })
199
+ }),
200
+ /* @__PURE__ */ y(C, {
201
+ id: "myb-ai-key",
202
+ type: "password",
203
+ value: I.providerKey,
204
+ onChange: (e) => L((t) => ({
205
+ ...t,
206
+ providerKey: e.target.value,
207
+ clearKey: !1
208
+ })),
209
+ placeholder: l.hasProviderKey ? "•••••••• (saved)" : "Optional",
210
+ disabled: I.clearKey
211
+ }),
212
+ /* @__PURE__ */ b("div", {
213
+ className: "flex items-center gap-2",
214
+ children: [/* @__PURE__ */ y("input", {
215
+ id: "myb-ai-clear-key",
216
+ type: "checkbox",
217
+ checked: I.clearKey,
218
+ onChange: (e) => L((t) => ({
219
+ ...t,
220
+ clearKey: e.target.checked,
221
+ providerKey: ""
222
+ })),
223
+ className: "h-4 w-4 rounded border-border-seam text-accent focus:ring-accent"
224
+ }), /* @__PURE__ */ y(w, {
225
+ htmlFor: "myb-ai-clear-key",
226
+ className: "text-xs font-normal text-text-secondary",
227
+ children: "Clear saved key and fall back to the workspace default"
228
+ })]
229
+ })
230
+ ]
231
+ })
232
+ ]
233
+ }),
234
+ B && /* @__PURE__ */ y("p", {
235
+ className: "mt-4 text-sm text-status-success-text",
236
+ children: "AI settings saved."
237
+ }),
238
+ R && /* @__PURE__ */ y("p", {
239
+ className: "mt-4 text-sm text-status-error-text",
240
+ children: R
241
+ })
242
+ ]
243
+ })
244
+ }),
245
+ /* @__PURE__ */ y(s, {
246
+ title: "Operation history",
247
+ 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, {
249
+ message: "We couldn't load the operation log.",
250
+ onRetry: () => u.refetch()
251
+ }) : u.data?.items.length === 0 ? /* @__PURE__ */ b(S, {
252
+ treatment: "plain",
253
+ className: "p-8 text-center",
254
+ children: [
255
+ /* @__PURE__ */ y(v, { className: "mx-auto h-8 w-8 text-text-tertiary" }),
256
+ /* @__PURE__ */ y("p", {
257
+ className: "mt-3 text-sm font-medium text-text-primary",
258
+ children: "No AI operations yet"
259
+ }),
260
+ /* @__PURE__ */ y("p", {
261
+ className: "mt-1 text-xs text-text-tertiary",
262
+ children: "Operations appear here once AI-generated guidance runs."
263
+ })
264
+ ]
265
+ }) : /* @__PURE__ */ y(c, {
266
+ columns: [
267
+ {
268
+ key: "createdAt",
269
+ header: "Time",
270
+ sortValue: (e) => new Date(e.createdAt).getTime(),
271
+ render: (e) => /* @__PURE__ */ b("div", {
272
+ className: "text-text-primary",
273
+ children: [/* @__PURE__ */ y("span", {
274
+ className: "block text-sm",
275
+ children: n(e.createdAt)
276
+ }), /* @__PURE__ */ y("span", {
277
+ className: "text-xs text-text-tertiary",
278
+ children: i(e.createdAt)
279
+ })]
280
+ })
281
+ },
282
+ {
283
+ key: "operationType",
284
+ header: "Operation",
285
+ render: (e) => /* @__PURE__ */ y("span", {
286
+ className: "text-text-secondary",
287
+ children: r(e.operationType)
288
+ })
289
+ },
290
+ {
291
+ key: "provider",
292
+ header: "Provider / Model",
293
+ render: (e) => /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("span", {
294
+ className: "block text-sm text-text-primary",
295
+ children: M[e.provider]
296
+ }), /* @__PURE__ */ y("span", {
297
+ className: "text-xs text-text-tertiary",
298
+ children: e.modelName
299
+ })] })
300
+ },
301
+ {
302
+ key: "tokens",
303
+ header: "Tokens",
304
+ align: "right",
305
+ render: (e) => /* @__PURE__ */ b("span", {
306
+ className: "tabular-nums text-text-secondary",
307
+ children: [
308
+ e.inputTokens ?? "—",
309
+ " / ",
310
+ e.outputTokens ?? "—"
311
+ ]
312
+ })
313
+ },
314
+ {
315
+ key: "status",
316
+ header: "Status",
317
+ render: (e) => /* @__PURE__ */ y("span", {
318
+ 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
+ children: r(e.status)
320
+ })
321
+ },
322
+ {
323
+ key: "error",
324
+ header: "Error",
325
+ hideOnMobile: !0,
326
+ render: (e) => e.errorMessage ? /* @__PURE__ */ y("span", {
327
+ className: "text-xs text-status-error-text",
328
+ children: e.errorMessage
329
+ }) : /* @__PURE__ */ y("span", {
330
+ className: "text-text-tertiary",
331
+ children: "—"
332
+ })
333
+ }
334
+ ],
335
+ rows: u.data?.items ?? [],
336
+ rowKey: (e) => e.id,
337
+ defaultSort: {
338
+ key: "createdAt",
339
+ dir: "desc"
340
+ }
341
+ })
342
+ })
343
+ ]
344
+ });
345
+ }
346
+ function I() {
347
+ let t = u(), n = l();
348
+ if (t.isLoading) return /* @__PURE__ */ y(e, {
349
+ title: "AI controls",
350
+ children: /* @__PURE__ */ y(o, { label: "Loading AI settings…" })
351
+ });
352
+ if (t.isError) return /* @__PURE__ */ y(e, {
353
+ title: "AI controls",
354
+ children: /* @__PURE__ */ y(a, {
355
+ title: "Could not load AI settings",
356
+ message: "We couldn't fetch your AI configuration. Try again in a moment.",
357
+ onRetry: () => t.refetch()
358
+ })
359
+ });
360
+ let r = t.data;
361
+ if (!r) return /* @__PURE__ */ y(e, {
362
+ title: "AI controls",
363
+ children: /* @__PURE__ */ y(a, {
364
+ title: "Could not load AI settings",
365
+ message: "We couldn't fetch your AI configuration. Try again in a moment.",
366
+ onRetry: () => t.refetch()
367
+ })
368
+ });
369
+ let i = `${r.id}:${r.updatedAt}`;
370
+ return /* @__PURE__ */ y(F, {
371
+ settings: r,
372
+ logsQuery: n
373
+ }, i);
374
+ }
375
+ //#endregion
376
+ export { I as AiControlsPage };
377
+
378
+ //# sourceMappingURL=AiControlsPage.js.map
@@ -0,0 +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"}
@@ -0,0 +1,18 @@
1
+ import { MybInsightsDay } from '../../types';
2
+
3
+ export declare function VolumeChart({ daily }: {
4
+ daily: MybInsightsDay[];
5
+ }): import("react").JSX.Element;
6
+ export declare function ReadinessSleepChart({ daily }: {
7
+ daily: MybInsightsDay[];
8
+ }): import("react").JSX.Element;
9
+ export declare function MacroChart({ daily }: {
10
+ daily: MybInsightsDay[];
11
+ }): import("react").JSX.Element;
12
+ export declare function CalorieChart({ daily }: {
13
+ daily: MybInsightsDay[];
14
+ }): import("react").JSX.Element;
15
+ export declare function CorrelationView({ daily }: {
16
+ daily: MybInsightsDay[];
17
+ }): import("react").JSX.Element;
18
+ //# sourceMappingURL=InsightsCharts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InsightsCharts.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/insights/InsightsCharts.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2BH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA4GlD,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,+BAgFjE;AAMD,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,+BAyFzE;AAMD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,+BAoDhE;AAMD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,+BA2DlE;AA6ED,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,+BAkDrE"}