From 4638cf0e81eab45f59c36134eb1123677f100020 Mon Sep 17 00:00:00 2001 From: anuragg-saxenaa Date: Wed, 22 Apr 2026 10:42:34 +0700 Subject: [PATCH] fix(ollama-local): support custom host URL for remote Ollama servers (closes #578) Add a shared resolveOllamaLocalHost() helper and wire it through the executor, models/validate/test routes, so users can point ollama-local at a remote Ollama instance instead of being locked to localhost:11434. Also expose the host as an "Ollama Host URL" field in AddApiKeyModal (empty = default localhost:11434), making the option reachable from the dashboard without hand-editing db.json. Co-authored-by: anuragg-saxenaa Made-with: Cursor --- open-sse/config/providers.js | 7 ++ open-sse/executors/base.js | 4 + .../providers/[id]/AddApiKeyModal.js | 77 ++++++++++++++----- src/app/api/providers/[id]/models/route.js | 8 +- src/app/api/providers/[id]/test/testUtils.js | 7 +- src/app/api/providers/validate/route.js | 5 +- 6 files changed, 77 insertions(+), 31 deletions(-) diff --git a/open-sse/config/providers.js b/open-sse/config/providers.js index 664ab033..fb2cbb9e 100644 --- a/open-sse/config/providers.js +++ b/open-sse/config/providers.js @@ -339,3 +339,10 @@ export const PROVIDERS = { noAuth: true }, }; + +export const OLLAMA_LOCAL_DEFAULT_HOST = "http://localhost:11434"; + +export function resolveOllamaLocalHost(credentials) { + const raw = credentials?.providerSpecificData?.baseUrl?.trim(); + return (raw || OLLAMA_LOCAL_DEFAULT_HOST).replace(/\/$/, ""); +} diff --git a/open-sse/executors/base.js b/open-sse/executors/base.js index a42828e0..eaea0792 100644 --- a/open-sse/executors/base.js +++ b/open-sse/executors/base.js @@ -1,4 +1,5 @@ import { HTTP_STATUS, RETRY_CONFIG, DEFAULT_RETRY_CONFIG } from "../config/runtimeConfig.js"; +import { resolveOllamaLocalHost } from "../config/providers.js"; import { proxyAwareFetch } from "../utils/proxyFetch.js"; /** @@ -35,6 +36,9 @@ export class BaseExecutor { const normalized = baseUrl.replace(/\/$/, ""); return `${normalized}/messages`; } + if (this.provider === "ollama-local") { + return `${resolveOllamaLocalHost(credentials)}/api/chat`; + } const baseUrls = this.getBaseUrls(); return baseUrls[urlIndex] || baseUrls[0] || this.config.baseUrl; } diff --git a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js index ef461db0..347f20e4 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js @@ -6,24 +6,33 @@ import { Button, Badge, Input, Modal, Select } from "@/shared/components"; export default function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthropic, proxyPools, onSave, onClose }) { const NONE_PROXY_POOL_VALUE = "__none__"; + const isOllamaLocal = provider === "ollama-local"; const [formData, setFormData] = useState({ name: "", apiKey: "", priority: 1, proxyPoolId: NONE_PROXY_POOL_VALUE, + ollamaHostUrl: "", }); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); const [saving, setSaving] = useState(false); + const buildProviderSpecificData = () => { + if (isOllamaLocal && formData.ollamaHostUrl.trim()) { + return { baseUrl: formData.ollamaHostUrl.trim() }; + } + return undefined; + }; + const handleValidate = async () => { setValidating(true); try { const res = await fetch("/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ provider, apiKey: formData.apiKey }), + body: JSON.stringify({ provider, apiKey: formData.apiKey, providerSpecificData: buildProviderSpecificData() }), }); const data = await res.json(); setValidationResult(data.valid ? "success" : "failed"); @@ -35,7 +44,12 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa }; const handleSubmit = async () => { - if (!provider || !formData.apiKey) return; + if (!provider) return; + if (!isOllamaLocal && !formData.apiKey) return; + if (!isOllamaLocal) { + // Non-ollama providers require a name + if (!formData.name) return; + } setSaving(true); try { @@ -46,7 +60,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa const res = await fetch("/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ provider, apiKey: formData.apiKey }), + body: JSON.stringify({ provider, apiKey: formData.apiKey, providerSpecificData: buildProviderSpecificData() }), }); const data = await res.json(); isValid = !!data.valid; @@ -58,12 +72,12 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa } await onSave({ - name: formData.name, + name: formData.name || (isOllamaLocal ? "Ollama Local" : ""), apiKey: formData.apiKey, priority: formData.priority, proxyPoolId: formData.proxyPoolId === NONE_PROXY_POOL_VALUE ? null : formData.proxyPoolId, testStatus: isValid ? "active" : "unknown", - providerSpecificData: undefined + providerSpecificData: buildProviderSpecificData() }); } finally { setSaving(false); @@ -79,22 +93,45 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa label="Name" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} - placeholder="Production Key" + placeholder={isOllamaLocal ? "Ollama Local" : "Production Key"} /> -
- setFormData({ ...formData, apiKey: e.target.value })} - className="flex-1" - /> -
- + {isOllamaLocal && ( +
+ setFormData({ ...formData, ollamaHostUrl: e.target.value })} + placeholder="http://localhost:11434" + className="flex-1" + /> +
+ +
-
+ )} + {!isOllamaLocal && ( +
+ setFormData({ ...formData, apiKey: e.target.value })} + className="flex-1" + /> +
+ +
+
+ )} + {isOllamaLocal && ( +

+ Leave blank to use http://localhost:11434. For remote Ollama, enter the full host URL (e.g. http://192.168.1.10:11434). +

+ )} {validationResult && ( {validationResult === "success" ? "Valid" : "Invalid"} @@ -137,7 +174,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa

-