@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
@@ -1,36 +1,332 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { useQuery } from "@tanstack/react-query";
5
- import { Bot, Loader2, CheckCircle2, XCircle, ChevronDown, ChevronRight, Server, Wrench, Eye, EyeOff, Database, Link2, Settings, Key } from "lucide-react";
3
+ import * as React from "react";
4
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { Bot, Loader2, CheckCircle2, XCircle, ChevronDown, ChevronRight, Server, Wrench, Eye, EyeOff, Database, Link2, Settings, Key, X } from "lucide-react";
6
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
7
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
8
+ import { AI_ASSISTANT_LAUNCHER_OPEN_EVENT } from "@open-mercato/ui/ai/AiAssistantLauncher";
9
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
6
10
  import { Button } from "@open-mercato/ui/primitives/button";
7
11
  import { Switch } from "@open-mercato/ui/primitives/switch";
12
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@open-mercato/ui/primitives/select";
13
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
8
14
  import { useAiAssistantVisibility } from "../../../frontend/hooks/useAiAssistantVisibility.js";
9
15
  import McpConfigDialog from "./McpConfigDialog.js";
10
16
  import SessionKeyDialog from "./SessionKeyDialog.js";
17
+ const LEGACY_AI_ASSISTANT_OPEN_EVENT = "om:open-ai-chat";
11
18
  async function fetchHealth() {
12
- const res = await fetch("/api/ai_assistant/health");
13
- if (!res.ok) throw new Error("Failed to fetch health");
14
- return res.json();
19
+ const result = await apiCall("/api/ai_assistant/health");
20
+ if (!result.ok || !result.result) throw new Error("Failed to fetch health");
21
+ return result.result;
15
22
  }
16
23
  async function fetchSettings() {
17
- const res = await fetch("/api/ai_assistant/settings");
18
- if (!res.ok) throw new Error("Failed to fetch settings");
19
- return res.json();
24
+ const result = await apiCall("/api/ai_assistant/settings");
25
+ if (!result.ok || !result.result) throw new Error("Failed to fetch settings");
26
+ return result.result;
20
27
  }
21
28
  async function fetchTools() {
22
- const res = await fetch("/api/ai_assistant/tools");
23
- if (!res.ok) throw new Error("Failed to fetch tools");
24
- return res.json();
29
+ const result = await apiCall("/api/ai_assistant/tools");
30
+ if (!result.ok || !result.result) throw new Error("Failed to fetch tools");
31
+ return result.result;
25
32
  }
