@open-mercato/ai-assistant 0.6.1-develop.3246.1.dbef9d7392 → 0.6.1-develop.3256.1.fe3dec2464

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 (133) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +82 -18
  3. package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +370 -0
  4. package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +7 -0
  5. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +194 -0
  6. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +7 -0
  7. package/dist/modules/ai_assistant/api/ai/agents/route.js +4 -0
  8. package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
  9. package/dist/modules/ai_assistant/api/ai/chat/route.js +169 -5
  10. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
  11. package/dist/modules/ai_assistant/api/route/route.js +38 -19
  12. package/dist/modules/ai_assistant/api/route/route.js.map +3 -3
  13. package/dist/modules/ai_assistant/api/settings/allowlist/route.js +195 -0
  14. package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +7 -0
  15. package/dist/modules/ai_assistant/api/settings/route.js +537 -22
  16. package/dist/modules/ai_assistant/api/settings/route.js.map +3 -3
  17. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +701 -147
  18. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
  19. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +338 -0
  20. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +7 -0
  21. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js +10 -0
  22. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js.map +7 -0
  23. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js +25 -0
  24. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js.map +7 -0
  25. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +1 -1
  26. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +2 -2
  27. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +75 -26
  28. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
  29. package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js +10 -0
  30. package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js.map +7 -0
  31. package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js +25 -0
  32. package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js.map +7 -0
  33. package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js +503 -168
  34. package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +2 -2
  35. package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js +5 -0
  36. package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js.map +7 -0
  37. package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js +5 -0
  38. package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js.map +7 -0
  39. package/dist/modules/ai_assistant/data/entities.js +123 -1
  40. package/dist/modules/ai_assistant/data/entities.js.map +2 -2
  41. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +157 -0
  42. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +7 -0
  43. package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js +77 -0
  44. package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js.map +7 -0
  45. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +1 -1
  46. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +2 -2
  47. package/dist/modules/ai_assistant/i18n/de.json +90 -1
  48. package/dist/modules/ai_assistant/i18n/en.json +90 -1
  49. package/dist/modules/ai_assistant/i18n/es.json +90 -1
  50. package/dist/modules/ai_assistant/i18n/pl.json +90 -1
  51. package/dist/modules/ai_assistant/lib/agent-registry.js +17 -1
  52. package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
  53. package/dist/modules/ai_assistant/lib/agent-runtime.js +133 -36
  54. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
  55. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
  56. package/dist/modules/ai_assistant/lib/baseurl-allowlist.js +29 -0
  57. package/dist/modules/ai_assistant/lib/baseurl-allowlist.js.map +7 -0
  58. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +4 -1
  59. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
  60. package/dist/modules/ai_assistant/lib/llm-adapters/google.js +4 -1
  61. package/dist/modules/ai_assistant/lib/llm-adapters/google.js.map +2 -2
  62. package/dist/modules/ai_assistant/lib/model-allowlist.js +211 -0
  63. package/dist/modules/ai_assistant/lib/model-allowlist.js.map +7 -0
  64. package/dist/modules/ai_assistant/lib/model-factory.js +203 -31
  65. package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
  66. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +32 -1
  67. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
  68. package/dist/modules/ai_assistant/migrations/Migration20260508140000.js +18 -0
  69. package/dist/modules/ai_assistant/migrations/Migration20260508140000.js.map +7 -0
  70. package/dist/modules/ai_assistant/migrations/Migration20260512090000.js +16 -0
  71. package/dist/modules/ai_assistant/migrations/Migration20260512090000.js.map +7 -0
  72. package/dist/modules/ai_assistant/migrations/Migration20260512130000.js +15 -0
  73. package/dist/modules/ai_assistant/migrations/Migration20260512130000.js.map +7 -0
  74. package/generated/entities/ai_agent_runtime_override/index.ts +13 -0
  75. package/generated/entities/ai_tenant_model_allowlist/index.ts +9 -0
  76. package/generated/entities.ids.generated.ts +2 -0
  77. package/generated/entity-fields-registry.ts +26 -0
  78. package/jest.config.cjs +2 -0
  79. package/package.json +4 -4
  80. package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +477 -0
  81. package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +116 -0
  82. package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +240 -0
  83. package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +251 -0
  84. package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -0
  85. package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +273 -0
  86. package/src/modules/ai_assistant/api/ai/chat/route.ts +211 -2
  87. package/src/modules/ai_assistant/api/route/route.ts +49 -25
  88. package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +408 -0
  89. package/src/modules/ai_assistant/api/settings/allowlist/route.ts +221 -0
  90. package/src/modules/ai_assistant/api/settings/route.ts +721 -27
  91. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +858 -177
  92. package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +458 -0
  93. package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.ts +23 -0
  94. package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.tsx +12 -0
  95. package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +1 -1
  96. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +89 -12
  97. package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.ts +23 -0
  98. package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.tsx +18 -0
  99. package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +617 -209
  100. package/src/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.ts +7 -0
  101. package/src/modules/ai_assistant/data/entities/AiTenantModelAllowlist.ts +2 -0
  102. package/src/modules/ai_assistant/data/entities.ts +164 -0
  103. package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +227 -0
  104. package/src/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.ts +132 -0
  105. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +337 -0
  106. package/src/modules/ai_assistant/data/repositories/__tests__/AiTenantModelAllowlistRepository.test.ts +181 -0
  107. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +1 -1
  108. package/src/modules/ai_assistant/i18n/de.json +90 -1
  109. package/src/modules/ai_assistant/i18n/en.json +90 -1
  110. package/src/modules/ai_assistant/i18n/es.json +90 -1
  111. package/src/modules/ai_assistant/i18n/pl.json +90 -1
  112. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +396 -0
  113. package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +60 -6
  114. package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +4 -2
  115. package/src/modules/ai_assistant/lib/__tests__/baseurl-allowlist.test.ts +75 -0
  116. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +18 -0
  117. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-google.test.ts +18 -0
  118. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +150 -4
  119. package/src/modules/ai_assistant/lib/__tests__/model-allowlist.test.ts +290 -0
  120. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +634 -0
  121. package/src/modules/ai_assistant/lib/agent-registry.ts +20 -1
  122. package/src/modules/ai_assistant/lib/agent-runtime.ts +220 -44
  123. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +48 -0
  124. package/src/modules/ai_assistant/lib/baseurl-allowlist.ts +64 -0
  125. package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +11 -1
  126. package/src/modules/ai_assistant/lib/llm-adapters/google.ts +4 -1
  127. package/src/modules/ai_assistant/lib/model-allowlist.ts +407 -0
  128. package/src/modules/ai_assistant/lib/model-factory.ts +486 -58
  129. package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +44 -0
  130. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +704 -235
  131. package/src/modules/ai_assistant/migrations/Migration20260508140000.ts +18 -0
  132. package/src/modules/ai_assistant/migrations/Migration20260512090000.ts +16 -0
  133. package/src/modules/ai_assistant/migrations/Migration20260512130000.ts +13 -0
