diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js index a68d0ee..5ea740d 100644 --- a/src/app/(dashboard)/dashboard/profile/page.js +++ b/src/app/(dashboard)/dashboard/profile/page.js @@ -1,11 +1,42 @@ "use client"; +import { useState, useEffect } from "react"; import { Card, Button, Badge, Toggle } from "@/shared/components"; import { useTheme } from "@/shared/hooks/useTheme"; import { APP_CONFIG } from "@/shared/constants/config"; export default function ProfilePage() { const { theme, setTheme, isDark } = useTheme(); + const [settings, setSettings] = useState({ fallbackStrategy: "fill-first" }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/settings") + .then((res) => res.json()) + .then((data) => { + setSettings(data); + setLoading(false); + }) + .catch((err) => { + console.error("Failed to fetch settings:", err); + setLoading(false); + }); + }, []); + + const updateFallbackStrategy = async (strategy) => { + try { + const res = await fetch("/api/settings", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ fallbackStrategy: strategy }), + }); + if (res.ok) { + setSettings(prev => ({ ...prev, fallbackStrategy: strategy })); + } + } catch (err) { + console.error("Failed to update settings:", err); + } + }; return (
@@ -28,6 +59,31 @@ export default function ProfilePage() {
+ {/* Routing Preferences */} + +

Routing Strategy

+
+
+
+

Round Robin

+

+ Cycle through accounts to distribute load +

+
+ updateFallbackStrategy(settings.fallbackStrategy === "round-robin" ? "fill-first" : "round-robin")} + disabled={loading} + /> +
+

+ {settings.fallbackStrategy === "round-robin" + ? "Currently distributing requests across all available accounts." + : "Currently using accounts in priority order (Fill First)."} +

+
+
+ {/* Theme Preferences */}

Appearance

diff --git a/src/app/api/settings/route.js b/src/app/api/settings/route.js index 423f182..5f10e09 100644 --- a/src/app/api/settings/route.js +++ b/src/app/api/settings/route.js @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/localDb"; export async function GET() { try { @@ -10,3 +10,14 @@ export async function GET() { return NextResponse.json({ error: error.message }, { status: 500 }); } } + +export async function PATCH(request) { + try { + const body = await request.json(); + const settings = await updateSettings(body); + return NextResponse.json(settings); + } catch (error) { + console.log("Error updating settings:", error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/sse/services/auth.js b/src/sse/services/auth.js index 1520042..90c053b 100644 --- a/src/sse/services/auth.js +++ b/src/sse/services/auth.js @@ -1,16 +1,16 @@ -import { getProviderConnections, validateApiKey, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnections, validateApiKey, updateProviderConnection, getSettings } from "@/lib/localDb"; import { isAccountUnavailable, getUnavailableUntil } from "open-sse/services/accountFallback.js"; import * as log from "../utils/logger.js"; /** * Get provider credentials from localDb - * Filters out unavailable accounts and returns the highest priority available account + * Filters out unavailable accounts and returns the selected account based on strategy * @param {string} provider - Provider name * @param {string|null} excludeConnectionId - Connection ID to exclude (for retry with next account) */ export async function getProviderCredentials(provider, excludeConnectionId = null) { const connections = await getProviderConnections({ provider, isActive: true }); - + if (connections.length === 0) { log.warn("AUTH", `No credentials for ${provider}`); return null; @@ -28,7 +28,26 @@ export async function getProviderCredentials(provider, excludeConnectionId = nul return null; } - const connection = availableConnections[0]; + const settings = await getSettings(); + const strategy = settings.fallbackStrategy || "fill-first"; + + let connection; + if (strategy === "round-robin") { + // Sort by lastUsed (nulls first) to pick the least recently used + const sorted = [...availableConnections].sort((a, b) => { + if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999); + if (!a.lastUsedAt) return -1; + if (!b.lastUsedAt) return 1; + return new Date(a.lastUsedAt) - new Date(b.lastUsedAt); + }); + connection = sorted[0]; + + // Update lastUsedAt asynchronously + updateProviderConnection(connection.id, { lastUsedAt: new Date().toISOString() }).catch(() => {}); + } else { + // Default: fill-first (already sorted by priority in getProviderConnections) + connection = availableConnections[0]; + } return { apiKey: connection.apiKey,