26
- function AiAssistantSettingsContent() {
27
- const [toolsExpanded, setToolsExpanded] = useState(false);
28
- const [mcpConfigOpen, setMcpConfigOpen] = useState(false);
29
- const [sessionKeyOpen, setSessionKeyOpen] = useState(false);
33
+ function GlobalOverrideForm({
34
+ availableProviders,
35
+ tenantOverride,
36
+ onSaved
37
+ }) {
38
+ const t = useT();
39
+ const [selectedProviderId, setSelectedProviderId] = React.useState(
40
+ tenantOverride?.providerId ?? ""
41
+ );
42
+ const [selectedModelId, setSelectedModelId] = React.useState(
43
+ tenantOverride?.modelId ?? ""
44
+ );
45
+ const selectedProvider = availableProviders.find((p) => p.id === selectedProviderId);
46
+ const { runMutation: runSaveMutation } = useGuardedMutation({ contextId: "ai-settings-save-override" });
47
+ const { runMutation: runClearMutation } = useGuardedMutation({ contextId: "ai-settings-clear-override" });
48
+ const [isSaving, setIsSaving] = React.useState(false);
49
+ const [isClearing, setIsClearing] = React.useState(false);
50
+ const handleSave = React.useCallback(async () => {
51
+ setIsSaving(true);
52
+ try {
53
+ await runSaveMutation({
54
+ operation: async () => {
55
+ const result = await apiCall("/api/ai_assistant/settings", {
56
+ method: "PUT",
57
+ headers: { "Content-Type": "application/json" },
58
+ body: JSON.stringify({
59
+ providerId: selectedProviderId || null,
60
+ modelId: selectedModelId || null
61
+ })
62
+ });
63
+ if (!result.ok) {
64
+ const err = result.result?.error;
65
+ throw new Error(err ?? t("ai_assistant.settings.saveError", "Failed to save override."));
66
+ }
67
+ },
68
+ context: {}
69
+ });
70
+ flash(t("ai_assistant.settings.saveSuccess", "Default model override saved."), "success");
71
+ onSaved();
72
+ } finally {
73
+ setIsSaving(false);
74
+ }
75
+ }, [onSaved, runSaveMutation, selectedModelId, selectedProviderId, t]);
76
+ const handleClear = React.useCallback(async () => {
77
+ setIsClearing(true);
78
+ try {
79
+ await runClearMutation({
80
+ operation: async () => {
81
+ const result = await apiCall("/api/ai_assistant/settings", {
82
+ method: "DELETE",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({})
85
+ });
86
+ if (!result.ok) {
87
+ const err = result.result?.error;
88
+ throw new Error(err ?? t("ai_assistant.settings.clearError", "Failed to clear override."));
89
+ }
90
+ },
91
+ context: {}
92
+ });
93
+ flash(t("ai_assistant.settings.clearSuccess", "Default model override cleared."), "success");
94
+ setSelectedProviderId("");
95
+ setSelectedModelId("");
96
+ onSaved();
97
+ } finally {
98
+ setIsClearing(false);
99
+ }
100
+ }, [runClearMutation, onSaved, t]);
101
+ const isBusy = isSaving || isClearing;
102
+ const configuredProviders = availableProviders.filter((p) => p.configured);
103
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6", "data-ai-settings-override-form": "", children: [
104
+ /* @__PURE__ */ jsx("h2", { className: "mb-1 text-sm font-semibold", children: t("ai_assistant.settings.defaultOverrideTitle", "Default model override") }),
105
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: t(
106
+ "ai_assistant.settings.defaultOverrideDescription",
107
+ "Set a tenant-wide default provider and model. Agents with a per-agent override or specific defaultModel will take precedence."
108
+ ) }),
109
+ tenantOverride && (tenantOverride.providerId || tenantOverride.modelId) ? /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2 text-xs", children: [
110
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("ai_assistant.settings.currentOverride", "Current override:") }),
111
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
112
+ tenantOverride.providerId ?? "\u2014",
113
+ " / ",
114
+ tenantOverride.modelId ?? "\u2014"
115
+ ] }),
116
+ /* @__PURE__ */ jsxs(
117
+ Button,
118
+ {
119
+ type: "button",
120
+ variant: "ghost",
121
+ size: "sm",
122
+ className: "ml-auto h-6 gap-1 text-xs",
123
+ disabled: isBusy,
124
+ onClick: handleClear,
125
+ "data-ai-settings-clear-override": "",
126
+ children: [
127
+ /* @__PURE__ */ jsx(X, { className: "size-3", "aria-hidden": true }),
128
+ t("ai_assistant.settings.clearOverride", "Clear override")
129
+ ]
130
+ }
131
+ )
132
+ ] }) : null,
133
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [
134
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
135
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("ai_assistant.settings.providerLabel", "Provider") }),
136
+ /* @__PURE__ */ jsxs(
137
+ Select,
138
+ {
139
+ value: selectedProviderId,
140
+ onValueChange: (val) => {
141
+ setSelectedProviderId(val);
142
+ setSelectedModelId("");
143
+ },
144
+ disabled: isBusy,
145
+ children: [
146
+ /* @__PURE__ */ jsx(
147
+ SelectTrigger,
148
+ {
149
+ className: "w-[180px]",
150
+ "data-ai-settings-provider-select": "",
151
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("ai_assistant.settings.selectProvider", "Select provider") })
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(SelectContent, { children: configuredProviders.map((provider) => /* @__PURE__ */ jsx(SelectItem, { value: provider.id, children: provider.name }, provider.id)) })
155
+ ]
156
+ }
157
+ )
158
+ ] }),
159
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
160
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("ai_assistant.settings.modelLabel", "Model") }),
161
+ /* @__PURE__ */ jsxs(
162
+ Select,
163
+ {
164
+ value: selectedModelId,
165
+ onValueChange: setSelectedModelId,
166
+ disabled: isBusy || !selectedProviderId,
167
+ children: [
168
+ /* @__PURE__ */ jsx(
169
+ SelectTrigger,
170
+ {
171
+ className: "w-[220px]",
172
+ "data-ai-settings-model-select": "",
173
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("ai_assistant.settings.selectModel", "Select model") })
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(SelectContent, { children: (selectedProvider?.defaultModels ?? []).map((model) => /* @__PURE__ */ jsx(SelectItem, { value: model.id, children: model.name }, model.id)) })
177
+ ]
178
+ }
179
+ )
180
+ ] }),
181
+ /* @__PURE__ */ jsxs(
182
+ Button,
183
+ {
184
+ type: "button",
185
+ size: "sm",
186
+ disabled: isBusy || !selectedProviderId || !selectedModelId,
187
+ onClick: handleSave,
188
+ "data-ai-settings-save-override": "",
189
+ children: [
190
+ isSaving ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin", "aria-hidden": true }) : null,
191
+ t("ai_assistant.settings.saveOverride", "Save override")
192
+ ]
193
+ }
194
+ )
195
+ ] })
196
+ ] });
197
+ }
198
+ function PerAgentOverrideList({
199
+ agents,
200
+ onCleared
201
+ }) {
202
+ const t = useT();
203
+ const { runMutation: runClearAgentMutation } = useGuardedMutation({ contextId: "ai-settings-clear-agent-override" });
204
+ const [clearingAgentId, setClearingAgentId] = React.useState(null);
205
+ const handleClearAgentOverride = React.useCallback(
206
+ async (agentId) => {
207
+ setClearingAgentId(agentId);
208
+ try {
209
+ await runClearAgentMutation({
210
+ operation: async () => {
211
+ const result = await apiCall("/api/ai_assistant/settings", {
212
+ method: "DELETE",
213
+ headers: { "Content-Type": "application/json" },
214
+ body: JSON.stringify({ agentId })
215
+ });
216
+ if (!result.ok) {
217
+ const err = result.result?.error;
218
+ throw new Error(err ?? t("ai_assistant.settings.clearAgentError", "Failed to clear agent override."));
219
+ }
220
+ },
221
+ context: {}
222
+ });
223
+ flash(t("ai_assistant.settings.clearAgentSuccess", "Agent override cleared."), "success");
224
+ onCleared();
225
+ } finally {
226
+ setClearingAgentId(null);
227
+ }
228
+ },
229
+ [runClearAgentMutation, onCleared, t]
230
+ );
231
+ if (agents.length === 0) return null;
232
+ const overriddenAgents = agents.filter((agent) => agent.source !== "env_default" && agent.source !== "provider_default");
233
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6", "data-ai-settings-agent-overrides": "", children: [
234
+ /* @__PURE__ */ jsx("h2", { className: "mb-1 text-sm font-semibold", children: t("ai_assistant.settings.agentOverridesTitle", "Per-agent model resolution") }),
235
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: t(
236
+ "ai_assistant.settings.agentOverridesDescription",
237
+ "Resolved model for each registered agent. Agents with a custom override show a Clear button."
238
+ ) }),
239
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
240
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border text-left text-muted-foreground", children: [
241
+ /* @__PURE__ */ jsx("th", { className: "pb-2 pr-4 font-medium", children: t("ai_assistant.settings.agentIdColumn", "Agent") }),
242
+ /* @__PURE__ */ jsx("th", { className: "pb-2 pr-4 font-medium", children: t("ai_assistant.settings.providerColumn", "Provider") }),
243
+ /* @__PURE__ */ jsx("th", { className: "pb-2 pr-4 font-medium", children: t("ai_assistant.settings.modelColumn", "Model") }),
244
+ /* @__PURE__ */ jsx("th", { className: "pb-2 pr-4 font-medium", children: t("ai_assistant.settings.sourceColumn", "Source") }),
245
+ /* @__PURE__ */ jsx("th", { className: "pb-2 font-medium" })
246
+ ] }) }),
247
+ /* @__PURE__ */ jsx("tbody", { children: agents.map((agent) => {
248
+ const hasOverride = overriddenAgents.some((a) => a.agentId === agent.agentId);
249
+ return /* @__PURE__ */ jsxs(
250
+ "tr",
251
+ {
252
+ className: "border-b border-border/50 last:border-0",
253
+ "data-ai-settings-agent-row": agent.agentId,
254
+ children: [
255
+ /* @__PURE__ */ jsx("td", { className: "py-2 pr-4 font-mono", children: agent.agentId }),
256
+ /* @__PURE__ */ jsx("td", { className: "py-2 pr-4", children: agent.providerId }),
257
+ /* @__PURE__ */ jsx("td", { className: "py-2 pr-4", children: agent.modelId }),
258
+ /* @__PURE__ */ jsx("td", { className: "py-2 pr-4 text-muted-foreground", children: agent.source }),
259
+ /* @__PURE__ */ jsx("td", { className: "py-2", children: hasOverride ? /* @__PURE__ */ jsxs(
260
+ Button,
261
+ {
262
+ type: "button",
263
+ variant: "ghost",
264
+ size: "sm",
265
+ className: "h-6 gap-1 text-xs",
266
+ disabled: clearingAgentId !== null,
267
+ onClick: () => void handleClearAgentOverride(agent.agentId),
268
+ "data-ai-settings-clear-agent-override": agent.agentId,
269
+ children: [
270
+ /* @__PURE__ */ jsx(X, { className: "size-3", "aria-hidden": true }),
271
+ t("ai_assistant.settings.clearOverride", "Clear override")
272
+ ]
273
+ }
274
+ ) : null })
275
+ ]
276
+ },
277
+ agent.agentId
278
+ );
279
+ }) })
280
+ ] }) })
281
+ ] });
282
+ }
283
+ function AiAssistantLauncherCard({
284
+ launchMode,
285
+ showVisibilityControl
286
+ }) {
287
+ const t = useT();
30
288
  const { isEnabled, toggleEnabled, isLoaded } = useAiAssistantVisibility();
31
289
  const openAiAssistant = () => {
32
- window.dispatchEvent(new CustomEvent("om:open-ai-chat"));
290
+ const eventName = launchMode === "legacy" ? LEGACY_AI_ASSISTANT_OPEN_EVENT : AI_ASSISTANT_LAUNCHER_OPEN_EVENT;
291
+ window.dispatchEvent(new CustomEvent(eventName));
33
292
  };
293
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
294
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
295
+ /* @__PURE__ */ jsxs("h2", { className: "flex items-center gap-2 text-lg font-semibold", children: [
296
+ /* @__PURE__ */ jsx(Bot, { className: "size-5" }),
297
+ t("ai_assistant.settings.visibilityTitle", "AI Assistant")
298
+ ] }),
299
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: showVisibilityControl ? isEnabled ? t("ai_assistant.settings.visibilityEnabled", "Visible in header with Cmd+J shortcut enabled.") : t("ai_assistant.settings.visibilityDisabled", "Hidden from header. Enable to show the button and Cmd+J shortcut.") : t("ai_assistant.settings.launchDescription", "Open the AI assistant from this page.") })
300
+ ] }),
301
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
302
+ showVisibilityControl ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-md border bg-muted/30 px-4 py-2", children: [
303
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("ai_assistant.settings.visibilityToggleLabel", "Visibility") }),
304
+ isEnabled ? /* @__PURE__ */ jsx(Eye, { className: "size-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(EyeOff, { className: "size-4 text-muted-foreground" }),
305
+ /* @__PURE__ */ jsx(
306
+ Switch,
307
+ {
308
+ checked: isEnabled,
309
+ onCheckedChange: toggleEnabled,
310
+ disabled: !isLoaded
311
+ }
312
+ )
313
+ ] }) : null,
314
+ /* @__PURE__ */ jsxs(Button, { type: "button", onClick: openAiAssistant, size: "default", className: "gap-2", children: [
315
+ /* @__PURE__ */ jsx(Bot, { className: "size-4" }),
316
+ launchMode === "legacy" ? t("ai_assistant.settings.openButton", "Open AI Assistant") : t("ai_assistant.settings.openSelectorButton", "Open AI assistants")
317
+ ] })
318
+ ] })
319
+ ] }) });
320
+ }
321
+ function AiAssistantSettingsContent({
322
+ launchMode,
323
+ showVisibilityControl
324
+ }) {
325
+ const t = useT();
326
+ const queryClient = useQueryClient();
327
+ const [toolsExpanded, setToolsExpanded] = React.useState(false);
328
+ const [mcpConfigOpen, setMcpConfigOpen] = React.useState(false);
329
+ const [sessionKeyOpen, setSessionKeyOpen] = React.useState(false);
34
330
  const healthQuery = useQuery({
35
331
  queryKey: ["ai-assistant", "health"],
36
332
  queryFn: fetchHealth,
@@ -47,16 +343,19 @@ function AiAssistantSettingsContent() {
47
343
  queryFn: fetchTools,
48
344
  staleTime: 6e4
49
345
  });
346
+ const handleOverrideSaved = React.useCallback(() => {
347
+ void queryClient.invalidateQueries({ queryKey: ["ai-assistant", "settings"] });
348
+ }, [queryClient]);
50
349
  const isLoading = healthQuery.isLoading || settingsQuery.isLoading || toolsQuery.isLoading;
51
350
  if (isLoading) {
52
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground py-8", children: [
53
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
54
- "Loading settings..."
351
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-8 text-muted-foreground", children: [
352
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }),
353
+ t("ai_assistant.settings.loading", "Loading settings...")
55
354
  ] });