@@ -0,0 +1,338 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { Loader2, Save, Shield, Trash2, Info, AlertCircle, ShieldCheck } from "lucide-react";
6
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
7
+ import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
8
+ import { Badge } from "@open-mercato/ui/primitives/badge";
9
+ import { Button } from "@open-mercato/ui/primitives/button";
10
+ import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
11
+ import { Label } from "@open-mercato/ui/primitives/label";
12
+ import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
13
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
14
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
15
+ async function fetchSettings() {
16
+ const { result, status } = await apiCallOrThrow(
17
+ "/api/ai_assistant/settings",
18
+ { method: "GET", credentials: "include" },
19
+ { errorMessage: "Failed to load AI settings" }
20
+ );
21
+ if (!result) throw new Error(`Failed to load settings (${status})`);
22
+ return result;
23
+ }
24
+ function snapshotToEditState(snapshot) {
25
+ return {
26
+ allowedProviders: snapshot?.allowedProviders ?? null,
27
+ allowedModelsByProvider: { ...snapshot?.allowedModelsByProvider ?? {} }
28
+ };
29
+ }
30
+ function AiTenantAllowlistPageClient() {
31
+ const t = useT();
32
+ const queryClient = useQueryClient();
33
+ const settingsQuery = useQuery({ queryKey: ["ai_assistant", "settings"], queryFn: fetchSettings, staleTime: 0 });
34
+ const [editState, setEditState] = React.useState({
35
+ allowedProviders: null,
36
+ allowedModelsByProvider: {}
37
+ });
38
+ const [dirty, setDirty] = React.useState(false);
39
+ const [saving, setSaving] = React.useState(false);
40
+ const [clearing, setClearing] = React.useState(false);
41
+ const [feedback, setFeedback] = React.useState(null);
42
+ const { runMutation: runSaveAllowlistMutation } = useGuardedMutation({
43
+ contextId: "ai-tenant-allowlist-save"
44
+ });
45
+ const { runMutation: runClearAllowlistMutation } = useGuardedMutation({
46
+ contextId: "ai-tenant-allowlist-clear"
47
+ });
48
+ React.useEffect(() => {
49
+ if (settingsQuery.data) {
50
+ setEditState(snapshotToEditState(settingsQuery.data.tenantAllowlist));
51
+ setDirty(false);
52
+ }
53
+ }, [settingsQuery.data]);
54
+ const pageHeader = /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
55
+ /* @__PURE__ */ jsxs("h1", { className: "flex items-center gap-2 text-2xl font-bold", children: [
56
+ /* @__PURE__ */ jsx(Shield, { className: "size-6" }),
57
+ t("ai_assistant.allowlist.title", "AI provider & model allowlist")
58
+ ] }),
59
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t(
60
+ "ai_assistant.allowlist.subtitle",
61
+ "Limit which providers and models the runtime, settings, and chat picker may use for this tenant. The env allowlist is the outer constraint \u2014 tenant picks narrow it further."
62
+ ) })
63
+ ] });
64
+ if (settingsQuery.isLoading) {
65
+ return /* @__PURE__ */ jsxs("div", { className: "flex max-w-3xl flex-col gap-4", children: [
66
+ pageHeader,
67
+ /* @__PURE__ */ jsxs("div", { className: "flex w-fit items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-muted-foreground", role: "status", children: [
68
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }),
69
+ t("ai_assistant.allowlist.loading", "Loading allowlist\u2026")
70
+ ] })
71
+ ] });
72
+ }
73
+ if (settingsQuery.isError || !settingsQuery.data) {
74
+ return /* @__PURE__ */ jsxs("div", { className: "flex max-w-3xl flex-col gap-4", children: [
75
+ pageHeader,
76
+ /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
77
+ /* @__PURE__ */ jsx(AlertCircle, { className: "size-4" }),
78
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.allowlist.loadError.title", "Failed to load allowlist") }),
79
+ /* @__PURE__ */ jsx(AlertDescription, { children: settingsQuery.error instanceof Error ? settingsQuery.error.message : t("ai_assistant.allowlist.loadError.body", "Try refreshing the page.") })
80
+ ] })
81
+ ] });
82
+ }
83
+ const settings = settingsQuery.data;
84
+ const envAllowedProviders = settings.allowlist.providers;
85
+ const envModelsByProvider = settings.allowlist.modelsByProvider;
86
+ const editableProviders = settings.allowlistProviders ?? settings.availableProviders;
87
+ const candidateProviders = editableProviders.filter((p) => {
88
+ if (envAllowedProviders === null) return true;
89
+ return envAllowedProviders.some((id) => id.toLowerCase() === p.id.toLowerCase());
90
+ });
91
+ const tenantPickedProviders = editState.allowedProviders;
92
+ const isProviderEnabled = (id) => {
93
+ if (tenantPickedProviders === null) return true;
94
+ return tenantPickedProviders.includes(id);
95
+ };
96
+ const toggleProvider = (id, next) => {
97
+ setDirty(true);
98
+ setFeedback(null);
99
+ setEditState((prev) => {
100
+ const current = prev.allowedProviders;
101
+ if (next) {
102
+ const list2 = current === null ? [id] : Array.from(/* @__PURE__ */ new Set([...current, id]));
103
+ return { ...prev, allowedProviders: list2 };
104
+ }
105
+ const list = current === null ? candidateProviders.map((p) => p.id).filter((pid) => pid !== id) : current.filter((pid) => pid !== id);
106
+ return { ...prev, allowedProviders: list };
107
+ });
108
+ };
109
+ const isModelEnabled = (providerId, modelId) => {
110
+ const list = editState.allowedModelsByProvider[providerId];
111
+ if (list === void 0) return true;
112
+ return list.includes(modelId);
113
+ };
114
+ const toggleModel = (providerId, modelId, next) => {
115
+ setDirty(true);
116
+ setFeedback(null);
117
+ const provider = candidateProviders.find((p) => p.id === providerId);
118
+ const allModelIds = provider?.defaultModels.map((m) => m.id) ?? [];
119
+ setEditState((prev) => {
120
+ const current = prev.allowedModelsByProvider[providerId];
121
+ const allowedModelsByProvider = { ...prev.allowedModelsByProvider };
122
+ if (next) {
123
+ const list = current === void 0 ? [modelId] : Array.from(/* @__PURE__ */ new Set([...current, modelId]));
124
+ allowedModelsByProvider[providerId] = list;
125
+ } else {
126
+ const baseline = current === void 0 ? allModelIds : current;
127
+ const list = baseline.filter((id) => id !== modelId);
128
+ allowedModelsByProvider[providerId] = list;
129
+ }
130
+ return { ...prev, allowedModelsByProvider };
131
+ });
132
+ };
133
+ const resetTenantPicks = () => {
134
+ setDirty(true);
135
+ setFeedback(null);
136
+ setEditState({ allowedProviders: null, allowedModelsByProvider: {} });
137
+ };
138
+ const handleSave = async () => {
139
+ setSaving(true);
140
+ setFeedback(null);
141
+ try {
142
+ await runSaveAllowlistMutation({
143
+ operation: async () => {
144
+ const { ok, status, result } = await apiCall(
145
+ "/api/ai_assistant/settings/allowlist",
146
+ {
147
+ method: "PUT",
148
+ credentials: "include",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify({
151
+ allowedProviders: editState.allowedProviders,
152
+ allowedModelsByProvider: editState.allowedModelsByProvider
153
+ })
154
+ }
155
+ );
156
+ if (!ok) {
157
+ throw new Error(
158
+ result?.error ?? t("ai_assistant.allowlist.save.error", `Save failed (${status})`)
159
+ );
160
+ }
161
+ },
162
+ context: {}
163
+ });
164
+ const successText = t("ai_assistant.allowlist.save.success", "Allowlist saved.");
165
+ setFeedback({ kind: "ok", text: successText });
166
+ flash(successText, "success");
167
+ setDirty(false);
168
+ await queryClient.invalidateQueries({ queryKey: ["ai_assistant", "settings"] });
169
+ } catch (err) {
170
+ const message = err instanceof Error ? err.message : String(err);
171
+ setFeedback({ kind: "error", text: message });
172
+ flash(message, "error");
173
+ } finally {
174
+ setSaving(false);
175
+ }
176
+ };
177
+ const handleClear = async () => {
178
+ setClearing(true);
179
+ setFeedback(null);
180
+ try {
181
+ await runClearAllowlistMutation({
182
+ operation: async () => {
183
+ const { ok, status, result } = await apiCall(
184
+ "/api/ai_assistant/settings/allowlist",
185
+ { method: "DELETE", credentials: "include" }
186
+ );
187
+ if (!ok) {
188
+ throw new Error(
189
+ result?.error ?? t("ai_assistant.allowlist.clear.error", `Clear failed (${status})`)
190
+ );
191
+ }
192
+ },
193
+ context: {}
194
+ });
195
+ const successText = t(
196
+ "ai_assistant.allowlist.clear.success",
197
+ "Tenant allowlist cleared. Env-only enforcement applies."
198
+ );
199
+ setFeedback({ kind: "ok", text: successText });
200
+ flash(successText, "success");
201
+ setDirty(false);
202
+ await queryClient.invalidateQueries({ queryKey: ["ai_assistant", "settings"] });
203
+ } catch (err) {
204
+ const message = err instanceof Error ? err.message : String(err);
205
+ setFeedback({ kind: "error", text: message });
206
+ flash(message, "error");
207
+ } finally {
208
+ setClearing(false);
209
+ }
210
+ };
211
+ const envBanner = envAllowedProviders === null && Object.keys(envModelsByProvider).length === 0 ? null : /* @__PURE__ */ jsxs(Alert, { children: [
212
+ /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
213
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.allowlist.envBanner.title", "Env allowlist is in effect") }),
214
+ /* @__PURE__ */ jsxs(AlertDescription, { className: "space-y-1", children: [
215
+ envAllowedProviders ? /* @__PURE__ */ jsxs("div", { children: [
216
+ t("ai_assistant.allowlist.envBanner.providers", "OM_AI_AVAILABLE_PROVIDERS"),
217
+ ": ",
218
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: envAllowedProviders.join(", ") })
219
+ ] }) : null,
220
+ Object.keys(envModelsByProvider).map((pid) => /* @__PURE__ */ jsxs("div", { children: [
221
+ /* @__PURE__ */ jsxs("code", { className: "font-mono text-xs", children: [
222
+ "OM_AI_AVAILABLE_MODELS_",
223
+ pid.toUpperCase()
224
+ ] }),
225
+ ": ",
226
+ envModelsByProvider[pid].join(", ")
227
+ ] }, pid)),
228
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t("ai_assistant.allowlist.envBanner.note", "Tenant picks may not widen the env list \u2014 values outside it are hidden.") })
229
+ ] })
230
+ ] });
231
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 max-w-3xl", children: [
232
+ pageHeader,
233
+ envBanner,
234
+ feedback ? /* @__PURE__ */ jsxs(Alert, { variant: feedback.kind === "error" ? "destructive" : void 0, children: [
235
+ feedback.kind === "error" ? /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
236
+ /* @__PURE__ */ jsx(AlertDescription, { children: feedback.text })
237
+ ] }) : null,
238
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 space-y-6", children: [
239
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
240
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
241
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("ai_assistant.allowlist.providers.title", "Providers") }),
242
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
243
+ "ai_assistant.allowlist.providers.help",
244
+ "Untick to forbid the runtime from using a provider for this tenant. Tick all to inherit the env allowlist."
245
+ ) })
246
+ ] }),
247
+ /* @__PURE__ */ jsx(
248
+ "span",
249
+ {
250
+ className: settings.effectiveAllowlist.tenantOverridesActive ? "inline-flex size-8 items-center justify-center rounded-md text-status-success-icon" : "inline-flex size-8 items-center justify-center rounded-md text-status-warning-icon",
251
+ role: "img",
252
+ "aria-label": settings.effectiveAllowlist.tenantOverridesActive ? t("ai_assistant.allowlist.badge.active", "Tenant rules active") : t("ai_assistant.allowlist.badge.envOnly", "Env-only"),
253
+ title: settings.effectiveAllowlist.tenantOverridesActive ? t("ai_assistant.allowlist.badge.active", "Tenant rules active") : t("ai_assistant.allowlist.badge.envOnly", "Env-only"),
254
+ children: settings.effectiveAllowlist.tenantOverridesActive ? /* @__PURE__ */ jsx(ShieldCheck, { className: "size-5", "aria-hidden": true }) : /* @__PURE__ */ jsx(Shield, { className: "size-5", "aria-hidden": true })
255
+ }
256
+ )
257
+ ] }),
258
+ candidateProviders.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("ai_assistant.allowlist.providers.empty", "No configured providers within the env allowlist.") }) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: candidateProviders.map((provider) => {
259
+ const enabled = isProviderEnabled(provider.id);
260
+ const envModels = envModelsByProvider[provider.id];
261
+ const candidateModels = envModels ? provider.defaultModels.filter((m) => envModels.includes(m.id)) : provider.defaultModels;
262
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border p-4 space-y-3", children: [
263
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
264
+ /* @__PURE__ */ jsx(
265
+ Checkbox,
266
+ {
267
+ id: `provider-${provider.id}`,
268
+ checked: enabled,
269
+ onCheckedChange: (value) => toggleProvider(provider.id, value === true)
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(Label, { htmlFor: `provider-${provider.id}`, className: "font-medium", children: provider.name }),
273
+ provider.configured ? /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: t("ai_assistant.allowlist.providers.configured", "configured") }) : /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs text-muted-foreground", children: t("ai_assistant.allowlist.providers.notConfigured", "not configured") })
274
+ ] }) }),
275
+ enabled && candidateModels.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "ml-7 space-y-2", children: [
276
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("ai_assistant.allowlist.models.help", "Tick the models tenants may pick. Empty = no model restriction (inherit env).") }),
277
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2", children: candidateModels.map((model) => {
278
+ const checked = isModelEnabled(provider.id, model.id);
279
+ return /* @__PURE__ */ jsxs(
280
+ "label",
281
+ {
282
+ className: "flex items-center gap-2 text-sm",
283
+ children: [
284
+ /* @__PURE__ */ jsx(
285
+ Checkbox,
286
+ {
287
+ checked,
288
+ onCheckedChange: (value) => toggleModel(provider.id, model.id, value === true)
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: model.id }),
292
+ model.id === provider.defaultModel ? /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: t("ai_assistant.allowlist.models.default", "default") }) : null
293
+ ]
294
+ },
295
+ `${provider.id}-${model.id}`
296
+ );
297
+ }) })
298
+ ] }) : null
299
+ ] }, provider.id);
300
+ }) })
301
+ ] }),
302
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
303
+ /* @__PURE__ */ jsxs(Button, { onClick: () => void handleSave(), disabled: !dirty || saving, className: "gap-2", children: [
304
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "h-4 w-4" }),
305
+ t("ai_assistant.allowlist.actions.save", "Save allowlist")
306
+ ] }),
307
+ /* @__PURE__ */ jsx(
308
+ Button,
309
+ {
310
+ variant: "outline",
311
+ onClick: resetTenantPicks,
312
+ disabled: saving || clearing,
313
+ className: "gap-2",
314
+ children: t("ai_assistant.allowlist.actions.reset", "Reset to env defaults")
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsxs(
318
+ Button,
319
+ {
320
+ variant: "ghost",
321
+ onClick: () => void handleClear(),
322
+ disabled: clearing || saving || !settings.tenantAllowlist,
323
+ className: "gap-2",
324
+ children: [
325
+ clearing ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
326
+ t("ai_assistant.allowlist.actions.clearStored", "Clear stored allowlist")
327
+ ]
328
+ }
329
+ )
330
+ ] })
331
+ ] });
332
+ }
333
+ var AiTenantAllowlistPageClient_default = AiTenantAllowlistPageClient;
334
+ export {
335
+ AiTenantAllowlistPageClient,
336
+ AiTenantAllowlistPageClient_default as default
337
+ };
338
+ //# sourceMappingURL=AiTenantAllowlistPageClient.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Loader2, Save, Shield, Trash2, Info, AlertCircle, ShieldCheck } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\ntype EnvAllowlistConfig = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n}\n\ntype TenantAllowlist = {\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\ntype EffectiveAllowlist = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n tenantOverridesActive: boolean\n}\n\ntype ProviderEntry = {\n id: string\n name: string\n defaultModel: string\n envKey: string | null\n configured: boolean\n defaultModels: Array<{ id: string; name: string; contextWindow?: number; tags?: string[] }>\n}\n\ntype SettingsResponse = {\n availableProviders: ProviderEntry[]\n allowlistProviders?: ProviderEntry[]\n allowlist: EnvAllowlistConfig\n tenantAllowlist: TenantAllowlist | null\n effectiveAllowlist: EffectiveAllowlist\n}\n\nasync function fetchSettings(): Promise<SettingsResponse> {\n const { result, status } = await apiCallOrThrow<SettingsResponse>(\n '/api/ai_assistant/settings',\n { method: 'GET', credentials: 'include' },\n { errorMessage: 'Failed to load AI settings' },\n )\n if (!result) throw new Error(`Failed to load settings (${status})`)\n return result\n}\n\ntype EditState = {\n /** null = \"no tenant restriction (inherit env)\"; array = explicit tenant pick */\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\nfunction snapshotToEditState(snapshot: TenantAllowlist | null): EditState {\n return {\n allowedProviders: snapshot?.allowedProviders ?? null,\n allowedModelsByProvider: { ...(snapshot?.allowedModelsByProvider ?? {}) },\n }\n}\n\nexport function AiTenantAllowlistPageClient(): React.JSX.Element {\n const t = useT()\n const queryClient = useQueryClient()\n const settingsQuery = useQuery({ queryKey: ['ai_assistant', 'settings'], queryFn: fetchSettings, staleTime: 0 })\n\n const [editState, setEditState] = React.useState<EditState>({\n allowedProviders: null,\n allowedModelsByProvider: {},\n })\n const [dirty, setDirty] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [clearing, setClearing] = React.useState(false)\n const [feedback, setFeedback] = React.useState<{ kind: 'ok' | 'error'; text: string } | null>(null)\n const { runMutation: runSaveAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-save',\n })\n const { runMutation: runClearAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-clear',\n })\n\n React.useEffect(() => {\n if (settingsQuery.data) {\n setEditState(snapshotToEditState(settingsQuery.data.tenantAllowlist))\n setDirty(false)\n }\n }, [settingsQuery.data])\n\n const pageHeader = (\n <div className=\"space-y-1\">\n <h1 className=\"flex items-center gap-2 text-2xl font-bold\">\n <Shield className=\"size-6\" />\n {t('ai_assistant.allowlist.title', 'AI provider & model allowlist')}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.subtitle',\n 'Limit which providers and models the runtime, settings, and chat picker may use for this tenant. The env allowlist is the outer constraint \u2014 tenant picks narrow it further.',\n )}\n </p>\n </div>\n )\n\n if (settingsQuery.isLoading) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <div className=\"flex w-fit items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-muted-foreground\" role=\"status\">\n <Loader2 className=\"size-4 animate-spin\" />\n {t('ai_assistant.allowlist.loading', 'Loading allowlist\u2026')}\n </div>\n </div>\n )\n }\n\n if (settingsQuery.isError || !settingsQuery.data) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <Alert variant=\"destructive\">\n <AlertCircle className=\"size-4\" />\n <AlertTitle>{t('ai_assistant.allowlist.loadError.title', 'Failed to load allowlist')}</AlertTitle>\n <AlertDescription>\n {settingsQuery.error instanceof Error\n ? settingsQuery.error.message\n : t('ai_assistant.allowlist.loadError.body', 'Try refreshing the page.')}\n </AlertDescription>\n </Alert>\n </div>\n )\n }\n\n const settings = settingsQuery.data\n const envAllowedProviders = settings.allowlist.providers\n const envModelsByProvider = settings.allowlist.modelsByProvider\n\n // Provider universe to render: env-allowed providers (or all configured if env unset).\n const editableProviders = settings.allowlistProviders ?? settings.availableProviders\n const candidateProviders = editableProviders.filter((p) => {\n if (envAllowedProviders === null) return true\n return envAllowedProviders.some((id) => id.toLowerCase() === p.id.toLowerCase())\n })\n\n const tenantPickedProviders = editState.allowedProviders\n const isProviderEnabled = (id: string): boolean => {\n if (tenantPickedProviders === null) return true\n return tenantPickedProviders.includes(id)\n }\n\n const toggleProvider = (id: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n setEditState((prev) => {\n const current = prev.allowedProviders\n if (next) {\n const list = current === null ? [id] : Array.from(new Set([...current, id]))\n return { ...prev, allowedProviders: list }\n }\n const list = current === null\n ? candidateProviders.map((p) => p.id).filter((pid) => pid !== id)\n : current.filter((pid) => pid !== id)\n return { ...prev, allowedProviders: list }\n })\n }\n\n const isModelEnabled = (providerId: string, modelId: string): boolean => {\n const list = editState.allowedModelsByProvider[providerId]\n if (list === undefined) return true\n return list.includes(modelId)\n }\n\n const toggleModel = (providerId: string, modelId: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n const provider = candidateProviders.find((p) => p.id === providerId)\n const allModelIds = provider?.defaultModels.map((m) => m.id) ?? []\n setEditState((prev) => {\n const current = prev.allowedModelsByProvider[providerId]\n const allowedModelsByProvider = { ...prev.allowedModelsByProvider }\n if (next) {\n const list = current === undefined ? [modelId] : Array.from(new Set([...current, modelId]))\n allowedModelsByProvider[providerId] = list\n } else {\n const baseline = current === undefined ? allModelIds : current\n const list = baseline.filter((id) => id !== modelId)\n allowedModelsByProvider[providerId] = list\n }\n return { ...prev, allowedModelsByProvider }\n })\n }\n\n const resetTenantPicks = (): void => {\n setDirty(true)\n setFeedback(null)\n setEditState({ allowedProviders: null, allowedModelsByProvider: {} })\n }\n\n const handleSave = async (): Promise<void> => {\n setSaving(true)\n setFeedback(null)\n try {\n await runSaveAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; code?: string }>(\n '/api/ai_assistant/settings/allowlist',\n {\n method: 'PUT',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n allowedProviders: editState.allowedProviders,\n allowedModelsByProvider: editState.allowedModelsByProvider,\n }),\n },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.save.error', `Save failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t('ai_assistant.allowlist.save.success', 'Allowlist saved.')\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }\n\n const handleClear = async (): Promise<void> => {\n setClearing(true)\n setFeedback(null)\n try {\n await runClearAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; cleared?: boolean }>(\n '/api/ai_assistant/settings/allowlist',\n { method: 'DELETE', credentials: 'include' },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.clear.error', `Clear failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t(\n 'ai_assistant.allowlist.clear.success',\n 'Tenant allowlist cleared. Env-only enforcement applies.',\n )\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setClearing(false)\n }\n }\n\n const envBanner = envAllowedProviders === null && Object.keys(envModelsByProvider).length === 0\n ? null\n : (\n <Alert>\n <Info className=\"h-4 w-4\" />\n <AlertTitle>{t('ai_assistant.allowlist.envBanner.title', 'Env allowlist is in effect')}</AlertTitle>\n <AlertDescription className=\"space-y-1\">\n {envAllowedProviders ? (\n <div>\n {t('ai_assistant.allowlist.envBanner.providers', 'OM_AI_AVAILABLE_PROVIDERS')}: <code className=\"font-mono text-xs\">{envAllowedProviders.join(', ')}</code>\n </div>\n ) : null}\n {Object.keys(envModelsByProvider).map((pid) => (\n <div key={pid}>\n <code className=\"font-mono text-xs\">OM_AI_AVAILABLE_MODELS_{pid.toUpperCase()}</code>: {envModelsByProvider[pid].join(', ')}\n </div>\n ))}\n <p className=\"text-xs text-muted-foreground mt-1\">\n {t('ai_assistant.allowlist.envBanner.note', 'Tenant picks may not widen the env list \u2014 values outside it are hidden.')}\n </p>\n </AlertDescription>\n </Alert>\n )\n\n return (\n <div className=\"flex flex-col gap-6 max-w-3xl\">\n {pageHeader}\n\n {envBanner}\n\n {feedback ? (\n <Alert variant={feedback.kind === 'error' ? 'destructive' : undefined}>\n {feedback.kind === 'error' ? <AlertCircle className=\"h-4 w-4\" /> : <Info className=\"h-4 w-4\" />}\n <AlertDescription>{feedback.text}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"rounded-lg border bg-card p-6 space-y-6\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">{t('ai_assistant.allowlist.providers.title', 'Providers')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.providers.help',\n 'Untick to forbid the runtime from using a provider for this tenant. Tick all to inherit the env allowlist.',\n )}\n </p>\n </div>\n <span\n className={\n settings.effectiveAllowlist.tenantOverridesActive\n ? 'inline-flex size-8 items-center justify-center rounded-md text-status-success-icon'\n : 'inline-flex size-8 items-center justify-center rounded-md text-status-warning-icon'\n }\n role=\"img\"\n aria-label={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n title={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n >\n {settings.effectiveAllowlist.tenantOverridesActive ? (\n <ShieldCheck className=\"size-5\" aria-hidden />\n ) : (\n <Shield className=\"size-5\" aria-hidden />\n )}\n </span>\n </div>\n\n {candidateProviders.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.empty', 'No configured providers within the env allowlist.')}\n </p>\n ) : (\n <div className=\"space-y-4\">\n {candidateProviders.map((provider) => {\n const enabled = isProviderEnabled(provider.id)\n const envModels = envModelsByProvider[provider.id]\n const candidateModels = envModels\n ? provider.defaultModels.filter((m) => envModels.includes(m.id))\n : provider.defaultModels\n return (\n <div key={provider.id} className=\"rounded-md border p-4 space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id={`provider-${provider.id}`}\n checked={enabled}\n onCheckedChange={(value) => toggleProvider(provider.id, value === true)}\n />\n <Label htmlFor={`provider-${provider.id}`} className=\"font-medium\">\n {provider.name}\n </Label>\n {provider.configured ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.providers.configured', 'configured')}\n </Badge>\n ) : (\n <Badge variant=\"outline\" className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.notConfigured', 'not configured')}\n </Badge>\n )}\n </div>\n </div>\n\n {enabled && candidateModels.length > 0 ? (\n <div className=\"ml-7 space-y-2\">\n <div className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.models.help', 'Tick the models tenants may pick. Empty = no model restriction (inherit env).')}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {candidateModels.map((model) => {\n const checked = isModelEnabled(provider.id, model.id)\n return (\n <label\n key={`${provider.id}-${model.id}`}\n className=\"flex items-center gap-2 text-sm\"\n >\n <Checkbox\n checked={checked}\n onCheckedChange={(value) => toggleModel(provider.id, model.id, value === true)}\n />\n <span className=\"font-mono text-xs\">{model.id}</span>\n {model.id === provider.defaultModel ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.models.default', 'default')}\n </Badge>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void handleSave()} disabled={!dirty || saving} className=\"gap-2\">\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.save', 'Save allowlist')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={resetTenantPicks}\n disabled={saving || clearing}\n className=\"gap-2\"\n >\n {t('ai_assistant.allowlist.actions.reset', 'Reset to env defaults')}\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => void handleClear()}\n disabled={clearing || saving || !settings.tenantAllowlist}\n className=\"gap-2\"\n >\n {clearing ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Trash2 className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.clearStored', 'Clear stored allowlist')}\n </Button>\n </div>\n </div>\n )\n}\n\nexport default AiTenantAllowlistPageClient\n"],
5
+ "mappings": "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",
6
+ "names": ["list"]
7
+ }
@@ -0,0 +1,10 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
3
+ import { AiTenantAllowlistPageClient } from "./AiTenantAllowlistPageClient.js";
4
+ async function AiAssistantAllowlistPage() {
5
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(AiTenantAllowlistPageClient, {}) }) });
6
+ }
7
+ export {
8
+ AiAssistantAllowlistPage as default
9
+ };
10
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.tsx"],
4
+ "sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { AiTenantAllowlistPageClient } from './AiTenantAllowlistPageClient'\n\nexport default async function AiAssistantAllowlistPage() {\n return (\n <Page>\n <PageBody>\n <AiTenantAllowlistPageClient />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,mCAAmC;AAE5C,eAAO,2BAAkD;AACvD,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,+BAA4B,GAC/B,GACF;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ const allowlistIcon = React.createElement(
3
+ "svg",
4
+ { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
5
+ React.createElement("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
6
+ React.createElement("path", { d: "m9 12 2 2 4-4" })
7
+ );
8
+ const metadata = {
9
+ requireAuth: true,
10
+ requireFeatures: ["ai_assistant.settings.manage"],
11
+ pageTitle: "AI Allowlist",
12
+ pageTitleKey: "ai_assistant.allowlist.navTitle",
13
+ pageGroup: "Module Configs",
14
+ pageGroupKey: "settings.sections.moduleConfigs",
15
+ pageOrder: 432,
16
+ icon: allowlistIcon,
17
+ pageContext: "settings",
18
+ breadcrumb: [
19
+ { label: "AI Allowlist", labelKey: "ai_assistant.allowlist.navTitle" }
20
+ ]
21
+ };
22
+ export {
23
+ metadata
24
+ };
25
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.ts"],
4
+ "sourcesContent": ["import React from 'react'\n\nconst allowlistIcon = React.createElement(\n 'svg',\n { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },\n React.createElement('path', { d: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z' }),\n React.createElement('path', { d: 'm9 12 2 2 4-4' }),\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['ai_assistant.settings.manage'],\n pageTitle: 'AI Allowlist',\n pageTitleKey: 'ai_assistant.allowlist.navTitle',\n pageGroup: 'Module Configs',\n pageGroupKey: 'settings.sections.moduleConfigs',\n pageOrder: 432,\n icon: allowlistIcon,\n pageContext: 'settings' as const,\n breadcrumb: [\n { label: 'AI Allowlist', labelKey: 'ai_assistant.allowlist.navTitle' },\n ],\n} as const\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,gBAAgB,MAAM;AAAA,EAC1B;AAAA,EACA,EAAE,OAAO,IAAI,QAAQ,IAAI,SAAS,aAAa,MAAM,QAAQ,QAAQ,gBAAgB,aAAa,GAAG,eAAe,SAAS,gBAAgB,QAAQ;AAAA,EACrJ,MAAM,cAAc,QAAQ,EAAE,GAAG,8CAA8C,CAAC;AAAA,EAChF,MAAM,cAAc,QAAQ,EAAE,GAAG,gBAAgB,CAAC;AACpD;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,8BAA8B;AAAA,EAChD,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,EAAE,OAAO,gBAAgB,UAAU,kCAAkC;AAAA,EACvE;AACF;",
6
+ "names": []
7
+ }
@@ -2,7 +2,7 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
3
3
  import { AiAssistantSettingsPageClient } from "../../../../components/AiAssistantSettingsPageClient.js";
4
4
  async function AiAssistantLegacySettingsPage() {
5
- return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(AiAssistantSettingsPageClient, {}) }) });
5
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(AiAssistantSettingsPageClient, { launchMode: "legacy", showVisibilityControl: true }) }) });
6
6
  }
