From 23c32409ed8dd41a9b9aa063b4e144f3064aa6ca Mon Sep 17 00:00:00 2001 From: sibarchannayak Date: Mon, 25 May 2026 18:02:58 +0530 Subject: [PATCH] feat: implement AI service and pages for managing tenant AI providers and configurations --- package-lock.json | 38 +++-- src/pages/superadmin/TenantDetails.tsx | 9 +- src/pages/tenant/TenantAIProviderCreate.tsx | 134 +++++++++++---- src/pages/tenant/TenantAIProviders.tsx | 176 ++++++++++++-------- src/services/ai-service.ts | 50 +++--- 5 files changed, 275 insertions(+), 132 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5a0eff..a9f3bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,6 +87,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -921,17 +922,6 @@ "@floating-ui/utils": "^0.2.11" } }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, "node_modules/@floating-ui/utils": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", @@ -1725,6 +1715,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.4.tgz", "integrity": "sha512-3i/DG89TFY/b34T5P+j35UcjYuB5d3+9K8u6qID+iUqNPiza015HPIZLuPfE5elNwVdV3EXIoPo0LLeBLgXXAg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1986,6 +1977,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.20.4.tgz", "integrity": "sha512-X+5plTKhOioNcQ4KsAFJJSb/3+zR8Xhdpow4HzXtoV1KcbdDey1fhZdpsfkbrzCL0s6/wAgwZuAchCK7HujurQ==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2104,6 +2096,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.20.4.tgz", "integrity": "sha512-PvW0Ja7ahWpo4bRuR8YCCVv4PH8lXjzhzlBAa4bMbsumOg+GbhX8Su7fwqd+IIPrHqfPXz9HTBMApSfzP6/08A==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2130,6 +2123,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.20.4.tgz", "integrity": "sha512-8p6hVT65DjuQjtEdlH6ewX9SOJHlVQAOee3sWIJQmeJNRnZNvqPIBLleebUqDiljNTpxBv6s6QWkSTKgf3btwg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -2144,6 +2138,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.4.tgz", "integrity": "sha512-rCHYSBToilBEuI6PtjziHDdRkABH/XqwJ7dG4Amn/SD3yGiZKYCiEApQlTUS2zZeo8DsLeuqqqB4vEOeD4OEPg==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -2381,6 +2376,7 @@ "integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2390,6 +2386,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2399,6 +2396,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2454,6 +2452,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2705,6 +2704,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2826,6 +2826,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3379,6 +3380,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4582,6 +4584,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4739,6 +4742,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", "license": "MIT", + "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -4768,6 +4772,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -4816,6 +4821,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -4852,6 +4858,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4861,6 +4868,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4873,6 +4881,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -4896,6 +4905,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -5006,7 +5016,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-persist": { "version": "6.0.0", @@ -5303,6 +5314,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5425,6 +5437,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -5580,6 +5593,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/pages/superadmin/TenantDetails.tsx b/src/pages/superadmin/TenantDetails.tsx index 9fbcc75..627796b 100644 --- a/src/pages/superadmin/TenantDetails.tsx +++ b/src/pages/superadmin/TenantDetails.tsx @@ -15,6 +15,7 @@ import { BadgeCheck, GitBranch, Zap, + Brain, } from "lucide-react"; import { Layout } from "@/components/layout/Layout"; import { @@ -40,6 +41,7 @@ import type { MyModule } from "@/types/module"; import { formatDate } from "@/utils/format-date"; import AuditLogs from "@/pages/tenant/AuditLogs"; import TenantSettings from "@/pages/tenant/Settings"; +import TenantAIProviders from "@/pages/tenant/TenantAIProviders"; // import DepartmentsTable from "@/components/superadmin/DepartmentsTable"; import DesignationsTable from "@/components/superadmin/DesignationsTable"; @@ -56,7 +58,8 @@ type TabType = | "settings" | "license" | "audit-logs" - | "billing"; + | "billing" + | "ai-providers"; const tabs: Array<{ id: TabType; label: string; icon: ReactElement }> = [ { id: "overview", label: "Overview", icon: }, @@ -85,6 +88,7 @@ const tabs: Array<{ id: TabType; label: string; icon: ReactElement }> = [ { id: "suppliers", label: "Suppliers", icon: }, { id: "modules", label: "Modules", icon: }, { id: "settings", label: "Settings", icon: }, + { id: "ai-providers", label: "AI Providers", icon: }, { id: "license", label: "License", icon: }, { id: "audit-logs", @@ -374,6 +378,9 @@ const TenantDetails = (): ReactElement => { {activeTab === "settings" && id && ( )} + {activeTab === "ai-providers" && id && ( + + )} {activeTab === "license" && } {activeTab === "audit-logs" && id && ( diff --git a/src/pages/tenant/TenantAIProviderCreate.tsx b/src/pages/tenant/TenantAIProviderCreate.tsx index afdc732..497f77d 100644 --- a/src/pages/tenant/TenantAIProviderCreate.tsx +++ b/src/pages/tenant/TenantAIProviderCreate.tsx @@ -7,7 +7,7 @@ import { SecondaryButton, FormTagInput, } from "@/components/shared"; -import { ArrowLeft } from "lucide-react"; +// import { ArrowLeft } from "lucide-react"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -29,7 +29,19 @@ const createConfigSchema = z.object({ custom_embedding_models: z.array(z.string()).default([]), }); -export const TenantAIProviderCreate = (): ReactElement => { +interface TenantAIProviderCreateProps { + customTenantId?: string; + hideLayout?: boolean; + onCancel?: () => void; + onSuccess?: () => void; +} + +export const TenantAIProviderCreate = ({ + customTenantId, + hideLayout = false, + onCancel, + onSuccess, +}: TenantAIProviderCreateProps = {}): ReactElement => { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -75,10 +87,14 @@ export const TenantAIProviderCreate = (): ReactElement => { custom_models: data.custom_models || [], default_embedding_model: data.default_embedding_model || undefined, custom_embedding_models: data.custom_embedding_models || [], - } as any); + } as any, customTenantId); showToast.success("AI Provider configuration created successfully!"); - navigate("/tenant/ai/providers"); + if (onSuccess) { + onSuccess(); + } else { + navigate("/tenant/ai/providers"); + } } catch (err: any) { if (err?.response?.data?.details && Array.isArray(err.response.data.details)) { err.response.data.details.forEach((detail: any) => { @@ -97,42 +113,26 @@ export const TenantAIProviderCreate = (): ReactElement => { } }; - return ( - - navigate("/tenant/ai/providers")} - className="h-10 px-5 min-w-[120px]" - > - Cancel - - - {isSubmitting ? "Saving..." : "Save Configuration"} - - - ), - }} - > -
+ const formContent = ( + <> + {/*
-
+
*/} -
+ {/* General Settings Section */}

@@ -370,6 +370,74 @@ export const TenantAIProviderCreate = (): ReactElement => {

+ + ); + + if (hideLayout) { + return ( +
+
+
+

AI Provider Configuration

+

Add or update API credentials and models for a tenant.

+
+
+ { + if (onCancel) { + onCancel(); + } else { + navigate("/tenant/ai/providers"); + } + }} + className="h-10 px-5 min-w-[120px]" + > + Cancel + + + {isSubmitting ? "Saving..." : "Save Configuration"} + +
+
+ {formContent} +
+ ); + } + + return ( + + navigate("/tenant/ai/providers")} + className="h-10 px-5 min-w-[120px]" + > + Cancel + + + {isSubmitting ? "Saving..." : "Save Configuration"} + + + ), + }} + > + {formContent} ); }; diff --git a/src/pages/tenant/TenantAIProviders.tsx b/src/pages/tenant/TenantAIProviders.tsx index e2b59c3..146b033 100644 --- a/src/pages/tenant/TenantAIProviders.tsx +++ b/src/pages/tenant/TenantAIProviders.tsx @@ -18,9 +18,19 @@ import { showToast } from "@/utils/toast"; import { formatDate } from "@/utils/format-date"; import { ViewAIProviderModal } from "@/components/tenant/ViewAIProviderModal"; import CodeBadge from "@/components/shared/CodeBadge"; +import { TenantAIProviderCreate } from "./TenantAIProviderCreate"; -export const TenantAIProviders = (): ReactElement => { +interface TenantAIProvidersProps { + customTenantId?: string; + hideLayout?: boolean; +} + +export const TenantAIProviders = ({ + customTenantId, + hideLayout = false, +}: TenantAIProvidersProps = {}): ReactElement => { const navigate = useNavigate(); + const [view, setView] = useState<"list" | "create">("list"); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [configs, setConfigs] = useState([]); @@ -42,7 +52,7 @@ export const TenantAIProviders = (): ReactElement => { setIsLoading(true); setError(null); try { - const data = await aiService.listConfigs(); + const data = await aiService.listConfigs(customTenantId); setConfigs(data || []); } catch (err: any) { const msg = @@ -56,12 +66,12 @@ export const TenantAIProviders = (): ReactElement => { useEffect(() => { void fetchConfigs(); - }, []); + }, [customTenantId]); const handleTestConnection = async (provider: string) => { setTestingProviders((prev) => ({ ...prev, [provider]: true })); try { - const resp = await aiService.testConfig(provider); + const resp = await aiService.testConfig(provider, customTenantId); if (resp && resp.healthy) { showToast.success( `Connection healthy for ${provider}! Latency: ${resp.latency_ms || "N/A"} ms`, @@ -80,7 +90,7 @@ export const TenantAIProviders = (): ReactElement => { const handleViewConfig = async (provider: string) => { try { - const cfg = await aiService.getConfig(provider); + const cfg = await aiService.getConfig(provider, customTenantId); setSelectedConfig(cfg); setIsViewModalOpen(true); } catch (err: any) { @@ -100,7 +110,7 @@ export const TenantAIProviders = (): ReactElement => { if (!providerToDelete) return; setIsDeleting(true); try { - await aiService.deleteConfig(providerToDelete); + await aiService.deleteConfig(providerToDelete, customTenantId); showToast.success(`${providerToDelete} config removed successfully`); void fetchConfigs(); setIsDeleteModalOpen(false); @@ -240,6 +250,100 @@ export const TenantAIProviders = (): ReactElement => { [testingProviders], ); + if (view === "create") { + return ( + setView("list")} + onSuccess={() => { + setView("list"); + void fetchConfigs(); + }} + /> + ); + } + + const listContent = ( +
+ {/* Subhead Toolbar matching Screenshot filter design */} +
+
+ + + setStatusFilter(typeof v === "string" ? v : "")} + placeholder="All" + options={[ + { value: "active", label: "Active" }, + { value: "disabled", label: "Disabled" }, + ]} + /> + + {(searchQuery || statusFilter) && ( + + )} +
+ + {hideLayout && ( + setView("create")} + className="h-10 px-4 flex items-center gap-1.5 shrink-0" + > + + Create AI Provider + + )} +
+ + {/* Table list */} + item.id || item.provider} + isLoading={isLoading} + error={error} + emptyMessage="No tenant AI providers configured." + /> + + setIsViewModalOpen(false)} + config={selectedConfig} + /> + + setIsDeleteModalOpen(false)} + onConfirm={onConfirmDelete} + title="Delete AI Provider" + message="Are you sure you want to delete this AI provider configuration? This action cannot be undone." + itemName={providerToDelete || ""} + isLoading={isDeleting} + /> +
+ ); + + if (hideLayout) { + return ( +
+ {listContent} +
+ ); + } + return ( { ), }} > -
- {/* Subhead Toolbar matching Screenshot filter design */} -
-
- - - setStatusFilter(typeof v === "string" ? v : "")} - placeholder="All" - options={[ - { value: "active", label: "Active" }, - { value: "disabled", label: "Disabled" }, - ]} - /> - - {(searchQuery || statusFilter) && ( - - )} -
-
- - {/* Table list */} - item.id || item.provider} - isLoading={isLoading} - error={error} - emptyMessage="No tenant AI providers configured." - /> -
- - setIsViewModalOpen(false)} - config={selectedConfig} - /> - - setIsDeleteModalOpen(false)} - onConfirm={onConfirmDelete} - title="Delete AI Provider" - message="Are you sure you want to delete this AI provider configuration? This action cannot be undone." - itemName={providerToDelete || ""} - isLoading={isDeleting} - /> + {listContent}
); }; diff --git a/src/services/ai-service.ts b/src/services/ai-service.ts index f8cfaa5..cfff38f 100644 --- a/src/services/ai-service.ts +++ b/src/services/ai-service.ts @@ -93,39 +93,47 @@ class AIService { return unwrap(response); } - async upsertConfig(payload: { - provider: string; - config_type: "azure" | "direct"; - api_key: string; - display_name?: string; - endpoint?: string; - deployment?: string; - api_version?: string; - custom_models?: string[]; - default_model?: string; - is_active?: boolean; - }): Promise { - const response = await apiClient.post("/ai/config", payload); + async upsertConfig( + payload: { + provider: string; + config_type: "azure" | "direct"; + api_key: string; + display_name?: string; + endpoint?: string; + deployment?: string; + api_version?: string; + custom_models?: string[]; + default_model?: string; + is_active?: boolean; + }, + tenantId?: string + ): Promise { + const headers = tenantId ? { "x-tenant-id": tenantId } : undefined; + const response = await apiClient.post("/ai/config", payload, { headers }); return unwrap(response); } - async listConfigs(): Promise { - const response = await apiClient.get("/ai/config"); + async listConfigs(tenantId?: string): Promise { + const headers = tenantId ? { "x-tenant-id": tenantId } : undefined; + const response = await apiClient.get("/ai/config", { headers }); return unwrap(response); } - async getConfig(provider: string): Promise { - const response = await apiClient.get(`/ai/config/${encodeURIComponent(provider)}`); + async getConfig(provider: string, tenantId?: string): Promise { + const headers = tenantId ? { "x-tenant-id": tenantId } : undefined; + const response = await apiClient.get(`/ai/config/${encodeURIComponent(provider)}`, { headers }); return unwrap(response); } - async testConfig(provider: string): Promise { - const response = await apiClient.post(`/ai/config/${encodeURIComponent(provider)}/test`, {}); + async testConfig(provider: string, tenantId?: string): Promise { + const headers = tenantId ? { "x-tenant-id": tenantId } : undefined; + const response = await apiClient.post(`/ai/config/${encodeURIComponent(provider)}/test`, {}, { headers }); return unwrap(response); } - async deleteConfig(provider: string): Promise { - await apiClient.delete(`/ai/config/${encodeURIComponent(provider)}`); + async deleteConfig(provider: string, tenantId?: string): Promise { + const headers = tenantId ? { "x-tenant-id": tenantId } : undefined; + await apiClient.delete(`/ai/config/${encodeURIComponent(provider)}`, { headers }); } async createPrompt(payload: {