56
355
  }
57
356
  const health = healthQuery.data;
58
357
  const settings = settingsQuery.data;
59
- const tools = toolsQuery.data?.tools || [];
358
+ const tools = toolsQuery.data?.tools ?? [];
60
359
  const toolsByModule = tools.reduce((acc, tool) => {
61
360
  const module = tool.module || "other";
62
361
  if (!acc[module]) acc[module] = [];
@@ -64,155 +363,175 @@ function AiAssistantSettingsContent() {
64
363
  return acc;
65
364
  }, {});
66
365
  const provider = settings?.provider;
67
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
366
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", "data-ai-assistant-settings": "", children: [
68
367
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
69
- /* @__PURE__ */ jsxs("h1", { className: "text-2xl font-bold flex items-center gap-2", children: [
70
- /* @__PURE__ */ jsx(Bot, { className: "h-6 w-6" }),
71
- "AI Assistant Settings"
368
+ /* @__PURE__ */ jsxs("h1", { className: "flex items-center gap-2 text-2xl font-bold", children: [
369
+ /* @__PURE__ */ jsx(Bot, { className: "size-6" }),
370
+ t("ai_assistant.settings.pageTitle", "AI Assistant Settings")
72
371
  ] }),
73
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Configure and monitor the AI assistant" })
372
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("ai_assistant.settings.pageDescription", "Configure and monitor the AI assistant") })
74
373
  ] }),
