diff --git a/backend/src/common/utils/ai-caller.ts b/backend/src/common/utils/ai-caller.ts index 13f80e8..d3b14a4 100644 --- a/backend/src/common/utils/ai-caller.ts +++ b/backend/src/common/utils/ai-caller.ts @@ -37,7 +37,12 @@ export async function callOpenAICompatible(params: AICallerParams): Promise((resolve, reject) => { - const url = new URL(`${apiUrl}/chat/completions`); + // Normalize: strip trailing slash and /chat/completions if user included it + let baseUrl = apiUrl.replace(/\/+$/, ''); + if (baseUrl.endsWith('/chat/completions')) { + baseUrl = baseUrl.slice(0, -'/chat/completions'.length); + } + const url = new URL(`${baseUrl}/chat/completions`); const options = { hostname: url.hostname, diff --git a/frontend/src/pages/admin/AdminShadowAiPage.tsx b/frontend/src/pages/admin/AdminShadowAiPage.tsx index 996a7f8..70ca42a 100644 --- a/frontend/src/pages/admin/AdminShadowAiPage.tsx +++ b/frontend/src/pages/admin/AdminShadowAiPage.tsx @@ -194,7 +194,7 @@ function ModelSlotCard({ slot, model, isLoading }: { slot: string; model?: Shado setName(e.target.value)} size="sm" /> - setApiUrl(e.target.value)} size="sm" /> + setApiUrl(e.target.value)} size="sm" /> setApiKey(e.target.value)} size="sm" /> setModelName(e.target.value)} size="sm" /> setIsActive(e.currentTarget.checked)} />