diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.js b/src/app/(dashboard)/dashboard/providers/[id]/page.js
index 764c59c2..494f055c 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/page.js
+++ b/src/app/(dashboard)/dashboard/providers/[id]/page.js
@@ -65,6 +65,7 @@ export default function ProviderDetailPage() {
const [providerStrategy, setProviderStrategy] = useState(null);
const [providerStickyLimit, setProviderStickyLimit] = useState("");
const [providerNoAuthEnabled, setProviderNoAuthEnabled] = useState(true);
+ const [providerTimeout, setProviderTimeout] = useState("");
const [thinkingMode, setThinkingMode] = useState("auto");
const [autoPing, setAutoPing] = useState({ enabled: false, connections: {} });
const [suggestedModels, setSuggestedModels] = useState([]);
@@ -323,6 +324,9 @@ export default function ProviderDetailPage() {
setProviderStrategy(override.fallbackStrategy || null);
setProviderStickyLimit(override.stickyRoundRobinLimit != null ? String(override.stickyRoundRobinLimit) : "1");
setProviderNoAuthEnabled(override.enabled !== false);
+ // Load per-provider connect timeout
+ const timeoutCfg = (settingsData.providerTimeouts || {})[providerId] || {};
+ setProviderTimeout(timeoutCfg.timeoutMs != null ? String(timeoutCfg.timeoutMs) : "");
// Load per-provider thinking config
const thinkingCfg = (settingsData.providerThinking || {})[providerId] || {};
setThinkingMode(thinkingCfg.mode || "auto");
@@ -459,6 +463,38 @@ export default function ProviderDetailPage() {
saveThinkingConfig(mode);
};
+ const saveProviderTimeout = async (ms) => {
+ try {
+ const settingsRes = await fetch("/api/settings", { cache: "no-store" });
+ const settingsData = settingsRes.ok ? await settingsRes.json() : {};
+ const current = settingsData.providerTimeouts || {};
+ const updated = { ...current };
+ if (!ms || ms === "") {
+ delete updated[providerId];
+ } else {
+ const timeoutMs = parseInt(ms, 10);
+ if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
+ updated[providerId] = { timeoutMs };
+ } else {
+ delete updated[providerId];
+ }
+ }
+ await fetch("/api/settings", {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ providerTimeouts: updated }),
+ });
+ } catch (error) {
+ console.log("Error saving provider timeout:", error);
+ }
+ };
+
+ const handleTimeoutChange = (value) => {
+ const cleaned = value.replace(/[^0-9]/g, "");
+ setProviderTimeout(cleaned);
+ saveProviderTimeout(cleaned);
+ };
+
const saveAutoPing = async (next) => {
const autoPingSettingsKey = AUTO_PING_SETTINGS_KEYS[providerId];
if (!autoPingSettingsKey) return;
@@ -1678,6 +1714,21 @@ export default function ProviderDetailPage() {
)}
>
)}
+ {/* Connect Timeout */}
+
+
Connect Timeout
+
+ handleTimeoutChange(e.target.value)}
+ placeholder="default"
+ className="w-20 px-2 py-1 text-xs border border-border rounded-md bg-background focus:outline-none focus:border-primary"
+ />
+ ms
+
+
{/* Round Robin toggle */}
Round Robin
diff --git a/src/lib/db/repos/settingsRepo.js b/src/lib/db/repos/settingsRepo.js
index a317840b..433660c7 100644
--- a/src/lib/db/repos/settingsRepo.js
+++ b/src/lib/db/repos/settingsRepo.js
@@ -13,6 +13,8 @@ const DEFAULT_SETTINGS = {
tailscaleUrl: "",
stickyRoundRobinLimit: 3,
providerStrategies: {},
+ providerTimeouts: {},
+ defaultTimeoutMs: null,
quotaVisibility: {},
comboStrategy: "fallback",
comboStickyRoundRobinLimit: 1,