"use client"; import { useState, useEffect } from "react"; import { Card, Button, ModelSelectModal, ManualConfigModal } from "@/shared/components"; import Image from "next/image"; export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, apiKeys, activeProviders, cloudEnabled, initialStatus }) { const [status, setStatus] = useState(initialStatus || null); const [checking, setChecking] = useState(false); const [applying, setApplying] = useState(false); const [restoring, setRestoring] = useState(false); const [message, setMessage] = useState(null); const [selectedApiKey, setSelectedApiKey] = useState(""); const [modelAliases, setModelAliases] = useState({}); const [showManualConfigModal, setShowManualConfigModal] = useState(false); // Model list management const [modelInput, setModelInput] = useState(""); const [modelList, setModelList] = useState([]); const [modalOpen, setModalOpen] = useState(false); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (initialStatus) setStatus(initialStatus); }, [initialStatus]); useEffect(() => { if (isExpanded && !status) { checkStatus(); fetchModelAliases(); } if (isExpanded) fetchModelAliases(); }, [isExpanded]); // Pre-fill model list from existing config useEffect(() => { if (status?.config && Array.isArray(status.config) && modelList.length === 0) { const entry = status.config.find((e) => e.name === "9Router"); if (entry?.models?.length > 0) { setModelList(entry.models.map((m) => m.id)); } } }, [status]); 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); } }; const getConfigStatus = () => { if (!status) return null; if (!status.has9Router) return "not_configured"; const url = status.currentUrl || ""; return url.includes("localhost") || url.includes("127.0.0.1") || url.includes(baseUrl) ? "configured" : "other"; }; const configStatus = getConfigStatus(); const getEffectiveBaseUrl = () => baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`; const addModel = () => { const val = modelInput.trim(); if (!val || modelList.includes(val)) return; setModelList((prev) => [...prev, val]); setModelInput(""); }; const removeModel = (id) => setModelList((prev) => prev.filter((m) => m !== id)); const checkStatus = async () => { setChecking(true); try { const res = await fetch("/api/cli-tools/copilot-settings"); const data = await res.json(); setStatus(data); } catch (error) { setStatus({ error: error.message }); } finally { setChecking(false); } }; const handleApply = async () => { setApplying(true); setMessage(null); try { const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); const res = await fetch("/api/cli-tools/copilot-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, models: modelList }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: data.message || "Settings applied successfully!" }); checkStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to apply settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setApplying(false); } }; const handleReset = async () => { setRestoring(true); setMessage(null); try { const res = await fetch("/api/cli-tools/copilot-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); setModelList([]); checkStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to reset settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setRestoring(false); } }; const getManualConfigs = () => { const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : ""); const effectiveBaseUrl = getEffectiveBaseUrl(); return [{ filename: "~/Library/Application Support/Code/User/chatLanguageModels.json", content: JSON.stringify([{ name: "9Router", vendor: "azure", apiKey: keyToUse, models: modelList.map((id) => ({ id, name: id, url: `${effectiveBaseUrl}/chat/completions#models.ai.azure.com`, toolCalling: true, vision: false, maxInputTokens: 128000, maxOutputTokens: 16000, })), }], 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 && (
{checking && (
progress_activity Checking Copilot config...
)} {!checking && ( <> {/* Info */}
info

Writes to chatLanguageModels.json

Reload VS Code after applying for changes to take effect.

{/* API Key */}
{apiKeys.length > 0 ? ( ) : ( {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Model input + Add */}
{/* Model list */} {modelList.length > 0 && (
{modelList.map((id) => (
{id}
))}
)}
setModelInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addModel()} placeholder="provider/model-id" className="min-w-0 px-3 py-2 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />
{message && (
{message.type === "success" ? "check_circle" : "error"} {message.text}
)}
)}
)} setModalOpen(false)} onSelect={(model) => { setModelInput(model.value); setModalOpen(false); }} selectedModel={modelInput} activeProviders={activeProviders} modelAliases={modelAliases} title="Select Model for GitHub Copilot" /> setShowManualConfigModal(false)} title="GitHub Copilot - Manual Configuration" configs={getManualConfigs()} />
); }