75
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
76
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
77
- /* @__PURE__ */ jsxs("h2", { className: "text-lg font-semibold flex items-center gap-2", children: [
78
- /* @__PURE__ */ jsx(Bot, { className: "h-5 w-5" }),
79
- "AI Assistant"
80
- ] }),
81
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isEnabled ? "Visible in header with Cmd+J shortcut enabled." : "Hidden from header. Enable to show the button and Cmd+J shortcut." })
82
- ] }),
83
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
84
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-2 rounded-md border bg-muted/30", children: [
85
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Visibility" }),
86
- isEnabled ? /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4 text-muted-foreground" }),
87
- /* @__PURE__ */ jsx(
88
- Switch,
89
- {
90
- checked: isEnabled,
91
- onCheckedChange: toggleEnabled,
92
- disabled: !isLoaded
93
- }
94
- )
95
- ] }),
96
- /* @__PURE__ */ jsxs(Button, { onClick: openAiAssistant, size: "default", className: "gap-2", children: [
97
- /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4" }),
98
- "Open AI Assistant"
99
- ] })
100
- ] })
101
- ] }) }),
374
+ /* @__PURE__ */ jsx(
375
+ AiAssistantLauncherCard,
376
+ {
377
+ launchMode,
378
+ showVisibilityControl
379
+ }
380
+ ),
381
+ settings ? /* @__PURE__ */ jsx(
382
+ GlobalOverrideForm,
383
+ {
384
+ availableProviders: settings.availableProviders,
385
+ tenantOverride: settings.tenantOverride,
386
+ onSaved: handleOverrideSaved
387
+ }
388
+ ) : null,
389
+ settings?.agents && settings.agents.length > 0 ? /* @__PURE__ */ jsx(
390
+ PerAgentOverrideList,
391
+ {
392
+ agents: settings.agents,
393
+ onCleared: handleOverrideSaved
394
+ }
395
+ ) : null,
102
396
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6", children: [
103
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
104
- /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold flex items-center gap-2", children: [
105
- /* @__PURE__ */ jsx(Link2, { className: "h-4 w-4" }),
106
- "Connections"
397
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between", children: [
398
+ /* @__PURE__ */ jsxs("h2", { className: "flex items-center gap-2 text-sm font-semibold", children: [
399
+ /* @__PURE__ */ jsx(Link2, { className: "size-4" }),
400
+ t("ai_assistant.settings.connectionsTitle", "Connections")
107
401
  ] }),