7
7
  export {
8
8
  AiAssistantLegacySettingsPage as default
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx"],
4
- "sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { AiAssistantSettingsPageClient } from '../../../../components/AiAssistantSettingsPageClient'\n\nexport default async function AiAssistantLegacySettingsPage() {\n return (\n <Page>\n <PageBody>\n <AiAssistantSettingsPageClient />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,qCAAqC;AAE9C,eAAO,gCAAuD;AAC5D,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,iCAA8B,GACjC,GACF;AAEJ;",
4
+ "sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { AiAssistantSettingsPageClient } from '../../../../components/AiAssistantSettingsPageClient'\n\nexport default async function AiAssistantLegacySettingsPage() {\n return (\n <Page>\n <PageBody>\n <AiAssistantSettingsPageClient launchMode=\"legacy\" showVisibilityControl />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,qCAAqC;AAE9C,eAAO,gCAAuD;AAC5D,SACE,oBAAC,QACC,8BAAC,YACC,8BAAC,iCAA8B,YAAW,UAAS,uBAAqB,MAAC,GAC3E,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -62,28 +62,35 @@ function PlaygroundNoAgents() {
62
62
  }
63
63
  function AgentDetails({ agent }) {
64
64
  const t = useT();
65
- return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/30 p-3 text-sm", children: [
66
- /* @__PURE__ */ jsx("div", { className: "font-semibold", children: agent.label }),
67
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: agent.description }),
68
- /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid grid-cols-2 gap-2 text-xs", children: [
69
- /* @__PURE__ */ jsxs("div", { children: [
70
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.module", "Module") }),
71
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.moduleId })
72
- ] }),
73
- /* @__PURE__ */ jsxs("div", { children: [
74
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.executionMode", "Execution mode") }),
75
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.executionMode })
76
- ] }),
77
- /* @__PURE__ */ jsxs("div", { children: [
78
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.mutationPolicy", "Mutation policy") }),
79
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.mutationPolicy })
80
- ] }),
81
- /* @__PURE__ */ jsxs("div", { children: [
82
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.tools", "Allowed tools") }),
83
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.allowedTools.length })
84
- ] })
85
- ] })
86
- ] });
65
+ return /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ className: "rounded-md border border-border bg-muted/30 p-3 text-sm",
69
+ "data-ai-playground-agent": agent.id,
70
+ children: [
71
+ /* @__PURE__ */ jsx("div", { className: "font-semibold", children: agent.label }),
72
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: agent.description }),
73
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid grid-cols-2 gap-2 text-xs", children: [
74
+ /* @__PURE__ */ jsxs("div", { children: [
75
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.module", "Module") }),
76
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.moduleId })
77
+ ] }),
78
+ /* @__PURE__ */ jsxs("div", { children: [
79
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.executionMode", "Execution mode") }),
80
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.executionMode })
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("div", { children: [
83
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.mutationPolicy", "Mutation policy") }),
84
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.mutationPolicy })
85
+ ] }),
86
+ /* @__PURE__ */ jsxs("div", { children: [
87
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.tools", "Allowed tools") }),
88
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.allowedTools.length })
89
+ ] })
90
+ ] })
91
+ ]
92
+ }
93
+ );
87
94
  }
