From 721da6f2eebd8f519ea659895d21a597215ec0d8 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 00:50:27 +0400 Subject: [PATCH] feat: add API Status Checker tab in Advanced Settings with connection status and navigation --- .../components/advanced-settings-modal.tsx | 26 +- .../components/settings/ApiStatusChecker.tsx | 318 ++++++++++++++++++ 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/components/settings/ApiStatusChecker.tsx diff --git a/packages/ui/src/components/advanced-settings-modal.tsx b/packages/ui/src/components/advanced-settings-modal.tsx index 477c2ed..6b3a9ba 100644 --- a/packages/ui/src/components/advanced-settings-modal.tsx +++ b/packages/ui/src/components/advanced-settings-modal.tsx @@ -7,6 +7,7 @@ import QwenCodeSettings from "./settings/QwenCodeSettings" import ZAISettings from "./settings/ZAISettings" import OpenCodeZenSettings from "./settings/OpenCodeZenSettings" import AntigravitySettings from "./settings/AntigravitySettings" +import ApiStatusChecker from "./settings/ApiStatusChecker" interface AdvancedSettingsModalProps { open: boolean @@ -17,7 +18,7 @@ interface AdvancedSettingsModalProps { } const AdvancedSettingsModal: Component = (props) => { - const [activeTab, setActiveTab] = createSignal("general") + const [activeTab, setActiveTab] = createSignal("api-status") return ( !open && props.onClose()}> @@ -31,6 +32,15 @@ const AdvancedSettingsModal: Component = (props) =>
+
+ +
+ { + if (apiId === "opencode-zen") setActiveTab("zen") + else if (apiId === "ollama-cloud") setActiveTab("ollama") + else if (apiId === "zai") setActiveTab("zai") + else if (apiId === "qwen-oauth") setActiveTab("qwen") + else if (apiId === "antigravity") setActiveTab("antigravity") + }} + /> +
+
+ diff --git a/packages/ui/src/components/settings/ApiStatusChecker.tsx b/packages/ui/src/components/settings/ApiStatusChecker.tsx new file mode 100644 index 0000000..a53b98a --- /dev/null +++ b/packages/ui/src/components/settings/ApiStatusChecker.tsx @@ -0,0 +1,318 @@ +import { Component, createSignal, onMount, For, Show, createEffect, on } from "solid-js" +import { CheckCircle, XCircle, Loader, RefreshCw, Settings, AlertTriangle } from "lucide-solid" + +interface ApiStatus { + id: string + name: string + icon: string + enabled: boolean + connected: boolean + checking: boolean + error?: string + lastChecked?: number +} + +interface ApiStatusCheck { + id: string + name: string + icon: string + checkEnabled: () => Promise + testConnection: () => Promise +} + +const API_CHECKS: ApiStatusCheck[] = [ + { + id: "opencode-zen", + name: "OpenCode Zen", + icon: "🆓", + checkEnabled: async () => true, // Always available + testConnection: async () => { + try { + const res = await fetch("/api/opencode-zen/test") + if (!res.ok) return false + const data = await res.json() + return data.connected === true + } catch { + return false + } + }, + }, + { + id: "ollama-cloud", + name: "Ollama Cloud", + icon: "🦙", + checkEnabled: async () => { + try { + const res = await fetch("/api/ollama/config") + if (!res.ok) return false + const data = await res.json() + return data.config?.enabled === true + } catch { + return false + } + }, + testConnection: async () => { + try { + const res = await fetch("/api/ollama/test", { method: "POST" }) + if (!res.ok) return false + const data = await res.json() + return data.connected === true + } catch { + return false + } + }, + }, + { + id: "zai", + name: "Z.AI Plan", + icon: "🧠", + checkEnabled: async () => { + try { + const res = await fetch("/api/zai/config") + if (!res.ok) return false + const data = await res.json() + return data.config?.enabled === true + } catch { + return false + } + }, + testConnection: async () => { + try { + const res = await fetch("/api/zai/test", { method: "POST" }) + if (!res.ok) return false + const data = await res.json() + return data.connected === true + } catch { + return false + } + }, + }, + { + id: "qwen-oauth", + name: "Qwen Code", + icon: "🔷", + checkEnabled: async () => { + const token = localStorage.getItem("qwen_oauth_token") + return token !== null && token.length > 0 + }, + testConnection: async () => { + try { + const tokenStr = localStorage.getItem("qwen_oauth_token") + if (!tokenStr) return false + const token = JSON.parse(tokenStr) + // Check if token is expired + const expiresAt = (token.created_at || 0) + (token.expires_in || 0) * 1000 + return Date.now() < expiresAt + } catch { + return false + } + }, + }, + { + id: "antigravity", + name: "Antigravity", + icon: "🚀", + checkEnabled: async () => { + const token = localStorage.getItem("antigravity_oauth_token") + return token !== null && token.length > 0 + }, + testConnection: async () => { + try { + const tokenStr = localStorage.getItem("antigravity_oauth_token") + if (!tokenStr) return false + const token = JSON.parse(tokenStr) + const expiresAt = (token.created_at || 0) + (token.expires_in || 0) * 1000 + return Date.now() < expiresAt + } catch { + return false + } + }, + }, +] + +interface ApiStatusCheckerProps { + onSettingsClick?: (apiId: string) => void + compact?: boolean +} + +const ApiStatusChecker: Component = (props) => { + const [statuses, setStatuses] = createSignal([]) + const [isChecking, setIsChecking] = createSignal(false) + const [lastFullCheck, setLastFullCheck] = createSignal(0) + + const checkAllApis = async () => { + setIsChecking(true) + const results: ApiStatus[] = [] + + for (const api of API_CHECKS) { + setStatuses((prev) => { + const existing = prev.find((s) => s.id === api.id) + if (existing) { + return prev.map((s) => (s.id === api.id ? { ...s, checking: true } : s)) + } + return [...prev, { id: api.id, name: api.name, icon: api.icon, enabled: false, connected: false, checking: true }] + }) + + try { + const enabled = await api.checkEnabled() + let connected = false + let error: string | undefined + + if (enabled) { + try { + connected = await api.testConnection() + } catch (e) { + error = e instanceof Error ? e.message : "Connection test failed" + } + } + + results.push({ + id: api.id, + name: api.name, + icon: api.icon, + enabled, + connected, + checking: false, + error, + lastChecked: Date.now(), + }) + } catch (e) { + results.push({ + id: api.id, + name: api.name, + icon: api.icon, + enabled: false, + connected: false, + checking: false, + error: e instanceof Error ? e.message : "Check failed", + lastChecked: Date.now(), + }) + } + } + + setStatuses(results) + setLastFullCheck(Date.now()) + setIsChecking(false) + } + + onMount(() => { + checkAllApis() + }) + + const getStatusIcon = (status: ApiStatus) => { + if (status.checking) { + return + } + if (!status.enabled) { + return
+ } + if (status.connected) { + return + } + if (status.error) { + return + } + return + } + + const getStatusText = (status: ApiStatus) => { + if (status.checking) return "Checking..." + if (!status.enabled) return "Not configured" + if (status.connected) return "Connected" + if (status.error) return status.error + return "Connection failed" + } + + const enabledCount = () => statuses().filter((s) => s.enabled && s.connected).length + const totalConfigured = () => statuses().filter((s) => s.enabled).length + + if (props.compact) { + return ( +
+ APIs: +
+ + {(status) => ( +
props.onSettingsClick?.(status.id)} + > + {status.icon} + + + +
+ )} +
+
+ +
+ ) + } + + return ( +
+
+
+

API Connections

+

+ {enabledCount()} of {totalConfigured()} APIs connected +

+
+ +
+ +
+ + {(status) => ( +
+
+ {status.icon} +
+
{status.name}
+
{getStatusText(status)}
+
+
+
+ {getStatusIcon(status)} + +
+
+ )} +
+
+ + 0}> +

+ Last checked: {new Date(lastFullCheck()).toLocaleTimeString()} +

+
+
+ ) +} + +export default ApiStatusChecker