"use client"; import { useState, useEffect, useRef } from "react"; import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; import Image from "next/image"; import BaseUrlSelect from "./BaseUrlSelect"; import ApiKeySelect from "./ApiKeySelect"; import { matchKnownEndpoint } from "./cliEndpointMatch"; export default function JcodeToolCard({ tool, isExpanded, onToggle, baseUrl, hasActiveProviders, apiKeys, activeProviders, cloudEnabled, initialStatus, tunnelEnabled, tunnelPublicUrl, tailscaleEnabled, tailscaleUrl, }) { const [jcodeStatus, setJcodeStatus] = useState(initialStatus || null); const [checkingJcode, setCheckingJcode] = 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 (!jcodeStatus?.installed) return null; if (!jcodeStatus?.has9Router) return "not_configured"; const currentProvider = jcodeStatus.config?.providers?.["9router"]; if (!currentProvider) return "not_configured"; return matchKnownEndpoint(currentProvider.base_url, { tunnelPublicUrl, tailscaleUrl }) ? "configured" : "other"; }; const configStatus = getConfigStatus(); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (initialStatus) setJcodeStatus(initialStatus); }, [initialStatus]); useEffect(() => { if (isExpanded && !jcodeStatus) { checkJcodeStatus(); fetchModelAliases(); } if (isExpanded) fetchModelAliases(); }, [isExpanded]); 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 (jcodeStatus?.installed && !hasInitializedModel.current) { hasInitializedModel.current = true; const provider = jcodeStatus.config?.providers?.["9router"]; if (provider) { if (provider.default_model) { setSelectedModel(provider.default_model); } // Try to match API key from env file const envApiKey = jcodeStatus.envApiKey; if (envApiKey && apiKeys?.some(k => k.key === envApiKey)) { setSelectedApiKey(envApiKey); } } } }, [jcodeStatus, apiKeys]); const checkJcodeStatus = async () => { setCheckingJcode(true); try { const res = await fetch("/api/cli-tools/jcode-settings"); const data = await res.json(); setJcodeStatus(data); } catch (error) { setJcodeStatus({ installed: false, error: error.message }); } finally { setCheckingJcode(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/jcode-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, models: selectedModel ? [selectedModel] : [], }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings applied successfully!" }); checkJcodeStatus(); } 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/jcode-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); setSelectedModel(""); setSelectedApiKey(""); checkJcodeStatus(); } 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 configToml = `[providers.9router] type = "openai-compatible" base_url = "${getEffectiveBaseUrl()}" auth = "bearer" api_key_env = "JCODE_9ROUTER_API_KEY" env_file = "provider-9router.env" default_model = "${selectedModel || "cc/claude-opus-4-7"}" requires_api_key = true [[providers.9router.models]] id = "${selectedModel || "cc/claude-opus-4-7"}"`; const envContent = `JCODE_9ROUTER_API_KEY="${keyToUse}"`; return [ { filename: "~/.jcode/config.toml", content: configToml, }, { filename: "~/.config/jcode/provider-9router.env", content: envContent, }, ]; }; 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 && (
{checkingJcode && (
progress_activity Checking jcode CLI...
)} {!checkingJcode && jcodeStatus && !jcodeStatus.installed && (
warning

jcode CLI not detected locally

Install jcode to enable automatic configuration:

curl -fsSL https://raw.githubusercontent.com/1jehuang/jcode/master/scripts/install.sh | bash

Manual configuration is still available if 9router is deployed on a remote server.

)} {!checkingJcode && jcodeStatus?.installed && ( <>
{/* Info notes */} {tool.notes && tool.notes.length > 0 && (
{tool.notes.map((note, idx) => (
{note.type === "info" ? "info" : note.type === "warning" ? "warning" : "help"} {note.text}
))}
)} {/* Endpoint (selector) */}
Select Endpoint arrow_forward
{/* Current configured */} {jcodeStatus?.config?.providers?.["9router"]?.base_url && (
Current arrow_forward {jcodeStatus.config.providers["9router"].base_url}
)} {/* API Key */}
API Key arrow_forward
{/* Default Model */}
Default Model arrow_forward
setSelectedModel(e.target.value)} placeholder="cc/claude-opus-4-7" className="w-full min-w-0 pl-2 pr-7 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> {selectedModel && }
{/* Usage hint */}

Usage:

jcode --provider-profile 9router jcode --provider-profile 9router --model {selectedModel || "cc/claude-opus-4-7"}
{message && (
{message.type === "success" ? "check_circle" : "error"} {message.text}
)}
)}
)} setModalOpen(false)} onSelect={handleModelSelect} selectedModel={selectedModel} activeProviders={activeProviders} modelAliases={modelAliases} title="Select Model for jcode" /> setShowManualConfigModal(false)} title="jcode - Manual Configuration" configs={getManualConfigs()} />
); }