88
95
  function buildDebugTools(agent) {
89
96
  if (agent.tools && agent.tools.length > 0) {
@@ -115,6 +122,47 @@ function buildDebugPromptSections(agent) {
115
122
  }
116
123
  return sections;
117
124
  }
125
+ async function fetchAgentResolutions() {
126
+ const result = await apiCall("/api/ai_assistant/settings");
127
+ if (!result.ok || !result.result) return { agents: [] };
128
+ return { agents: result.result.agents ?? [] };
129
+ }
130
+ function ModelResolutionPanel({ agentId }) {
131
+ const t = useT();
132
+ const { data } = useQuery({
133
+ queryKey: ["ai_assistant", "settings", "agents"],
134
+ queryFn: fetchAgentResolutions,
135
+ staleTime: 3e4
136
+ });
137
+ const resolution = data?.agents.find((agent) => agent.agentId === agentId);
138
+ if (!resolution) return null;
139
+ return /* @__PURE__ */ jsxs(
140
+ "dl",
141
+ {
142
+ className: "grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs sm:grid-cols-4",
143
+ "data-ai-playground-model-resolution": agentId,
144
+ "data-ai-playground-resolution-panel": agentId,
145
+ children: [
146
+ /* @__PURE__ */ jsxs("div", { children: [
147
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.resolution.provider", "Provider") }),
148
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", "data-ai-playground-resolution-provider": true, children: resolution.providerId })
149
+ ] }),
150
+ /* @__PURE__ */ jsxs("div", { children: [
151
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.resolution.model", "Model") }),
152
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", "data-ai-playground-resolution-model": true, children: resolution.modelId })
153
+ ] }),
154
+ /* @__PURE__ */ jsxs("div", { children: [
155
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.resolution.baseUrl", "Base URL") }),
156
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", "data-ai-playground-resolution-base-url": true, children: resolution.baseURL ?? t("ai_assistant.playground.resolution.none", "\u2014") })
157
+ ] }),
158
+ /* @__PURE__ */ jsxs("div", { children: [
159
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.resolution.source", "Source") }),
160
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", "data-ai-playground-resolution-source": true, children: resolution.source })
161
+ ] })
162
+ ]
163
+ }
164
+ );
165
+ }
118
166
  function readPlaygroundUiPartSeeds() {
119
167
  if (typeof window === "undefined") return [];
120
168
  try {
@@ -155,20 +203,20 @@ function ChatLane({ agent, debug }) {
155
203
  ) })
156
204
  ] });
