"use client"; import { useState, useEffect, useRef } from "react"; import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; import Image from "next/image"; export default function OpenClawToolCard({ tool, isExpanded, onToggle, baseUrl, hasActiveProviders, apiKeys, activeProviders, cloudEnabled, }) { const [openclawStatus, setOpenclawStatus] = useState(null); const [checkingOpenclaw, setCheckingOpenclaw] = useState(false); const [applying, setApplying] = useState(false); const [restoring, setRestoring] = useState(false); const [message, setMessage] = useState(null); const [selectedApiKey, setSelectedApiKey] = useState(""); const [selectedModel, setSelectedModel] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [modelAliases, setModelAliases] = useState({}); const [showManualConfigModal, setShowManualConfigModal] = useState(false); const [customBaseUrl, setCustomBaseUrl] = useState(""); const hasInitializedModel = useRef(false); const getConfigStatus = () => { if (!openclawStatus?.installed) return null; const currentProvider = openclawStatus.settings?.models?.providers?.["9router"]; if (!currentProvider) return "not_configured"; const localMatch = currentProvider.baseUrl?.includes("localhost") || currentProvider.baseUrl?.includes("127.0.0.1") || currentProvider.baseUrl?.includes("0.0.0.0"); const tunnelMatch = baseUrl && currentProvider.baseUrl?.startsWith(baseUrl); if (localMatch || tunnelMatch) return "configured"; return "other"; }; const configStatus = getConfigStatus(); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (isExpanded && !openclawStatus) { checkOpenclawStatus(); fetchModelAliases(); } }, [isExpanded, openclawStatus]); const fetchModelAliases = async () => { try { const res = await fetch("/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { console.log("Error fetching model aliases:", error); } }; useEffect(() => { if (openclawStatus?.installed && !hasInitializedModel.current) { hasInitializedModel.current = true; const provider = openclawStatus.settings?.models?.providers?.["9router"]; if (provider) { const primaryModel = openclawStatus.settings?.agents?.defaults?.model?.primary; if (primaryModel) { const modelId = primaryModel.replace("9router/", ""); setSelectedModel(modelId); } if (provider.apiKey && apiKeys?.some(k => k.key === provider.apiKey)) { setSelectedApiKey(provider.apiKey); } } } }, [openclawStatus, apiKeys]); const checkOpenclawStatus = async () => { setCheckingOpenclaw(true); try { const res = await fetch("/api/cli-tools/openclaw-settings"); const data = await res.json(); setOpenclawStatus(data); } catch (error) { setOpenclawStatus({ installed: false, error: error.message }); } finally { setCheckingOpenclaw(false); } }; const normalizeLocalhost = (url) => url.replace("://localhost", "://127.0.0.1"); const getLocalBaseUrl = () => { if (typeof window !== "undefined") { return normalizeLocalhost(window.location.origin); } return "http://127.0.0.1:20128"; }; const getEffectiveBaseUrl = () => { const url = customBaseUrl || getLocalBaseUrl(); return url.endsWith("/v1") ? url : `${url}/v1`; }; const getDisplayUrl = () => { const url = customBaseUrl || getLocalBaseUrl(); return url.endsWith("/v1") ? url : `${url}/v1`; }; const handleApplySettings = async () => { setApplying(true); setMessage(null); try { const keyToUse = selectedApiKey?.trim() || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); const res = await fetch("/api/cli-tools/openclaw-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings applied successfully!" }); checkOpenclawStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to apply settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setApplying(false); } }; const handleResetSettings = async () => { setRestoring(true); setMessage(null); try { const res = await fetch("/api/cli-tools/openclaw-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); setSelectedModel(""); setSelectedApiKey(""); checkOpenclawStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to reset settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setRestoring(false); } }; const handleModelSelect = (model) => { setSelectedModel(model.value); setModalOpen(false); }; const getManualConfigs = () => { const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : ""); const settingsContent = { agents: { defaults: { model: { primary: `9router/${selectedModel || "provider/model-id"}`, }, }, }, models: { providers: { "9router": { baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, api: "openai-completions", models: [ { id: selectedModel || "provider/model-id", name: (selectedModel || "provider/model-id").split("/").pop(), }, ], }, }, }, }; return [ { filename: "~/.openclaw/openclaw.json", content: JSON.stringify(settingsContent, null, 2), }, ]; }; return (
{tool.name} { e.target.style.display = "none"; }} />

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} {configStatus === "other" && Other}

{tool.description}

expand_more
{isExpanded && (
{checkingOpenclaw && (
progress_activity Checking Open Claw CLI...
)} {!checkingOpenclaw && openclawStatus && !openclawStatus.installed && (
warning

Open Claw CLI not installed

Please install Open Claw CLI to use this feature.

)} {!checkingOpenclaw && openclawStatus?.installed && ( <>
{/* Current Base URL */} {openclawStatus?.settings?.models?.providers?.["9router"]?.baseUrl && (
Current arrow_forward {openclawStatus.settings.models.providers["9router"].baseUrl}
)} {/* Base URL */}
Base URL arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> {customBaseUrl && customBaseUrl !== baseUrl && ( )}
{/* API Key */}
API Key arrow_forward {apiKeys.length > 0 ? ( ) : ( {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Model */}
Model arrow_forward setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> {selectedModel && }
{message && (
{message.type === "success" ? "check_circle" : "error"} {message.text}
)}
)}
)} setModalOpen(false)} onSelect={handleModelSelect} selectedModel={selectedModel} activeProviders={activeProviders} modelAliases={modelAliases} title="Select Model for Open Claw" /> setShowManualConfigModal(false)} title="Open Claw - Manual Configuration" configs={getManualConfigs()} />
); }