108
- healthQuery.isFetching && !healthQuery.isLoading && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin text-muted-foreground" })
402
+ healthQuery.isFetching && !healthQuery.isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin text-muted-foreground" }) : null
109
403
  ] }),
110
404
  /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [
111
- /* @__PURE__ */ jsx("div", { className: `p-4 rounded-lg border-2 ${health?.status === "ok" && health.opencode?.healthy ? "border-status-success-border bg-status-success-bg" : "border-destructive/50 bg-destructive/5"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
112
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
113
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
114
- /* @__PURE__ */ jsx(Server, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
115
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "OpenCode" })
116
- ] }),
117
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: health?.status === "ok" && health.opencode?.healthy ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
118
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3" }),
119
- "Connected"
120
- ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
121
- /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3" }),
122
- health?.message || "Disconnected"
123
- ] }) }),
124
- health?.opencode?.version && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
125
- "v",
126
- health.opencode.version
127
- ] }),
128
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: health?.url || "Not configured" })
129
- ] }),
130
- health?.status === "ok" && health.opencode?.healthy && /* @__PURE__ */ jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })
131
- ] }) }),
405
+ /* @__PURE__ */ jsx(
406
+ "div",
407
+ {
408
+ className: `rounded-lg border-2 p-4 ${health?.status === "ok" && health.opencode?.healthy ? "border-status-success-border bg-status-success-bg" : "border-destructive/50 bg-destructive/5"}`,
409
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
410
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
411
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
412
+ /* @__PURE__ */ jsx(Server, { className: "size-4 shrink-0 text-muted-foreground" }),
413
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "OpenCode" })
414
+ ] }),
415
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: health?.status === "ok" && health.opencode?.healthy ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
416
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3" }),
417
+ t("ai_assistant.settings.connected", "Connected")
418
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
419
+ /* @__PURE__ */ jsx(XCircle, { className: "size-3" }),
420
+ health?.message ?? t("ai_assistant.settings.disconnected", "Disconnected")
421
+ ] }) }),
422
+ health?.opencode?.version ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
423
+ "v",
424
+ health.opencode.version
425
+ ] }) : null,
426
+ /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-muted-foreground", children: health?.url ?? t("ai_assistant.settings.notConfigured", "Not configured") })
427
+ ] }),
428
+ health?.status === "ok" && health.opencode?.healthy ? /* @__PURE__ */ jsx("div", { className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon", children: /* @__PURE__ */ jsx("svg", { className: "size-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) }) : null
429
+ ] })
430
+ }
431
+ ),
132
432
  (() => {
133
433
  const mcpConnected = health?.mcp && Object.values(health.mcp).some((s) => s.status === "connected");
134
434
  const mcpConnecting = health?.mcp && Object.values(health.mcp).some((s) => s.status === "connecting");
135
435
  const mcpError = health?.mcp && Object.values(health.mcp).find((s) => s.error)?.error;
136
- return /* @__PURE__ */ jsx("div", { className: `p-4 rounded-lg border-2 ${mcpConnected ? "border-status-success-border bg-status-success-bg" : mcpConnecting ? "border-status-warning-border bg-status-warning-bg" : "border-destructive/50 bg-destructive/5"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
137
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
138
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
139
- /* @__PURE__ */ jsx(Wrench, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
140
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "MCP Server" })
141
- ] }),
142
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: mcpConnected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
143
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3" }),
144
- "Connected"
145
- ] }) : mcpConnecting ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-warning-text", children: [
146
- /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
147
- "Connecting..."
148
- ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
149
- /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3" }),
150
- mcpError || "Disconnected"
151
- ] }) }),
152
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: health?.mcpUrl || "Not configured" })
153
- ] }),
154
- mcpConnected && /* @__PURE__ */ jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })
155
- ] }) });
436
+ return /* @__PURE__ */ jsx(
437
+ "div",
438
+ {
439
+ className: `rounded-lg border-2 p-4 ${mcpConnected ? "border-status-success-border bg-status-success-bg" : mcpConnecting ? "border-status-warning-border bg-status-warning-bg" : "border-destructive/50 bg-destructive/5"}`,
440
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
441
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
442
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
443
+ /* @__PURE__ */ jsx(Wrench, { className: "size-4 shrink-0 text-muted-foreground" }),
444
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "MCP Server" })
445
+ ] }),
446
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: mcpConnected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
447
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3" }),
448
+ t("ai_assistant.settings.connected", "Connected")
449
+ ] }) : mcpConnecting ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-warning-text", children: [
450
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }),
451
+ t("ai_assistant.settings.connecting", "Connecting...")
452
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
453
+ /* @__PURE__ */ jsx(XCircle, { className: "size-3" }),
454
+ mcpError ?? t("ai_assistant.settings.disconnected", "Disconnected")
455
+ ] }) }),
456
+ /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-muted-foreground", children: health?.mcpUrl ?? t("ai_assistant.settings.notConfigured", "Not configured") })
457
+ ] }),
458
+ mcpConnected ? /* @__PURE__ */ jsx("div", { className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon", children: /* @__PURE__ */ jsx("svg", { className: "size-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) }) : null
459
+ ] })
460
+ }
461
+ );
156
462
  })(),
