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
-