@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +82 -18
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +370 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +194 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js +4 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +169 -5
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/route/route.js +38 -19
- package/dist/modules/ai_assistant/api/route/route.js.map +3 -3
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +195 -0
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +537 -22
- package/dist/modules/ai_assistant/api/settings/route.js.map +3 -3
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +701 -147
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +338 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +1 -1
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +75 -26
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js +503 -168
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +123 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +157 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js +77 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +1 -1
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +90 -1
- package/dist/modules/ai_assistant/i18n/en.json +90 -1
- package/dist/modules/ai_assistant/i18n/es.json +90 -1
- package/dist/modules/ai_assistant/i18n/pl.json +90 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js +17 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +133 -36
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js +29 -0
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-allowlist.js +211 -0
- package/dist/modules/ai_assistant/lib/model-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/model-factory.js +203 -31
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +32 -1
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js +18 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js +16 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js +15 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +13 -0
- package/generated/entities/ai_tenant_model_allowlist/index.ts +9 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +26 -0
- package/jest.config.cjs +2 -0
- package/package.json +4 -4
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +477 -0
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +116 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +240 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +251 -0
- package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -0
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +273 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +211 -2
- package/src/modules/ai_assistant/api/route/route.ts +49 -25
- package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +408 -0
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +221 -0
- package/src/modules/ai_assistant/api/settings/route.ts +721 -27
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +858 -177
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +458 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +1 -1
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +89 -12
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.tsx +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +617 -209
- package/src/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.ts +7 -0
- package/src/modules/ai_assistant/data/entities/AiTenantModelAllowlist.ts +2 -0
- package/src/modules/ai_assistant/data/entities.ts +164 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +227 -0
- package/src/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.ts +132 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +337 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTenantModelAllowlistRepository.test.ts +181 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/i18n/de.json +90 -1
- package/src/modules/ai_assistant/i18n/en.json +90 -1
- package/src/modules/ai_assistant/i18n/es.json +90 -1
- package/src/modules/ai_assistant/i18n/pl.json +90 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +396 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +60 -6
- package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +4 -2
- package/src/modules/ai_assistant/lib/__tests__/baseurl-allowlist.test.ts +75 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-google.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +150 -4
- package/src/modules/ai_assistant/lib/__tests__/model-allowlist.test.ts +290 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +634 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +20 -1
- package/src/modules/ai_assistant/lib/agent-runtime.ts +220 -44
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +48 -0
- package/src/modules/ai_assistant/lib/baseurl-allowlist.ts +64 -0
- package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +11 -1
- package/src/modules/ai_assistant/lib/llm-adapters/google.ts +4 -1
- package/src/modules/ai_assistant/lib/model-allowlist.ts +407 -0
- package/src/modules/ai_assistant/lib/model-factory.ts +486 -58
- package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +44 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +704 -235
- package/src/modules/ai_assistant/migrations/Migration20260508140000.ts +18 -0
- package/src/modules/ai_assistant/migrations/Migration20260512090000.ts +16 -0
- package/src/modules/ai_assistant/migrations/Migration20260512130000.ts +13 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
|
|
2
|
+
import { canonicalProviderId } from "../../lib/model-allowlist.js";
|
|
3
|
+
import { AiAgentRuntimeOverride } from "../entities.js";
|
|
4
|
+
class AiAgentRuntimeOverrideRepository {
|
|
5
|
+
constructor(em) {
|
|
6
|
+
this.em = em;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Returns the most-specific active runtime override for the given context.
|
|
10
|
+
*
|
|
11
|
+
* When `agentId` is provided, an agent-specific row takes precedence over a
|
|
12
|
+
* tenant-wide null-agent row. Returns null when neither exists.
|
|
13
|
+
*
|
|
14
|
+
* Never returns a row with `deleted_at IS NOT NULL`.
|
|
15
|
+
*/
|
|
16
|
+
async getDefault(ctx) {
|
|
17
|
+
if (!ctx?.tenantId) return null;
|
|
18
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
19
|
+
if (ctx.agentId) {
|
|
20
|
+
const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {
|
|
21
|
+
tenantId: ctx.tenantId,
|
|
22
|
+
organizationId: orgFilter,
|
|
23
|
+
agentId: ctx.agentId,
|
|
24
|
+
deletedAt: null
|
|
25
|
+
});
|
|
26
|
+
if (agentRow) return agentRow;
|
|
27
|
+
}
|
|
28
|
+
const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {
|
|
29
|
+
tenantId: ctx.tenantId,
|
|
30
|
+
organizationId: orgFilter,
|
|
31
|
+
agentId: null,
|
|
32
|
+
deletedAt: null
|
|
33
|
+
});
|
|
34
|
+
return tenantRow ?? null;
|
|
35
|
+
}
|
|
36
|
+
async getExact(ctx) {
|
|
37
|
+
if (!ctx?.tenantId) return null;
|
|
38
|
+
const row = await this.em.findOne(AiAgentRuntimeOverride, {
|
|
39
|
+
tenantId: ctx.tenantId,
|
|
40
|
+
organizationId: ctx.organizationId ?? null,
|
|
41
|
+
agentId: ctx.agentId ?? null,
|
|
42
|
+
deletedAt: null
|
|
43
|
+
});
|
|
44
|
+
return row ?? null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Inserts or updates the runtime override for the given context.
|
|
48
|
+
*
|
|
49
|
+
* Validates `providerId` against the registry at write time so an admin
|
|
50
|
+
* cannot save a typo (Phase 1.4 contract re-applied per spec §Data Models).
|
|
51
|
+
* An unknown provider id throws a typed error.
|
|
52
|
+
*
|
|
53
|
+
* The R6 base-URL allowlist check is intentionally NOT performed here —
|
|
54
|
+
* that enforcement lives at the HTTP layer (PUT settings route). The
|
|
55
|
+
* repository trusts that callers have already validated the value.
|
|
56
|
+
*/
|
|
57
|
+
async upsertDefault(input, ctx) {
|
|
58
|
+
if (!ctx?.tenantId) {
|
|
59
|
+
throw new Error("AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId");
|
|
60
|
+
}
|
|
61
|
+
const normalizedProviderId = input.providerId ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id)) : null;
|
|
62
|
+
if (input.providerId) {
|
|
63
|
+
const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null;
|
|
64
|
+
if (!knownProvider) {
|
|
65
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
66
|
+
`Unknown provider id "${input.providerId}". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(", ")}.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
71
|
+
const agentIdFilter = input.agentId ?? null;
|
|
72
|
+
const hasProviderId = Object.prototype.hasOwnProperty.call(input, "providerId");
|
|
73
|
+
const hasModelId = Object.prototype.hasOwnProperty.call(input, "modelId");
|
|
74
|
+
const hasBaseURL = Object.prototype.hasOwnProperty.call(input, "baseURL");
|
|
75
|
+
const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, "allowedOverrideProviders");
|
|
76
|
+
const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, "allowedOverrideModelsByProvider");
|
|
77
|
+
return this.em.transactional(async (tx) => {
|
|
78
|
+
const existing = await tx.findOne(AiAgentRuntimeOverride, {
|
|
79
|
+
tenantId: ctx.tenantId,
|
|
80
|
+
organizationId: orgFilter,
|
|
81
|
+
agentId: agentIdFilter,
|
|
82
|
+
deletedAt: null
|
|
83
|
+
});
|
|
84
|
+
if (existing) {
|
|
85
|
+
if (hasProviderId) existing.providerId = normalizedProviderId;
|
|
86
|
+
if (hasModelId) existing.modelId = input.modelId ?? null;
|
|
87
|
+
if (hasBaseURL) existing.baseUrl = input.baseURL ?? null;
|
|
88
|
+
if (hasAllowedOverrideProviders) {
|
|
89
|
+
existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null;
|
|
90
|
+
}
|
|
91
|
+
if (hasAllowedOverrideModels) {
|
|
92
|
+
existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {};
|
|
93
|
+
}
|
|
94
|
+
existing.updatedByUserId = ctx.userId ?? null;
|
|
95
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
96
|
+
await tx.persist(existing).flush();
|
|
97
|
+
return existing;
|
|
98
|
+
}
|
|
99
|
+
const row = tx.create(AiAgentRuntimeOverride, {
|
|
100
|
+
tenantId: ctx.tenantId,
|
|
101
|
+
organizationId: orgFilter,
|
|
102
|
+
agentId: agentIdFilter,
|
|
103
|
+
providerId: hasProviderId ? normalizedProviderId : null,
|
|
104
|
+
modelId: hasModelId ? input.modelId ?? null : null,
|
|
105
|
+
baseUrl: hasBaseURL ? input.baseURL ?? null : null,
|
|
106
|
+
allowedOverrideProviders: hasAllowedOverrideProviders ? input.allowedOverrideProviders ?? null : null,
|
|
107
|
+
allowedOverrideModelsByProvider: hasAllowedOverrideModels ? input.allowedOverrideModelsByProvider ?? {} : {},
|
|
108
|
+
updatedByUserId: ctx.userId ?? null
|
|
109
|
+
});
|
|
110
|
+
await tx.persist(row).flush();
|
|
111
|
+
return row;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Soft-deletes the active override matching the given context by setting
|
|
116
|
+
* `deleted_at = now()`. Returns true when a row was found and cleared,
|
|
117
|
+
* false when no active row existed.
|
|
118
|
+
*/
|
|
119
|
+
async clearDefault(ctx) {
|
|
120
|
+
if (!ctx?.tenantId) return false;
|
|
121
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
122
|
+
const agentIdFilter = ctx.agentId ?? null;
|
|
123
|
+
return this.em.transactional(async (tx) => {
|
|
124
|
+
const existing = await tx.findOne(AiAgentRuntimeOverride, {
|
|
125
|
+
tenantId: ctx.tenantId,
|
|
126
|
+
organizationId: orgFilter,
|
|
127
|
+
agentId: agentIdFilter,
|
|
128
|
+
deletedAt: null
|
|
129
|
+
});
|
|
130
|
+
if (!existing) return false;
|
|
131
|
+
if (existing.allowedOverrideProviders != null || Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0) {
|
|
132
|
+
existing.providerId = null;
|
|
133
|
+
existing.modelId = null;
|
|
134
|
+
existing.baseUrl = null;
|
|
135
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
136
|
+
await tx.persist(existing).flush();
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
existing.deletedAt = /* @__PURE__ */ new Date();
|
|
140
|
+
await tx.persist(existing).flush();
|
|
141
|
+
return true;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
class AiAgentRuntimeOverrideValidationError extends Error {
|
|
146
|
+
constructor(message) {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = "AiAgentRuntimeOverrideValidationError";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
var AiAgentRuntimeOverrideRepository_default = AiAgentRuntimeOverrideRepository;
|
|
152
|
+
export {
|
|
153
|
+
AiAgentRuntimeOverrideRepository,
|
|
154
|
+
AiAgentRuntimeOverrideValidationError,
|
|
155
|
+
AiAgentRuntimeOverrideRepository_default as default
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=AiAgentRuntimeOverrideRepository.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { canonicalProviderId } from '../../lib/model-allowlist'\nimport { AiAgentRuntimeOverride } from '../entities'\n\nexport interface AiAgentRuntimeOverrideContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiAgentRuntimeOverrideInput {\n /** null means tenant-wide default (no agent pinning). */\n agentId?: string | null\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n allowedOverrideProviders?: string[] | null\n allowedOverrideModelsByProvider?: Record<string, string[]>\n}\n\n/**\n * Repository for per-tenant AI runtime overrides (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * All reads MUST filter by `tenant_id` first. The three public methods follow\n * the same fail-safe pattern as sibling repositories: `getDefault` returns\n * null on missing tenant context; `upsertDefault` validates provider id at\n * write time; `clearDefault` soft-deletes via `deleted_at`.\n *\n * Resolution precedence returned by `getDefault`:\n * 1. Agent-specific row (non-null `agent_id`) when `agentId` is provided.\n * 2. Tenant-wide row (`agent_id IS NULL`) for the same `(tenant_id, org_id)`.\n * Both rows are always scoped to the caller's `tenant_id` \u2014 cross-tenant\n * reads are impossible because every query filters `WHERE tenant_id = ?`.\n */\nexport class AiAgentRuntimeOverrideRepository {\n constructor(private readonly em: EntityManager) {}\n\n /**\n * Returns the most-specific active runtime override for the given context.\n *\n * When `agentId` is provided, an agent-specific row takes precedence over a\n * tenant-wide null-agent row. Returns null when neither exists.\n *\n * Never returns a row with `deleted_at IS NOT NULL`.\n */\n async getDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n\n const orgFilter = ctx.organizationId ?? null\n\n // Try agent-specific first when caller provided an agentId.\n if (ctx.agentId) {\n const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: ctx.agentId,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (agentRow) return agentRow\n }\n\n // Fall back to tenant-wide row (agentId = null).\n const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return tenantRow ?? null\n }\n\n async getExact(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n const row = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: ctx.agentId ?? null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return row ?? null\n }\n\n /**\n * Inserts or updates the runtime override for the given context.\n *\n * Validates `providerId` against the registry at write time so an admin\n * cannot save a typo (Phase 1.4 contract re-applied per spec \u00A7Data Models).\n * An unknown provider id throws a typed error.\n *\n * The R6 base-URL allowlist check is intentionally NOT performed here \u2014\n * that enforcement lives at the HTTP layer (PUT settings route). The\n * repository trusts that callers have already validated the value.\n */\n async upsertDefault(\n input: AiAgentRuntimeOverrideInput,\n ctx: AiAgentRuntimeOverrideContext,\n ): Promise<AiAgentRuntimeOverride> {\n if (!ctx?.tenantId) {\n throw new Error('AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId')\n }\n\n const normalizedProviderId = input.providerId\n ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id))\n : null\n if (input.providerId) {\n const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null\n if (!knownProvider) {\n throw new AiAgentRuntimeOverrideValidationError(\n `Unknown provider id \"${input.providerId}\". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(', ')}.`,\n )\n }\n }\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = input.agentId ?? null\n const hasProviderId = Object.prototype.hasOwnProperty.call(input, 'providerId')\n const hasModelId = Object.prototype.hasOwnProperty.call(input, 'modelId')\n const hasBaseURL = Object.prototype.hasOwnProperty.call(input, 'baseURL')\n const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideProviders')\n const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideModelsByProvider')\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n\n if (existing) {\n if (hasProviderId) existing.providerId = normalizedProviderId\n if (hasModelId) existing.modelId = input.modelId ?? null\n if (hasBaseURL) existing.baseUrl = input.baseURL ?? null\n if (hasAllowedOverrideProviders) {\n existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null\n }\n if (hasAllowedOverrideModels) {\n existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {}\n }\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n providerId: hasProviderId ? normalizedProviderId : null,\n modelId: hasModelId ? (input.modelId ?? null) : null,\n baseUrl: hasBaseURL ? (input.baseURL ?? null) : null,\n allowedOverrideProviders: hasAllowedOverrideProviders\n ? (input.allowedOverrideProviders ?? null)\n : null,\n allowedOverrideModelsByProvider: hasAllowedOverrideModels\n ? (input.allowedOverrideModelsByProvider ?? {})\n : {},\n updatedByUserId: ctx.userId ?? null,\n } as unknown as AiAgentRuntimeOverride)\n await tx.persist(row).flush()\n return row\n })\n }\n\n /**\n * Soft-deletes the active override matching the given context by setting\n * `deleted_at = now()`. Returns true when a row was found and cleared,\n * false when no active row existed.\n */\n async clearDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = ctx.agentId ?? null\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (!existing) return false\n if (\n existing.allowedOverrideProviders != null ||\n Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0\n ) {\n existing.providerId = null\n existing.modelId = null\n existing.baseUrl = null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return true\n }\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\n/**\n * Thrown by `upsertDefault` when an unknown provider id is submitted.\n */\nexport class AiAgentRuntimeOverrideValidationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AiAgentRuntimeOverrideValidationError'\n }\n}\n\nexport default AiAgentRuntimeOverrideRepository\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AiTenantModelAllowlist } from "../entities.js";
|
|
2
|
+
class AiTenantModelAllowlistRepository {
|
|
3
|
+
constructor(em) {
|
|
4
|
+
this.em = em;
|
|
5
|
+
}
|
|
6
|
+
async getForTenant(ctx) {
|
|
7
|
+
if (!ctx?.tenantId) return null;
|
|
8
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
9
|
+
const row = await this.em.findOne(AiTenantModelAllowlist, {
|
|
10
|
+
tenantId: ctx.tenantId,
|
|
11
|
+
organizationId: orgFilter,
|
|
12
|
+
deletedAt: null
|
|
13
|
+
});
|
|
14
|
+
return row ?? null;
|
|
15
|
+
}
|
|
16
|
+
async getSnapshot(ctx) {
|
|
17
|
+
const row = await this.getForTenant(ctx);
|
|
18
|
+
if (!row) return null;
|
|
19
|
+
return {
|
|
20
|
+
allowedProviders: row.allowedProviders ?? null,
|
|
21
|
+
allowedModelsByProvider: row.allowedModelsByProvider ?? {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async upsert(input, ctx) {
|
|
25
|
+
if (!ctx?.tenantId) {
|
|
26
|
+
throw new Error("AiTenantModelAllowlistRepository.upsert requires tenantId");
|
|
27
|
+
}
|
|
28
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
29
|
+
const providers = input.allowedProviders === void 0 ? null : input.allowedProviders;
|
|
30
|
+
const models = input.allowedModelsByProvider ?? {};
|
|
31
|
+
return this.em.transactional(async (tx) => {
|
|
32
|
+
const existing = await tx.findOne(AiTenantModelAllowlist, {
|
|
33
|
+
tenantId: ctx.tenantId,
|
|
34
|
+
organizationId: orgFilter,
|
|
35
|
+
deletedAt: null
|
|
36
|
+
});
|
|
37
|
+
if (existing) {
|
|
38
|
+
existing.allowedProviders = providers;
|
|
39
|
+
existing.allowedModelsByProvider = models;
|
|
40
|
+
existing.updatedByUserId = ctx.userId ?? null;
|
|
41
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
42
|
+
await tx.persist(existing).flush();
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
const row = tx.create(AiTenantModelAllowlist, {
|
|
46
|
+
tenantId: ctx.tenantId,
|
|
47
|
+
organizationId: orgFilter,
|
|
48
|
+
allowedProviders: providers,
|
|
49
|
+
allowedModelsByProvider: models,
|
|
50
|
+
updatedByUserId: ctx.userId ?? null
|
|
51
|
+
});
|
|
52
|
+
await tx.persist(row).flush();
|
|
53
|
+
return row;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async clear(ctx) {
|
|
57
|
+
if (!ctx?.tenantId) return false;
|
|
58
|
+
const orgFilter = ctx.organizationId ?? null;
|
|
59
|
+
return this.em.transactional(async (tx) => {
|
|
60
|
+
const existing = await tx.findOne(AiTenantModelAllowlist, {
|
|
61
|
+
tenantId: ctx.tenantId,
|
|
62
|
+
organizationId: orgFilter,
|
|
63
|
+
deletedAt: null
|
|
64
|
+
});
|
|
65
|
+
if (!existing) return false;
|
|
66
|
+
existing.deletedAt = /* @__PURE__ */ new Date();
|
|
67
|
+
await tx.persist(existing).flush();
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
var AiTenantModelAllowlistRepository_default = AiTenantModelAllowlistRepository;
|
|
73
|
+
export {
|
|
74
|
+
AiTenantModelAllowlistRepository,
|
|
75
|
+
AiTenantModelAllowlistRepository_default as default
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=AiTenantModelAllowlistRepository.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { AiTenantModelAllowlist } from '../entities'\n\nexport interface AiTenantModelAllowlistContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiTenantModelAllowlistInput {\n /**\n * Tenant-permitted provider ids. `null` means \"inherit env\" (no tenant\n * restriction beyond what `OM_AI_AVAILABLE_PROVIDERS` imposes). An empty\n * array means \"no providers permitted\" \u2014 the runtime will fall through to\n * env-default behaviour but the settings UI will surface zero options.\n */\n allowedProviders?: string[] | null\n /**\n * Per-provider allowed model lists. A missing key means \"inherit env\" for\n * that provider; an empty array means \"no models permitted for this\n * provider\" (effectively disabling it for tenant-side picks).\n */\n allowedModelsByProvider?: Record<string, string[]>\n}\n\nexport interface AiTenantModelAllowlistSnapshot {\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\n/**\n * Repository for the per-tenant provider/model allowlist (Phase 1780-6 of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * Reads always filter by `tenant_id`. The tenant allowlist clips runtime\n * choices within the env-driven outer constraint \u2014 the route layer is\n * responsible for rejecting writes that escape `OM_AI_AVAILABLE_*`. This\n * repository does not consult the env directly; it stores whatever the route\n * has already validated.\n */\nexport class AiTenantModelAllowlistRepository {\n constructor(private readonly em: EntityManager) {}\n\n async getForTenant(ctx: {\n tenantId: string\n organizationId?: string | null\n }): Promise<AiTenantModelAllowlist | null> {\n if (!ctx?.tenantId) return null\n const orgFilter = ctx.organizationId ?? null\n const row = await this.em.findOne(AiTenantModelAllowlist, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiTenantModelAllowlist>)\n return row ?? null\n }\n\n async getSnapshot(ctx: {\n tenantId: string\n organizationId?: string | null\n }): Promise<AiTenantModelAllowlistSnapshot | null> {\n const row = await this.getForTenant(ctx)\n if (!row) return null\n return {\n allowedProviders: row.allowedProviders ?? null,\n allowedModelsByProvider: row.allowedModelsByProvider ?? {},\n }\n }\n\n async upsert(\n input: AiTenantModelAllowlistInput,\n ctx: AiTenantModelAllowlistContext,\n ): Promise<AiTenantModelAllowlist> {\n if (!ctx?.tenantId) {\n throw new Error('AiTenantModelAllowlistRepository.upsert requires tenantId')\n }\n\n const orgFilter = ctx.organizationId ?? null\n const providers = input.allowedProviders === undefined\n ? null\n : input.allowedProviders\n const models = input.allowedModelsByProvider ?? {}\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiTenantModelAllowlist, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiTenantModelAllowlist>)\n\n if (existing) {\n existing.allowedProviders = providers\n existing.allowedModelsByProvider = models\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiTenantModelAllowlist, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n allowedProviders: providers,\n allowedModelsByProvider: models,\n updatedByUserId: ctx.userId ?? null,\n } as unknown as AiTenantModelAllowlist)\n await tx.persist(row).flush()\n return row\n })\n }\n\n async clear(ctx: {\n tenantId: string\n organizationId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n const orgFilter = ctx.organizationId ?? null\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiTenantModelAllowlist, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiTenantModelAllowlist>)\n if (!existing) return false\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\nexport default AiTenantModelAllowlistRepository\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,8BAA8B;AAuChC,MAAM,iCAAiC;AAAA,EAC5C,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAEjD,MAAM,aAAa,KAGwB;AACzC,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,UAAM,YAAY,IAAI,kBAAkB;AACxC,UAAM,MAAM,MAAM,KAAK,GAAG,QAAQ,wBAAwB;AAAA,MACxD,UAAU,IAAI;AAAA,MACd,gBAAgB;AAAA,MAChB,WAAW;AAAA,IACb,CAA+C;AAC/C,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,KAGiC;AACjD,UAAM,MAAM,MAAM,KAAK,aAAa,GAAG;AACvC,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO;AAAA,MACL,kBAAkB,IAAI,oBAAoB;AAAA,MAC1C,yBAAyB,IAAI,2BAA2B,CAAC;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,KACiC;AACjC,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,2DAA2D;AAAA,IAC7E;AAEA,UAAM,YAAY,IAAI,kBAAkB;AACxC,UAAM,YAAY,MAAM,qBAAqB,SACzC,OACA,MAAM;AACV,UAAM,SAAS,MAAM,2BAA2B,CAAC;AAEjD,WAAO,KAAK,GAAG,cAAc,OAAO,OAAO;AACzC,YAAM,WAAW,MAAM,GAAG,QAAQ,wBAAwB;AAAA,QACxD,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,WAAW;AAAA,MACb,CAA+C;AAE/C,UAAI,UAAU;AACZ,iBAAS,mBAAmB;AAC5B,iBAAS,0BAA0B;AACnC,iBAAS,kBAAkB,IAAI,UAAU;AACzC,iBAAS,YAAY,oBAAI,KAAK;AAC9B,cAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AACjC,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,GAAG,OAAO,wBAAwB;AAAA,QAC5C,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,yBAAyB;AAAA,QACzB,iBAAiB,IAAI,UAAU;AAAA,MACjC,CAAsC;AACtC,YAAM,GAAG,QAAQ,GAAG,EAAE,MAAM;AAC5B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,KAGS;AACnB,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,UAAM,YAAY,IAAI,kBAAkB;AACxC,WAAO,KAAK,GAAG,cAAc,OAAO,OAAO;AACzC,YAAM,WAAW,MAAM,GAAG,QAAQ,wBAAwB;AAAA,QACxD,UAAU,IAAI;AAAA,QACd,gBAAgB;AAAA,QAChB,WAAW;AAAA,MACb,CAA+C;AAC/C,UAAI,CAAC,SAAU,QAAO;AACtB,eAAS,YAAY,oBAAI,KAAK;AAC9B,YAAM,GAAG,QAAQ,QAAQ,EAAE,MAAM;AACjC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAEA,IAAO,2CAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -131,7 +131,7 @@ function AiAssistantSettingsContent() {
|
|
|
131
131
|
] }),
|
|
132
132
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-3", children: [
|
|
133
133
|
"Set ",
|
|
134
|
-
/* @__PURE__ */ jsx("code", { className: "font-mono text-overline bg-muted px-1 rounded", children: "
|
|
134
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-overline bg-muted px-1 rounded", children: "OM_AI_PROVIDER" }),
|
|
135
135
|
" in .env to change provider (anthropic, openai, google)."
|
|
136
136
|
] })
|
|
137
137
|
] }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useState } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { Bot, Loader2, CheckCircle2, XCircle, ChevronDown, ChevronRight, Server, Wrench, AlertTriangle } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n CommandPaletteProvider,\n CommandPalette,\n useCommandPaletteContext,\n} from '../../../../frontend'\n\n// OpenCode health response type\ntype OpenCodeHealthResponse = {\n status: 'ok' | 'error'\n opencode?: {\n healthy: boolean\n version: string\n }\n mcp?: Record<string, { status: string; error?: string }>\n search?: {\n available: boolean\n driver: string | null\n }\n url: string\n message?: string\n}\n\n// Provider config type from settings API\ntype ProviderConfig = {\n id: string\n name: string\n model: string\n defaultModel: string\n envKey: string\n configured: boolean\n}\n\ntype SettingsResponse = {\n provider: ProviderConfig\n availableProviders: ProviderConfig[]\n}\n\n// Tool info type\ntype ToolInfo = {\n name: string\n description: string\n module: string\n inputSchema: Record<string, unknown>\n}\n\n// API fetch functions\nasync function fetchHealth(): Promise<OpenCodeHealthResponse> {\n const res = await fetch('/api/ai_assistant/health')\n if (!res.ok) throw new Error('Failed to fetch health')\n return res.json()\n}\n\nasync function fetchSettings(): Promise<SettingsResponse> {\n const res = await fetch('/api/ai_assistant/settings')\n if (!res.ok) throw new Error('Failed to fetch settings')\n return res.json()\n}\n\nasync function fetchTools(): Promise<{ tools: ToolInfo[] }> {\n const res = await fetch('/api/ai_assistant/tools')\n if (!res.ok) throw new Error('Failed to fetch tools')\n return res.json()\n}\n\nfunction AiAssistantSettingsContent() {\n const [toolsExpanded, setToolsExpanded] = useState(false)\n const { setIsOpen } = useCommandPaletteContext()\n\n // Health query - polls every 10 seconds\n const healthQuery = useQuery({\n queryKey: ['ai-assistant', 'health'],\n queryFn: fetchHealth,\n refetchInterval: 10000,\n staleTime: 5000,\n })\n\n // Settings query - no polling needed (static config)\n const settingsQuery = useQuery({\n queryKey: ['ai-assistant', 'settings'],\n queryFn: fetchSettings,\n staleTime: 60000,\n })\n\n // Tools query - no polling needed\n const toolsQuery = useQuery({\n queryKey: ['ai-assistant', 'tools'],\n queryFn: fetchTools,\n staleTime: 60000,\n })\n\n // Open AI Assistant palette\n const openAiAssistant = () => {\n setIsOpen(true)\n }\n\n const isLoading = healthQuery.isLoading || settingsQuery.isLoading || toolsQuery.isLoading\n\n if (isLoading) {\n return (\n <div className=\"flex items-center gap-2 text-muted-foreground py-8\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Loading settings...\n </div>\n )\n }\n\n const health = healthQuery.data\n const settings = settingsQuery.data\n const tools = toolsQuery.data?.tools || []\n\n // Group tools by module\n const toolsByModule = tools.reduce<Record<string, ToolInfo[]>>((acc, tool) => {\n const module = tool.module || 'other'\n if (!acc[module]) acc[module] = []\n acc[module].push(tool)\n return acc\n }, {})\n\n const provider = settings?.provider\n\n return (\n <div className=\"flex flex-col gap-6\">\n {/* Header */}\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-bold flex items-center gap-2\">\n <Bot className=\"h-6 w-6\" />\n AI Assistant Settings\n </h1>\n <p className=\"text-muted-foreground\">\n Configure and monitor the AI assistant\n </p>\n </div>\n\n {/* Test AI Assistant Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-start justify-between\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold flex items-center gap-2\">\n <Bot className=\"h-5 w-5\" />\n Test AI Assistant\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n Click the button to open the AI Assistant command palette.\n </p>\n </div>\n <Button onClick={openAiAssistant} size=\"lg\" className=\"gap-2\">\n <Bot className=\"h-4 w-4\" />\n Open AI Assistant\n </Button>\n </div>\n </div>\n\n {/* Configuration Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-sm font-semibold mb-4 flex items-center gap-2\">\n <Server className=\"h-4 w-4\" />\n Configuration\n </h2>\n <div className=\"bg-muted/50 rounded-md p-4 space-y-2\">\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Provider:</span>\n <span className=\"font-medium\">{provider?.name || 'Anthropic'}</span>\n {provider?.configured ? (\n <span className=\"flex items-center gap-1 text-status-success-text text-xs\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Configured\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-status-warning-text text-xs\">\n <XCircle className=\"h-3 w-3\" />\n Not configured\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Model:</span>\n <code className=\"font-mono text-xs bg-background px-1.5 py-0.5 rounded\">{provider?.model || 'claude-haiku-4-5-20251001'}</code>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Required:</span>\n <span>Set <code className=\"font-mono text-xs bg-background px-1.5 py-0.5 rounded\">{provider?.envKey || 'ANTHROPIC_API_KEY'}</code> in .env</span>\n </div>\n </div>\n\n {/* Available Providers */}\n {settings?.availableProviders && settings.availableProviders.length > 1 && (\n <div className=\"mt-4\">\n <p className=\"text-xs text-muted-foreground mb-2\">Available Providers:</p>\n <div className=\"flex flex-wrap gap-2\">\n {settings.availableProviders.map((p) => (\n <div\n key={p.id}\n className={`px-2 py-1 rounded text-xs ${\n p.id === provider?.id\n ? 'bg-primary text-primary-foreground'\n : p.configured\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-muted text-muted-foreground'\n }`}\n >\n {p.name}\n {p.id === provider?.id && ' (active)'}\n {p.id !== provider?.id && p.configured && ' (ready)'}\n </div>\n ))}\n </div>\n </div>\n )}\n\n <p className=\"text-xs text-muted-foreground mt-3\">\n Set <code className=\"font-mono text-overline bg-muted px-1 rounded\">OPENCODE_PROVIDER</code> in .env to change provider (anthropic, openai, google).\n </p>\n </div>\n\n {/* Requirements Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-sm font-semibold mb-4 flex items-center gap-2\">\n <AlertTriangle className=\"h-4 w-4\" />\n Requirements\n </h2>\n <div className=\"bg-muted/50 rounded-md p-4 space-y-3\">\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Full-Text Search:</span>\n {health?.search?.available ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Meilisearch connected\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-status-warning-text\">\n <XCircle className=\"h-3 w-3\" />\n Not available\n </span>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n A full-text search driver (Meilisearch) is required for API endpoint discovery.\n Endpoints are indexed automatically when the MCP server starts.\n </p>\n </div>\n </div>\n\n {/* OpenCode Connection Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-sm font-semibold flex items-center gap-2\">\n <Server className=\"h-4 w-4\" />\n OpenCode Connection\n </h2>\n {healthQuery.isFetching && !healthQuery.isLoading && (\n <Loader2 className=\"h-3 w-3 animate-spin text-muted-foreground\" />\n )}\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {/* OpenCode Server Status */}\n <div className={`p-4 rounded-lg border-2 ${\n health?.status === 'ok' && health.opencode?.healthy\n ? 'border-status-success-border bg-status-success-bg'\n : 'border-destructive/50 bg-destructive/5'\n }`}>\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"text-sm font-medium\">OpenCode Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {health?.status === 'ok' && health.opencode?.healthy ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Connected\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-destructive\">\n <XCircle className=\"h-3 w-3\" />\n {health?.message || 'Disconnected'}\n </span>\n )}\n </p>\n {health?.opencode?.version && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Version: {health.opencode.version}\n </p>\n )}\n <p className=\"text-xs text-muted-foreground mt-1\">\n {health?.url || 'http://localhost:4096'}\n </p>\n </div>\n {health?.status === 'ok' && health.opencode?.healthy && (\n <div className=\"flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon\">\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n )}\n </div>\n </div>\n\n {/* MCP Server Status */}\n {health?.mcp && Object.entries(health.mcp).map(([name, mcpStatus]) => (\n <div\n key={name}\n className={`p-4 rounded-lg border-2 ${\n mcpStatus.status === 'connected'\n ? 'border-status-success-border bg-status-success-bg'\n : mcpStatus.status === 'connecting'\n ? 'border-status-warning-border bg-status-warning-bg'\n : 'border-destructive/50 bg-destructive/5'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"text-sm font-medium\">MCP Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {mcpStatus.status === 'connected' ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Connected\n </span>\n ) : mcpStatus.status === 'connecting' ? (\n <span className=\"flex items-center gap-1 text-status-warning-text\">\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n Connecting...\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-destructive\">\n <XCircle className=\"h-3 w-3\" />\n {mcpStatus.error || 'Failed'}\n </span>\n )}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">{name}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">localhost:3001</p>\n </div>\n {mcpStatus.status === 'connected' && (\n <div className=\"flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon\">\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n )}\n </div>\n </div>\n ))}\n\n {/* Show placeholder if no MCP info */}\n {(!health?.mcp || Object.keys(health.mcp).length === 0) && (\n <div className=\"p-4 rounded-lg border-2 border-border bg-muted/30\">\n <div>\n <p className=\"text-sm font-medium\">MCP Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n <span className=\"flex items-center gap-1\">\n <XCircle className=\"h-3 w-3\" />\n Not connected\n </span>\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">localhost:3001</p>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* MCP Tools Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <button\n onClick={() => setToolsExpanded(!toolsExpanded)}\n className=\"w-full flex items-center justify-between text-left\"\n >\n <h2 className=\"text-sm font-semibold flex items-center gap-2\">\n <Wrench className=\"h-4 w-4\" />\n MCP Tools ({tools.length} tools)\n </h2>\n {toolsExpanded ? (\n <ChevronDown className=\"h-4 w-4 text-muted-foreground\" />\n ) : (\n <ChevronRight className=\"h-4 w-4 text-muted-foreground\" />\n )}\n </button>\n\n {toolsExpanded && (\n <div className=\"mt-4 space-y-4\">\n {Object.entries(toolsByModule).map(([module, moduleTools]) => (\n <div key={module} className=\"space-y-2\">\n <h3 className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {module}\n </h3>\n <div className=\"space-y-1\">\n {moduleTools.map((tool) => (\n <div key={tool.name} className=\"pl-2 border-l-2 border-muted py-1\">\n <p className=\"text-sm font-medium\">{tool.name}</p>\n <p className=\"text-xs text-muted-foreground\">{tool.description}</p>\n </div>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport function AiAssistantSettingsPageClient() {\n return (\n <CommandPaletteProvider tenantId=\"\" organizationId={null}>\n <AiAssistantSettingsContent />\n <CommandPalette />\n </CommandPaletteProvider>\n )\n}\n\nexport default AiAssistantSettingsPageClient\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useState } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { Bot, Loader2, CheckCircle2, XCircle, ChevronDown, ChevronRight, Server, Wrench, AlertTriangle } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n CommandPaletteProvider,\n CommandPalette,\n useCommandPaletteContext,\n} from '../../../../frontend'\n\n// OpenCode health response type\ntype OpenCodeHealthResponse = {\n status: 'ok' | 'error'\n opencode?: {\n healthy: boolean\n version: string\n }\n mcp?: Record<string, { status: string; error?: string }>\n search?: {\n available: boolean\n driver: string | null\n }\n url: string\n message?: string\n}\n\n// Provider config type from settings API\ntype ProviderConfig = {\n id: string\n name: string\n model: string\n defaultModel: string\n envKey: string\n configured: boolean\n}\n\ntype SettingsResponse = {\n provider: ProviderConfig\n availableProviders: ProviderConfig[]\n}\n\n// Tool info type\ntype ToolInfo = {\n name: string\n description: string\n module: string\n inputSchema: Record<string, unknown>\n}\n\n// API fetch functions\nasync function fetchHealth(): Promise<OpenCodeHealthResponse> {\n const res = await fetch('/api/ai_assistant/health')\n if (!res.ok) throw new Error('Failed to fetch health')\n return res.json()\n}\n\nasync function fetchSettings(): Promise<SettingsResponse> {\n const res = await fetch('/api/ai_assistant/settings')\n if (!res.ok) throw new Error('Failed to fetch settings')\n return res.json()\n}\n\nasync function fetchTools(): Promise<{ tools: ToolInfo[] }> {\n const res = await fetch('/api/ai_assistant/tools')\n if (!res.ok) throw new Error('Failed to fetch tools')\n return res.json()\n}\n\nfunction AiAssistantSettingsContent() {\n const [toolsExpanded, setToolsExpanded] = useState(false)\n const { setIsOpen } = useCommandPaletteContext()\n\n // Health query - polls every 10 seconds\n const healthQuery = useQuery({\n queryKey: ['ai-assistant', 'health'],\n queryFn: fetchHealth,\n refetchInterval: 10000,\n staleTime: 5000,\n })\n\n // Settings query - no polling needed (static config)\n const settingsQuery = useQuery({\n queryKey: ['ai-assistant', 'settings'],\n queryFn: fetchSettings,\n staleTime: 60000,\n })\n\n // Tools query - no polling needed\n const toolsQuery = useQuery({\n queryKey: ['ai-assistant', 'tools'],\n queryFn: fetchTools,\n staleTime: 60000,\n })\n\n // Open AI Assistant palette\n const openAiAssistant = () => {\n setIsOpen(true)\n }\n\n const isLoading = healthQuery.isLoading || settingsQuery.isLoading || toolsQuery.isLoading\n\n if (isLoading) {\n return (\n <div className=\"flex items-center gap-2 text-muted-foreground py-8\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Loading settings...\n </div>\n )\n }\n\n const health = healthQuery.data\n const settings = settingsQuery.data\n const tools = toolsQuery.data?.tools || []\n\n // Group tools by module\n const toolsByModule = tools.reduce<Record<string, ToolInfo[]>>((acc, tool) => {\n const module = tool.module || 'other'\n if (!acc[module]) acc[module] = []\n acc[module].push(tool)\n return acc\n }, {})\n\n const provider = settings?.provider\n\n return (\n <div className=\"flex flex-col gap-6\">\n {/* Header */}\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-bold flex items-center gap-2\">\n <Bot className=\"h-6 w-6\" />\n AI Assistant Settings\n </h1>\n <p className=\"text-muted-foreground\">\n Configure and monitor the AI assistant\n </p>\n </div>\n\n {/* Test AI Assistant Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-start justify-between\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold flex items-center gap-2\">\n <Bot className=\"h-5 w-5\" />\n Test AI Assistant\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n Click the button to open the AI Assistant command palette.\n </p>\n </div>\n <Button onClick={openAiAssistant} size=\"lg\" className=\"gap-2\">\n <Bot className=\"h-4 w-4\" />\n Open AI Assistant\n </Button>\n </div>\n </div>\n\n {/* Configuration Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-sm font-semibold mb-4 flex items-center gap-2\">\n <Server className=\"h-4 w-4\" />\n Configuration\n </h2>\n <div className=\"bg-muted/50 rounded-md p-4 space-y-2\">\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Provider:</span>\n <span className=\"font-medium\">{provider?.name || 'Anthropic'}</span>\n {provider?.configured ? (\n <span className=\"flex items-center gap-1 text-status-success-text text-xs\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Configured\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-status-warning-text text-xs\">\n <XCircle className=\"h-3 w-3\" />\n Not configured\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Model:</span>\n <code className=\"font-mono text-xs bg-background px-1.5 py-0.5 rounded\">{provider?.model || 'claude-haiku-4-5-20251001'}</code>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Required:</span>\n <span>Set <code className=\"font-mono text-xs bg-background px-1.5 py-0.5 rounded\">{provider?.envKey || 'ANTHROPIC_API_KEY'}</code> in .env</span>\n </div>\n </div>\n\n {/* Available Providers */}\n {settings?.availableProviders && settings.availableProviders.length > 1 && (\n <div className=\"mt-4\">\n <p className=\"text-xs text-muted-foreground mb-2\">Available Providers:</p>\n <div className=\"flex flex-wrap gap-2\">\n {settings.availableProviders.map((p) => (\n <div\n key={p.id}\n className={`px-2 py-1 rounded text-xs ${\n p.id === provider?.id\n ? 'bg-primary text-primary-foreground'\n : p.configured\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-muted text-muted-foreground'\n }`}\n >\n {p.name}\n {p.id === provider?.id && ' (active)'}\n {p.id !== provider?.id && p.configured && ' (ready)'}\n </div>\n ))}\n </div>\n </div>\n )}\n\n <p className=\"text-xs text-muted-foreground mt-3\">\n Set <code className=\"font-mono text-overline bg-muted px-1 rounded\">OM_AI_PROVIDER</code> in .env to change provider (anthropic, openai, google).\n </p>\n </div>\n\n {/* Requirements Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-sm font-semibold mb-4 flex items-center gap-2\">\n <AlertTriangle className=\"h-4 w-4\" />\n Requirements\n </h2>\n <div className=\"bg-muted/50 rounded-md p-4 space-y-3\">\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">Full-Text Search:</span>\n {health?.search?.available ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Meilisearch connected\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-status-warning-text\">\n <XCircle className=\"h-3 w-3\" />\n Not available\n </span>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n A full-text search driver (Meilisearch) is required for API endpoint discovery.\n Endpoints are indexed automatically when the MCP server starts.\n </p>\n </div>\n </div>\n\n {/* OpenCode Connection Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-sm font-semibold flex items-center gap-2\">\n <Server className=\"h-4 w-4\" />\n OpenCode Connection\n </h2>\n {healthQuery.isFetching && !healthQuery.isLoading && (\n <Loader2 className=\"h-3 w-3 animate-spin text-muted-foreground\" />\n )}\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {/* OpenCode Server Status */}\n <div className={`p-4 rounded-lg border-2 ${\n health?.status === 'ok' && health.opencode?.healthy\n ? 'border-status-success-border bg-status-success-bg'\n : 'border-destructive/50 bg-destructive/5'\n }`}>\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"text-sm font-medium\">OpenCode Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {health?.status === 'ok' && health.opencode?.healthy ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Connected\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-destructive\">\n <XCircle className=\"h-3 w-3\" />\n {health?.message || 'Disconnected'}\n </span>\n )}\n </p>\n {health?.opencode?.version && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Version: {health.opencode.version}\n </p>\n )}\n <p className=\"text-xs text-muted-foreground mt-1\">\n {health?.url || 'http://localhost:4096'}\n </p>\n </div>\n {health?.status === 'ok' && health.opencode?.healthy && (\n <div className=\"flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon\">\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n )}\n </div>\n </div>\n\n {/* MCP Server Status */}\n {health?.mcp && Object.entries(health.mcp).map(([name, mcpStatus]) => (\n <div\n key={name}\n className={`p-4 rounded-lg border-2 ${\n mcpStatus.status === 'connected'\n ? 'border-status-success-border bg-status-success-bg'\n : mcpStatus.status === 'connecting'\n ? 'border-status-warning-border bg-status-warning-bg'\n : 'border-destructive/50 bg-destructive/5'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <p className=\"text-sm font-medium\">MCP Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {mcpStatus.status === 'connected' ? (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Connected\n </span>\n ) : mcpStatus.status === 'connecting' ? (\n <span className=\"flex items-center gap-1 text-status-warning-text\">\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n Connecting...\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-destructive\">\n <XCircle className=\"h-3 w-3\" />\n {mcpStatus.error || 'Failed'}\n </span>\n )}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">{name}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">localhost:3001</p>\n </div>\n {mcpStatus.status === 'connected' && (\n <div className=\"flex h-5 w-5 items-center justify-center rounded-full bg-status-success-bg text-status-success-icon\">\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n )}\n </div>\n </div>\n ))}\n\n {/* Show placeholder if no MCP info */}\n {(!health?.mcp || Object.keys(health.mcp).length === 0) && (\n <div className=\"p-4 rounded-lg border-2 border-border bg-muted/30\">\n <div>\n <p className=\"text-sm font-medium\">MCP Server</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n <span className=\"flex items-center gap-1\">\n <XCircle className=\"h-3 w-3\" />\n Not connected\n </span>\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">localhost:3001</p>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* MCP Tools Section */}\n <div className=\"rounded-lg border bg-card p-6\">\n <button\n onClick={() => setToolsExpanded(!toolsExpanded)}\n className=\"w-full flex items-center justify-between text-left\"\n >\n <h2 className=\"text-sm font-semibold flex items-center gap-2\">\n <Wrench className=\"h-4 w-4\" />\n MCP Tools ({tools.length} tools)\n </h2>\n {toolsExpanded ? (\n <ChevronDown className=\"h-4 w-4 text-muted-foreground\" />\n ) : (\n <ChevronRight className=\"h-4 w-4 text-muted-foreground\" />\n )}\n </button>\n\n {toolsExpanded && (\n <div className=\"mt-4 space-y-4\">\n {Object.entries(toolsByModule).map(([module, moduleTools]) => (\n <div key={module} className=\"space-y-2\">\n <h3 className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {module}\n </h3>\n <div className=\"space-y-1\">\n {moduleTools.map((tool) => (\n <div key={tool.name} className=\"pl-2 border-l-2 border-muted py-1\">\n <p className=\"text-sm font-medium\">{tool.name}</p>\n <p className=\"text-xs text-muted-foreground\">{tool.description}</p>\n </div>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport function AiAssistantSettingsPageClient() {\n return (\n <CommandPaletteProvider tenantId=\"\" organizationId={null}>\n <AiAssistantSettingsContent />\n <CommandPalette />\n </CommandPaletteProvider>\n )\n}\n\nexport default AiAssistantSettingsPageClient\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -87,6 +87,31 @@
|
|
|
87
87
|
"ai_assistant.agents.tools.subtitle": "In Phase 2 schreibgeschützte Oberfläche. Bearbeitung pro Tool und Mutationsrichtlinie folgen in Schritt 5.4 / Phase 3.",
|
|
88
88
|
"ai_assistant.agents.tools.title": "Erlaubte Tools",
|
|
89
89
|
"ai_assistant.agents.tools.tooltipDisabled": "Bearbeitbar, sobald Phase 3 die Mutationsrichtlinien-Steuerung liefert.",
|
|
90
|
+
"ai_assistant.allowlist.actions.clearStored": "Gespeicherte Liste löschen",
|
|
91
|
+
"ai_assistant.allowlist.actions.reset": "Auf ENV-Standard zurücksetzen",
|
|
92
|
+
"ai_assistant.allowlist.actions.save": "Liste speichern",
|
|
93
|
+
"ai_assistant.allowlist.badge.active": "Mandantenregeln aktiv",
|
|
94
|
+
"ai_assistant.allowlist.badge.envOnly": "Nur ENV",
|
|
95
|
+
"ai_assistant.allowlist.clear.error": "Löschen fehlgeschlagen",
|
|
96
|
+
"ai_assistant.allowlist.clear.success": "Mandantenliste gelöscht. Es gilt nur die ENV-Erzwingung.",
|
|
97
|
+
"ai_assistant.allowlist.envBanner.note": "Mandantenauswahl darf die ENV-Liste nicht erweitern — Werte außerhalb sind ausgeblendet.",
|
|
98
|
+
"ai_assistant.allowlist.envBanner.providers": "OM_AI_AVAILABLE_PROVIDERS",
|
|
99
|
+
"ai_assistant.allowlist.envBanner.title": "ENV-Liste ist aktiv",
|
|
100
|
+
"ai_assistant.allowlist.loadError.body": "Bitte Seite neu laden.",
|
|
101
|
+
"ai_assistant.allowlist.loadError.title": "Liste konnte nicht geladen werden",
|
|
102
|
+
"ai_assistant.allowlist.loading": "Lade Liste …",
|
|
103
|
+
"ai_assistant.allowlist.models.default": "Standard",
|
|
104
|
+
"ai_assistant.allowlist.models.help": "Modelle ankreuzen, die Mandanten wählen dürfen. Leer = keine Modellbeschränkung (ENV erben).",
|
|
105
|
+
"ai_assistant.allowlist.navTitle": "AI Allowlist",
|
|
106
|
+
"ai_assistant.allowlist.providers.configured": "konfiguriert",
|
|
107
|
+
"ai_assistant.allowlist.providers.empty": "Keine konfigurierten Anbieter innerhalb der ENV-Liste.",
|
|
108
|
+
"ai_assistant.allowlist.providers.help": "Abwählen, um die Verwendung eines Anbieters für diesen Mandanten zu verbieten. Alle wählen, um ENV zu erben.",
|
|
109
|
+
"ai_assistant.allowlist.providers.notConfigured": "nicht konfiguriert",
|
|
110
|
+
"ai_assistant.allowlist.providers.title": "Anbieter",
|
|
111
|
+
"ai_assistant.allowlist.save.error": "Speichern fehlgeschlagen",
|
|
112
|
+
"ai_assistant.allowlist.save.success": "Liste gespeichert.",
|
|
113
|
+
"ai_assistant.allowlist.subtitle": "Schränke Anbieter und Modelle ein, die für diesen Mandanten verwendet werden dürfen. Die ENV-Liste ist die äußere Beschränkung — die Mandantenauswahl engt sie weiter ein.",
|
|
114
|
+
"ai_assistant.allowlist.title": "Anbieter- & Modell-Allowlist",
|
|
90
115
|
"ai_assistant.chat.assistantRoleLabel": "Assistent",
|
|
91
116
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
92
117
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -220,7 +245,7 @@
|
|
|
220
245
|
"ai_assistant.launcher.hint.launch": "Launch",
|
|
221
246
|
"ai_assistant.launcher.hint.navigate": "Navigate",
|
|
222
247
|
"ai_assistant.launcher.searchPlaceholder": "Search assistants...",
|
|
223
|
-
"ai_assistant.launcher.setup.body": "KI-Assistenten sind installiert, aber es ist kein Anbieterschlüssel konfiguriert. Lege
|
|
248
|
+
"ai_assistant.launcher.setup.body": "KI-Assistenten sind installiert, aber es ist kein Anbieterschlüssel konfiguriert. Lege OM_AI_PROVIDER und einen passenden API-Schlüssel in deiner .env-Datei fest und starte die App neu.",
|
|
224
249
|
"ai_assistant.launcher.setup.docs": "Dokumentation zum KI-Assistenten",
|
|
225
250
|
"ai_assistant.launcher.setup.settingsDocs": "Anbietereinstellungen",
|
|
226
251
|
"ai_assistant.launcher.setup.title": "Konfiguriere einen KI-Anbieter, um Assistenten zu verwenden",
|
|
@@ -244,6 +269,12 @@
|
|
|
244
269
|
"ai_assistant.mcp.notGenerated": "Nicht generiert",
|
|
245
270
|
"ai_assistant.mcp.saveKeyWarning": "Speichern Sie diesen Schlüssel jetzt — er wird nicht erneut angezeigt!",
|
|
246
271
|
"ai_assistant.mcp.title": "MCP-Konfiguration",
|
|
272
|
+
"ai_assistant.modelPicker.activeBadge": "aktiv",
|
|
273
|
+
"ai_assistant.modelPicker.defaultBadge": "Standard",
|
|
274
|
+
"ai_assistant.modelPicker.defaultLabel": "Modell: Standard",
|
|
275
|
+
"ai_assistant.modelPicker.listAriaLabel": "Verfügbare Modelle",
|
|
276
|
+
"ai_assistant.modelPicker.triggerAriaLabel": "KI-Modell auswählen",
|
|
277
|
+
"ai_assistant.modelPicker.useDefault": "Standard des Agenten verwenden",
|
|
247
278
|
"ai_assistant.playground.agentPickerLabel": "Agent",
|
|
248
279
|
"ai_assistant.playground.chat.notSupportedBody": "Wählen Sie einen Agenten, dessen Ausführungsmodus \"chat\" ist, oder wechseln Sie zum Objekt-Modus-Tab.",
|
|
249
280
|
"ai_assistant.playground.chat.notSupportedTitle": "Chat-Modus ist für diesen Agenten nicht verfügbar.",
|
|
@@ -271,6 +302,11 @@
|
|
|
271
302
|
"ai_assistant.playground.object.run": "Objekt ausführen",
|
|
272
303
|
"ai_assistant.playground.object.shortcutHint": "Drücken Sie Cmd/Ctrl+Enter zum Ausführen.",
|
|
273
304
|
"ai_assistant.playground.refresh": "Agenten aktualisieren",
|
|
305
|
+
"ai_assistant.playground.resolution.baseUrl": "Basis-URL",
|
|
306
|
+
"ai_assistant.playground.resolution.model": "Modell",
|
|
307
|
+
"ai_assistant.playground.resolution.none": "—",
|
|
308
|
+
"ai_assistant.playground.resolution.provider": "Anbieter",
|
|
309
|
+
"ai_assistant.playground.resolution.source": "Quelle",
|
|
274
310
|
"ai_assistant.playground.retry": "Wiederholen",
|
|
275
311
|
"ai_assistant.playground.subtitle": "Testen Sie jeden registrierten KI-Agenten End-to-End. Verwenden Sie das Debug-Panel, um Request- und Response-Payloads zu inspizieren, und den Objekt-Modus-Tab, um strukturierte Ausgabe zu betrachten.",
|
|
276
312
|
"ai_assistant.playground.tabs.chat": "Chat",
|
|
@@ -289,6 +325,59 @@
|
|
|
289
325
|
"ai_assistant.session.llmInstructions": "LLM-Anweisungen",
|
|
290
326
|
"ai_assistant.session.title": "Sitzungs-API-Schlüssel",
|
|
291
327
|
"ai_assistant.session.tokenLabel": "Sitzungstoken",
|
|
328
|
+
"ai_assistant.settings.agentIdColumn": "Agent",
|
|
329
|
+
"ai_assistant.settings.agentOverridesDescription": "Aufgelöstes Modell für jeden registrierten Agenten. Agenten mit einer benutzerdefinierten Überschreibung zeigen eine Löschen-Schaltfläche.",
|
|
330
|
+
"ai_assistant.settings.agentOverridesTitle": "Modellauflösung pro Agent",
|
|
331
|
+
"ai_assistant.settings.clearAgentError": "Agenten-Überschreibung konnte nicht gelöscht werden.",
|
|
332
|
+
"ai_assistant.settings.clearAgentSuccess": "Agenten-Überschreibung gelöscht.",
|
|
333
|
+
"ai_assistant.settings.clearError": "Überschreibung konnte nicht gelöscht werden.",
|
|
334
|
+
"ai_assistant.settings.clearOverride": "Überschreibung löschen",
|
|
335
|
+
"ai_assistant.settings.clearSuccess": "Standard-Modell-Überschreibung gelöscht.",
|
|
336
|
+
"ai_assistant.settings.configured": "Konfiguriert",
|
|
337
|
+
"ai_assistant.settings.connected": "Verbunden",
|
|
338
|
+
"ai_assistant.settings.connecting": "Verbindet...",
|
|
339
|
+
"ai_assistant.settings.connectionsTitle": "Verbindungen",
|
|
340
|
+
"ai_assistant.settings.currentOverride": "Aktuelle Überschreibung:",
|
|
341
|
+
"ai_assistant.settings.defaultOverrideDescription": "Legen Sie einen mandantenweiten Standard-Anbieter und ein Modell fest. Agenten mit einer Überschreibung pro Agent oder einem bestimmten defaultModel haben Vorrang.",
|
|
342
|
+
"ai_assistant.settings.defaultOverrideTitle": "Standard-Modell-Überschreibung",
|
|
343
|
+
"ai_assistant.settings.developerToolsTitle": "Entwicklertools",
|
|
344
|
+
"ai_assistant.settings.disconnected": "Getrennt",
|
|
345
|
+
"ai_assistant.settings.envKeyConfigured": "{{key}} konfiguriert",
|
|
346
|
+
"ai_assistant.settings.envKeyMissing": "{{key}} nicht gesetzt",
|
|
347
|
+
"ai_assistant.settings.generateMcpConfig": "MCP-Konfiguration generieren",
|
|
348
|
+
"ai_assistant.settings.generateSessionKey": "Sitzungsschlüssel generieren",
|
|
349
|
+
"ai_assistant.settings.llmProviderLabel": "LLM-Anbieter:",
|
|
350
|
+
"ai_assistant.settings.loading": "Einstellungen werden geladen...",
|
|
351
|
+
"ai_assistant.settings.mcpAuthLabel": "MCP-Authentifizierung:",
|
|
352
|
+
"ai_assistant.settings.mcpAuthNote": "Erforderlich für den KI-Zugriff auf Plattform-Tools über den MCP-Server.",
|
|
353
|
+
"ai_assistant.settings.mcpConfigDescription": "Konfiguration für Claude Code oder andere MCP-Clients generieren.",
|
|
354
|
+
"ai_assistant.settings.mcpConfigTitle": "MCP-Konfiguration",
|
|
355
|
+
"ai_assistant.settings.mcpKeyConfigured": "MCP_SERVER_API_KEY konfiguriert",
|
|
356
|
+
"ai_assistant.settings.mcpKeyMissing": "MCP_SERVER_API_KEY nicht gesetzt",
|
|
357
|
+
"ai_assistant.settings.mcpToolsCount": "Tools",
|
|
358
|
+
"ai_assistant.settings.mcpToolsTitle": "MCP-Tools",
|
|
359
|
+
"ai_assistant.settings.meilisearchNote": "Meilisearch ist für die API-Endpunkt-Erkennung erforderlich. Endpunkte werden automatisch beim Start des MCP-Servers indiziert.",
|
|
360
|
+
"ai_assistant.settings.modelColumn": "Modell",
|
|
361
|
+
"ai_assistant.settings.modelLabel": "Modell",
|
|
362
|
+
"ai_assistant.settings.notAvailable": "Nicht verfügbar",
|
|
363
|
+
"ai_assistant.settings.notConfigured": "Nicht konfiguriert",
|
|
364
|
+
"ai_assistant.settings.openButton": "KI-Assistent öffnen",
|
|
365
|
+
"ai_assistant.settings.pageDescription": "KI-Assistent konfigurieren und überwachen",
|
|
366
|
+
"ai_assistant.settings.pageTitle": "KI-Assistent-Einstellungen",
|
|
367
|
+
"ai_assistant.settings.providerColumn": "Anbieter",
|
|
368
|
+
"ai_assistant.settings.providerLabel": "Anbieter",
|
|
369
|
+
"ai_assistant.settings.saveError": "Überschreibung konnte nicht gespeichert werden.",
|
|
370
|
+
"ai_assistant.settings.saveOverride": "Überschreibung speichern",
|
|
371
|
+
"ai_assistant.settings.saveSuccess": "Standard-Modell-Überschreibung gespeichert.",
|
|
372
|
+
"ai_assistant.settings.selectModel": "Modell auswählen",
|
|
373
|
+
"ai_assistant.settings.selectProvider": "Anbieter auswählen",
|
|
374
|
+
"ai_assistant.settings.sessionKeyDescription": "Temporären Token für programmatischen LLM-Zugriff generieren. Läuft nach 2 Stunden ab.",
|
|
375
|
+
"ai_assistant.settings.sessionKeyTitle": "Sitzungs-API-Schlüssel",
|
|
376
|
+
"ai_assistant.settings.sourceColumn": "Quelle",
|
|
377
|
+
"ai_assistant.settings.visibilityDisabled": "Im Header ausgeblendet. Aktivieren, um den Button und den Cmd+J-Shortcut anzuzeigen.",
|
|
378
|
+
"ai_assistant.settings.visibilityEnabled": "Im Header sichtbar mit aktiviertem Cmd+J-Shortcut.",
|
|
379
|
+
"ai_assistant.settings.visibilityTitle": "KI-Assistent",
|
|
380
|
+
"ai_assistant.settings.visibilityToggleLabel": "Sichtbarkeit",
|
|
292
381
|
"ai_assistant.status.analyzing": "Anfrage wird analysiert...",
|
|
293
382
|
"ai_assistant.status.executing": "Tools werden ausgeführt...",
|
|
294
383
|
"ai_assistant.status.responding": "Antwort wird erstellt...",
|