157
- /* @__PURE__ */ jsx("div", { className: `p-4 rounded-lg border-2 ${health?.search?.available ? "border-status-success-border bg-status-success-bg" : "border-destructive/50 bg-destructive/5"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
158
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
159
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
160
- /* @__PURE__ */ jsx(Database, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
161
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Meilisearch" })
162
- ] }),
163
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: health?.search?.available ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
164
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3" }),
165
- "Connected"
166
- ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
167
- /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3" }),
168
- "Not available"
169
- ] }) }),
170
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 truncate", children: health?.search?.url || "Not configured" })
171
- ] }),
172
- health?.search?.available && /* @__PURE__ */ jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })
173
- ] }) })
463
+ /* @__PURE__ */ jsx(
464
+ "div",
465
+ {
466
+ className: `rounded-lg border-2 p-4 ${health?.search?.available ? "border-status-success-border bg-status-success-bg" : "border-destructive/50 bg-destructive/5"}`,
467
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
468
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
469
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
470
+ /* @__PURE__ */ jsx(Database, { className: "size-4 shrink-0 text-muted-foreground" }),
471
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Meilisearch" })
472
+ ] }),
473
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: health?.search?.available ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text", children: [
474
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3" }),
475
+ t("ai_assistant.settings.connected", "Connected")
476
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-destructive", children: [
477
+ /* @__PURE__ */ jsx(XCircle, { className: "size-3" }),
478
+ t("ai_assistant.settings.notAvailable", "Not available")
479
+ ] }) }),
480
+ /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-xs text-muted-foreground", children: health?.search?.url ?? t("ai_assistant.settings.notConfigured", "Not configured") })
481
+ ] }),
482
+ health?.search?.available ? /* @__PURE__ */ jsx("div", { className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon", children: /* @__PURE__ */ jsx("svg", { className: "size-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) }) : null
483
+ ] })
484
+ }
485
+ )
174
486
  ] }),
175
- /* @__PURE__ */ jsxs("div", { className: "mt-4 p-3 rounded-md bg-muted/30 border", children: [
487
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-md border bg-muted/30 p-3", children: [
176
488
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
177
- /* @__PURE__ */ jsx(Key, { className: "h-4 w-4 text-muted-foreground" }),
178
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "MCP Authentication:" }),
179
- settings?.mcpKeyConfigured ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text text-xs", children: [
180
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3" }),
181
- "MCP_SERVER_API_KEY configured"
182
- ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-warning-text text-xs", children: [
183
- /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3" }),
184
- "MCP_SERVER_API_KEY not set"
489
+ /* @__PURE__ */ jsx(Key, { className: "size-4 text-muted-foreground" }),
490
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("ai_assistant.settings.mcpAuthLabel", "MCP Authentication:") }),
491
+ settings?.mcpKeyConfigured ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-status-success-text", children: [
492
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3" }),
493
+ t("ai_assistant.settings.mcpKeyConfigured", "MCP_SERVER_API_KEY configured")
494
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-status-warning-text", children: [
495
+ /* @__PURE__ */ jsx(XCircle, { className: "size-3" }),
496
+ t("ai_assistant.settings.mcpKeyMissing", "MCP_SERVER_API_KEY not set")
185
497
  ] })
186
498
  ] }),
187
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1 ml-6", children: "Required for AI to access platform tools via MCP server." })
499
+ /* @__PURE__ */ jsx("p", { className: "ml-6 mt-1 text-xs text-muted-foreground", children: t(
500
+ "ai_assistant.settings.mcpAuthNote",
501
+ "Required for AI to access platform tools via MCP server."
502
+ ) })
188
503
  ] }),
189
- /* @__PURE__ */ jsx("div", { className: "mt-4 p-3 rounded-md bg-muted/30 border", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
190
- /* @__PURE__ */ jsx(Server, { className: "h-4 w-4 text-muted-foreground" }),
191
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "LLM Provider:" }),
192
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: provider?.name || "Anthropic" }),
193
- provider?.configured ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-success-text text-xs", children: [
194
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3" }),
195
- provider?.envKey,
196
- " configured"
197
- ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-status-warning-text text-xs", children: [
198
- /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3" }),
199
- provider?.envKey || "ANTHROPIC_API_KEY",
200
- " not set"
504
+ /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-md border bg-muted/30 p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
505
+ /* @__PURE__ */ jsx(Server, { className: "size-4 text-muted-foreground" }),
506
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("ai_assistant.settings.llmProviderLabel", "LLM Provider:") }),
507
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: provider?.name ?? "Anthropic" }),
508
+ provider?.configured ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-status-success-text", children: [
509
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3" }),
510
+ provider?.envKey ? t("ai_assistant.settings.envKeyConfigured", "{{key}} configured", { key: provider.envKey }) : t("ai_assistant.settings.configured", "Configured")
511
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-status-warning-text", children: [
512
+ /* @__PURE__ */ jsx(XCircle, { className: "size-3" }),
513
+ t("ai_assistant.settings.envKeyMissing", "{{key}} not set", { key: provider?.envKey ?? "ANTHROPIC_API_KEY" })
201
514
  ] })
202
515
  ] }) }),
203
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-4", children: "Meilisearch is required for API endpoint discovery. Endpoints are indexed automatically when the MCP server starts." })
516
+ /* @__PURE__ */ jsx("p", { className: "mt-4 text-xs text-muted-foreground", children: t(
517
+ "ai_assistant.settings.meilisearchNote",
518
+ "Meilisearch is required for API endpoint discovery. Endpoints are indexed automatically when the MCP server starts."
519
+ ) })
204
520
  ] }),
205
521
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6", children: [
206
- /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold mb-4 flex items-center gap-2", children: [
207
- /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
208
- "Developer Tools"
522
+ /* @__PURE__ */ jsxs("h2", { className: "mb-4 flex items-center gap-2 text-sm font-semibold", children: [
523
+ /* @__PURE__ */ jsx(Settings, { className: "size-4" }),
524
+ t("ai_assistant.settings.developerToolsTitle", "Developer Tools")
209
525
  ] }),
210
526
  /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
211
- /* @__PURE__ */ jsx("div", { className: "p-4 rounded-lg border bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
212
- /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-primary/10", children: /* @__PURE__ */ jsx(Settings, { className: "h-5 w-5 text-primary" }) }),
213
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
214
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "MCP Configuration" }),
215
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Generate config for Claude Code or other MCP clients." }),
527
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-muted/30 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
528
+ /* @__PURE__ */ jsx("div", { className: "rounded-md bg-primary/10 p-2", children: /* @__PURE__ */ jsx(Settings, { className: "size-5 text-primary" }) }),
529
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
530
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("ai_assistant.settings.mcpConfigTitle", "MCP Configuration") }),
531
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
532
+ "ai_assistant.settings.mcpConfigDescription",
533
+ "Generate config for Claude Code or other MCP clients."
534
+ ) }),
216
535
  /* @__PURE__ */ jsx(
217
536
  Button,
218
537
  {
@@ -220,16 +539,19 @@ function AiAssistantSettingsContent() {
220
539
  variant: "outline",
221
540
  className: "mt-3",
222
541
  onClick: () => setMcpConfigOpen(true),
223
- children: "Generate MCP Config"
542
+ children: t("ai_assistant.settings.generateMcpConfig", "Generate MCP Config")
224
543
  }
225
544
  )
226
545
  ] })
227
546
  ] }) }),
228
- /* @__PURE__ */ jsx("div", { className: "p-4 rounded-lg border bg-muted/30", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
229
- /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-primary/10", children: /* @__PURE__ */ jsx(Key, { className: "h-5 w-5 text-primary" }) }),
230
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
231
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Session API Key" }),
232
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Generate a temporary token for programmatic LLM access. Expires after 2 hours." }),
547
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-muted/30 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
548
+ /* @__PURE__ */ jsx("div", { className: "rounded-md bg-primary/10 p-2", children: /* @__PURE__ */ jsx(Key, { className: "size-5 text-primary" }) }),
549
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
550
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("ai_assistant.settings.sessionKeyTitle", "Session API Key") }),
551
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
552
+ "ai_assistant.settings.sessionKeyDescription",
553
+ "Generate a temporary token for programmatic LLM access. Expires after 2 hours."
554
+ ) }),
233
555
  /* @__PURE__ */ jsx(
234
556
  Button,
235
557
  {
@@ -237,7 +559,7 @@ function AiAssistantSettingsContent() {
237
559
  variant: "outline",
238
560
  className: "mt-3",
239
561
  onClick: () => setSessionKeyOpen(true),
240
- children: "Generate Session Key"
562
+ children: t("ai_assistant.settings.generateSessionKey", "Generate Session Key")
241
563
  }
242
564
  )
243
565
  ] })
@@ -249,7 +571,7 @@ function AiAssistantSettingsContent() {
249
571
  {
250
572
  open: mcpConfigOpen,
251
573
  onOpenChange: setMcpConfigOpen,
252
- mcpUrl: health?.mcpUrl || "http://localhost:3001"
574
+ mcpUrl: health?.mcpUrl ?? "http://localhost:3001"
253
575
  }
254
576
  ),
255
577
  /* @__PURE__ */ jsx(
@@ -263,31 +585,44 @@ function AiAssistantSettingsContent() {
263
585
  /* @__PURE__ */ jsxs(
264
586
  "button",
265
587
  {
266
- onClick: () => setToolsExpanded(!toolsExpanded),
267
- className: "w-full flex items-center justify-between text-left",
588
+ type: "button",
589
+ onClick: () => setToolsExpanded((prev) => !prev),
590
+ className: "flex w-full items-center justify-between text-left",
268
591
  children: [
269
- /* @__PURE__ */ jsxs("h2", { className: "text-sm font-semibold flex items-center gap-2", children: [
270
- /* @__PURE__ */ jsx(Wrench, { className: "h-4 w-4" }),
271
- "MCP Tools (",
592
+ /* @__PURE__ */ jsxs("h2", { className: "flex items-center gap-2 text-sm font-semibold", children: [
593
+ /* @__PURE__ */ jsx(Wrench, { className: "size-4" }),
594
+ t("ai_assistant.settings.mcpToolsTitle", "MCP Tools"),
595
+ " (",
272
596
  tools.length,
273
- " tools)"
597
+ " ",
598
+ t("ai_assistant.settings.mcpToolsCount", "tools"),
599
+ ")"
274
600
  ] }),
275
- toolsExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
601
+ toolsExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 text-muted-foreground" })
276
602
  ]
277
603
  }
278
604
  ),
279
- toolsExpanded && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-4", children: Object.entries(toolsByModule).map(([module, moduleTools]) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
280
- /* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: module }),
281
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: moduleTools.map((tool) => /* @__PURE__ */ jsxs("div", { className: "pl-2 border-l-2 border-muted py-1", children: [
605
+ toolsExpanded ? /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-4", children: Object.entries(toolsByModule).map(([module, moduleTools]) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
606
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: module }),
607
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: moduleTools.map((tool) => /* @__PURE__ */ jsxs("div", { className: "border-l-2 border-muted py-1 pl-2", children: [
282
608
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: tool.name }),
283
609
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tool.description })
284
610
  ] }, tool.name)) })
285
- ] }, module)) })
611
+ ] }, module)) }) : null
286
612
  ] })
287
613
  ] });
288
614
  }
289
- function AiAssistantSettingsPageClient() {
290
- return /* @__PURE__ */ jsx(AiAssistantSettingsContent, {});
615
+ function AiAssistantSettingsPageClient({
616
+ launchMode = "selector",
617
+ showVisibilityControl = false
618
+ }) {
619
+ return /* @__PURE__ */ jsx(
620
+ AiAssistantSettingsContent,
621
+ {
622
+ launchMode,
623
+ showVisibilityControl
624
+ }
625
+ );
291
626
  }
292
627
  var AiAssistantSettingsPageClient_default = AiAssistantSettingsPageClient;
293
628
  export {