157
205
  }
158
- return /* @__PURE__ */ jsx(
206
+ return /* @__PURE__ */ jsx("div", { "data-ai-playground-chat": agent.id, children: /* @__PURE__ */ jsx(
159
207
  AiChat,
160
208
  {
161
209
  agent: agent.id,
162
210
  pageContext: { source: "playground", pageId: "ai_assistant.playground" },
163
211
  debug,
164
212
  registry,
165
- className: "min-h-[360px]",
213
+ className: "min-h-96",
166
214
  debugTools,
167
215
  debugPromptSections,
168
216
  uiParts
169
217
  },
170
218
  agent.id
171
- );
219
+ ) });
172
220
  }
173
221
  function ObjectLane({ agent }) {
174
222
  const t = useT();
@@ -435,7 +483,8 @@ function AiPlaygroundPageClient() {
435
483
  )
436
484
  ] })
437
485
  ] }),
438
- selectedAgent ? /* @__PURE__ */ jsx(AgentDetails, { agent: selectedAgent }) : null
486
+ selectedAgent ? /* @__PURE__ */ jsx(AgentDetails, { agent: selectedAgent }) : null,
487
+ selectedAgent ? /* @__PURE__ */ jsx(ModelResolutionPanel, { agentId: selectedAgent.id }) : null
439
488
  ] }),
440
489
  selectedAgent ? /* @__PURE__ */ jsxs(
441
490
  Tabs,