"use client"; import { useState, useEffect } from "react"; import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; import Image from "next/image"; export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders, cloudEnabled }) { const [codexStatus, setCodexStatus] = useState(null); const [checkingCodex, setCheckingCodex] = useState(false); const [applying, setApplying] = useState(false); const [restoring, setRestoring] = useState(false); const [message, setMessage] = useState(null); const [showInstallGuide, setShowInstallGuide] = useState(false); const [selectedApiKey, setSelectedApiKey] = useState(""); const [selectedModel, setSelectedModel] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [modelAliases, setModelAliases] = useState({}); const [showManualConfigModal, setShowManualConfigModal] = useState(false); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (isExpanded && !codexStatus) { checkCodexStatus(); fetchModelAliases(); } }, [isExpanded, codexStatus]); 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); } }; // Parse model from config content useEffect(() => { if (codexStatus?.config) { const modelMatch = codexStatus.config.match(/^model\s*=\s*"([^"]+)"/m); if (modelMatch) setSelectedModel(modelMatch[1]); } }, [codexStatus]); const getConfigStatus = () => { if (!codexStatus?.installed) return null; if (!codexStatus.config) return "not_configured"; const hasBaseUrl = codexStatus.config.includes(baseUrl) || codexStatus.config.includes("localhost") || codexStatus.config.includes("127.0.0.1"); return hasBaseUrl ? "configured" : "other"; }; const configStatus = getConfigStatus(); const checkCodexStatus = async () => { setCheckingCodex(true); try { const res = await fetch("/api/cli-tools/codex-settings"); const data = await res.json(); setCodexStatus(data); } catch (error) { setCodexStatus({ installed: false, error: error.message }); } finally { setCheckingCodex(false); } }; const handleApplySettings = async () => { setApplying(true); setMessage(null); try { // Use sk_9router for localhost if no key, otherwise use selected key const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); const res = await fetch("/api/cli-tools/codex-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl, apiKey: keyToUse, model: selectedModel }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings applied successfully!" }); checkCodexStatus(); } 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/codex-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); setSelectedModel(""); checkCodexStatus(); } 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 configContent = `# 9Router Configuration for Codex CLI model = "${selectedModel}" model_provider = "9router" [model_providers.9router] name = "9Router" base_url = "${baseUrl}/v1" wire_api = "responses" `; const authContent = JSON.stringify({ OPENAI_API_KEY: keyToUse }, null, 2); return [ { filename: "~/.codex/config.toml", content: configContent, }, { filename: "~/.codex/auth.json", content: authContent, }, ]; }; return (
{tool.name} { e.target.style.display = "none"; }} />

{tool.name}

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

{tool.description}

expand_more
{isExpanded && (
{checkingCodex && (
progress_activity Checking Codex CLI...
)} {!checkingCodex && codexStatus && !codexStatus.installed && (
warning

Codex CLI not installed

Please install Codex CLI to use auto-apply feature.

{showInstallGuide && (

Installation Guide

macOS / Linux / Windows:

npm install -g @openai/codex

After installation, run codex to verify.

Codex uses ~/.codex/auth.json with OPENAI_API_KEY. Click "Apply" to auto-configure.

)}
)} {!checkingCodex && codexStatus?.installed && ( <>
check_circle URL: {baseUrl}/v1
Key: {apiKeys.length > 0 ? ( ) : ( {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router"} )}
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 Codex" /> setShowManualConfigModal(false)} title="Codex CLI - Manual Configuration" configs={getManualConfigs()} />
); }