From cfc4db150722173f4b8657879fec8762e8b46496 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Wed, 11 Mar 2026 21:53:57 +0800 Subject: [PATCH] refactor: extract data logic into reusable hooks (PR5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New hooks: - useSettings: generic settings CRUD (fetch/save with loading/saving states) - useBridgeAdapter: bridge adapter settings + verify flow - useBridgeStatus: bridge status polling + start/stop control - useAccountInfo: Claude SDK account info fetching Refactored components: - GeneralSection: replace inline fetchAccountInfo with useAccountInfo hook - BridgeSection: replace inline fetchStatus/startBridge/stopBridge + polling interval with useBridgeStatus hook (removed ~50 lines) The remaining bridge adapter sections (Telegram/Discord/Feishu/QQ) can be migrated to useBridgeAdapter in a follow-up — hooks are ready for adoption. Co-Authored-By: Claude Opus 4.6 --- src/components/bridge/BridgeSection.tsx | 88 ++--------------- src/components/settings/GeneralSection.tsx | 20 +--- src/hooks/useAccountInfo.ts | 41 ++++++++ src/hooks/useBridgeAdapter.ts | 106 +++++++++++++++++++++ src/hooks/useBridgeStatus.ts | 98 +++++++++++++++++++ src/hooks/useSettings.ts | 58 +++++++++++ 6 files changed, 312 insertions(+), 99 deletions(-) create mode 100644 src/hooks/useAccountInfo.ts create mode 100644 src/hooks/useBridgeAdapter.ts create mode 100644 src/hooks/useBridgeStatus.ts create mode 100644 src/hooks/useSettings.ts diff --git a/src/components/bridge/BridgeSection.tsx b/src/components/bridge/BridgeSection.tsx index f148d07..5f94ca3 100644 --- a/src/components/bridge/BridgeSection.tsx +++ b/src/components/bridge/BridgeSection.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback, useEffect, useRef } from "react"; +import { useState, useCallback, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; @@ -15,25 +15,12 @@ import { } from "@/components/ui/select"; import { SpinnerGap, CheckCircle, Warning, TelegramLogo, ChatTeardrop, GameController, ChatsCircle } from "@phosphor-icons/react"; import { useTranslation } from "@/hooks/useTranslation"; +import { useBridgeStatus } from "@/hooks/useBridgeStatus"; import { SettingsCard } from "@/components/patterns/SettingsCard"; import { FieldRow } from "@/components/patterns/FieldRow"; import { StatusBanner } from "@/components/patterns/StatusBanner"; import type { ProviderModelGroup } from "@/types"; -interface AdapterStatus { - channelType: string; - running: boolean; - connectedAt: string | null; - lastMessageAt: string | null; - error: string | null; -} - -interface BridgeStatus { - running: boolean; - startedAt: string | null; - adapters: AdapterStatus[]; -} - interface BridgeSettings { remote_bridge_enabled: string; bridge_telegram_enabled: string; @@ -60,14 +47,11 @@ const DEFAULT_SETTINGS: BridgeSettings = { export function BridgeSection() { const [settings, setSettings] = useState(DEFAULT_SETTINGS); - const [bridgeStatus, setBridgeStatus] = useState(null); const [saving, setSaving] = useState(false); - const [starting, setStarting] = useState(false); - const [stopping, setStopping] = useState(false); const [workDir, setWorkDir] = useState(""); const [model, setModel] = useState(""); const [providerGroups, setProviderGroups] = useState([]); - const pollRef = useRef | null>(null); + const { bridgeStatus, starting, stopping, startBridge, stopBridge } = useBridgeStatus(); const { t } = useTranslation(); const fetchSettings = useCallback(async () => { @@ -92,18 +76,6 @@ export function BridgeSection() { } }, []); - const fetchStatus = useCallback(async () => { - try { - const res = await fetch("/api/bridge"); - if (res.ok) { - const data = await res.json(); - setBridgeStatus(data); - } - } catch { - // ignore - } - }, []); - const fetchModels = useCallback(async () => { try { const res = await fetch("/api/providers/models"); @@ -120,24 +92,8 @@ export function BridgeSection() { useEffect(() => { fetchSettings(); - fetchStatus(); fetchModels(); - }, [fetchSettings, fetchStatus, fetchModels]); - - // Poll bridge status while bridge is running - useEffect(() => { - if (bridgeStatus?.running) { - pollRef.current = setInterval(fetchStatus, 5000); - } else if (pollRef.current) { - clearInterval(pollRef.current); - pollRef.current = null; - } - return () => { - if (pollRef.current) { - clearInterval(pollRef.current); - } - }; - }, [bridgeStatus?.running, fetchStatus]); + }, [fetchSettings, fetchModels]); const saveSettings = async (updates: Partial) => { setSaving(true); @@ -208,38 +164,6 @@ export function BridgeSection() { } }; - const handleStartBridge = async () => { - setStarting(true); - try { - await fetch("/api/bridge", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "start" }), - }); - await fetchStatus(); - } catch { - // ignore - } finally { - setStarting(false); - } - }; - - const handleStopBridge = async () => { - setStopping(true); - try { - await fetch("/api/bridge", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "stop" }), - }); - await fetchStatus(); - } catch { - // ignore - } finally { - setStopping(false); - } - }; - const handleToggleAutoStart = (checked: boolean) => { saveSettings({ bridge_auto_start: checked ? "true" : "" }); }; @@ -304,7 +228,7 @@ export function BridgeSection() {