"use client"; import { useState, useEffect } from "react"; import { Card, Button } from "@/shared/components"; import Image from "next/image"; import { useTranslations } from "next-intl"; import { matchesSearch } from "@/shared/utils/turkishText"; /** * GitHub Copilot Configuration Generator * * Generates the chatLanguageModels.json block for VS Code GitHub Copilot * using the Azure vendor pattern as required by Copilot's architecture. * * Feature request: https://github.com/diegosouzapw/OmniRoute/issues/142 */ export default function CopilotToolCard({ tool, isExpanded = false, onToggle = () => {}, baseUrl, apiKeys, activeProviders = [], hasActiveProviders = false, cloudEnabled = false, batchStatus, }) { const t = useTranslations("cliTools"); const [copiedField, setCopiedField] = useState(null); const [selectedModels, setSelectedModels] = useState>(() => { if (typeof window === "undefined") return new Set(); try { const saved = localStorage.getItem("omniroute-copilot-selected-models"); return saved ? new Set(JSON.parse(saved)) : new Set(); } catch { return new Set(); } }); const [selectedApiKeyId, setSelectedApiKeyId] = useState(() => { if (typeof window !== "undefined") { const savedKey = localStorage.getItem("omniroute-cli-key-copilot"); if (savedKey && apiKeys?.some((k: any) => k.id === savedKey)) return savedKey; } return apiKeys?.length > 0 ? apiKeys[0].id : ""; }); const [maxInputTokens, setMaxInputTokens] = useState(128000); const [maxOutputTokens, setMaxOutputTokens] = useState(16000); const [toolCalling, setToolCalling] = useState(true); const [vision, setVision] = useState(false); const [allModels, setAllModels] = useState>([]); const [modelsLoaded, setModelsLoaded] = useState(false); const [searchFilter, setSearchFilter] = useState(""); // Fetch ALL models dynamically from /v1/models (includes combos, custom, aliased) // Per @alpgul feedback: /api/models/alias doesn't include combo definitions useEffect(() => { if (!isExpanded || modelsLoaded) return; let cancelled = false; fetch("/v1/models") .then((res) => res.json()) .then((data) => { if (cancelled) return; const modelList = (data.data || []) .filter((m: any) => m && !m.type && !m.parent && m.id) // Only chat models with valid IDs .map((m: any) => ({ value: m.id, label: m.id, })); setAllModels(modelList); setModelsLoaded(true); }) .catch(() => { if (!cancelled) setModelsLoaded(true); }); return () => { cancelled = true; }; }, [isExpanded, modelsLoaded]); // Filter models by search const availableModels = searchFilter ? allModels.filter((m) => matchesSearch(m.label, searchFilter)) : allModels; // Persist selection useEffect(() => { if (selectedModels.size > 0) { localStorage.setItem( "omniroute-copilot-selected-models", JSON.stringify([...selectedModels]) ); } }, [selectedModels]); const toggleModel = (modelValue: string) => { setSelectedModels((prev) => { const next = new Set(prev); if (next.has(modelValue)) { next.delete(modelValue); } else { next.add(modelValue); } return next; }); }; const selectAll = () => { setSelectedModels(new Set(allModels.map((m) => m.value))); }; const deselectAll = () => { setSelectedModels(new Set()); }; const getBaseUrlForConfig = () => { const url = baseUrl; return `${url}/v1/chat/completions`; }; // Generate the Copilot chatLanguageModels.json config const generateConfig = () => { const models = [...selectedModels].map((modelId) => ({ id: modelId, name: modelId, url: `${getBaseUrlForConfig()}#models.ai.azure.com`, toolCalling, vision, maxInputTokens, maxOutputTokens, })); const responseModels = [...selectedModels].map((modelId) => ({ id: modelId, name: modelId, url: `${baseUrl}/v1/responses#models.ai.azure.com`, supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh"], zeroDataRetentionEnabled: true, toolCalling, vision, maxInputTokens, maxOutputTokens, })); const config = { name: "OmniRoute", vendor: "azure", apiKey: `\${input:chat.lm.secret.omniroute}`, models, }; const responsesConfig = { name: "OmniRoute-responses", vendor: "azure", apiKey: `\${input:chat.lm.secret.omniroute}`, models: responseModels, }; return [config, responsesConfig].map((entry) => JSON.stringify(entry, null, 2)).join(",\n"); }; const handleCopy = async (text: string, field: string) => { await navigator.clipboard.writeText(text); setCopiedField(field); setTimeout(() => setCopiedField(null), 2000); }; const handleApiKeyChange = (value: string) => { setSelectedApiKeyId(value); if (value) localStorage.setItem("omniroute-cli-key-copilot", value); }; return ( {/* Header */}
{tool.name} { (e.currentTarget as HTMLElement).style.display = "none"; }} />

{tool.name}

{t("guide")}

{tool.description}

expand_more
{/* Expanded content */} {isExpanded && (
{/* Info box */}
info

{t("copilotConfigGenerator")}

Generates the{" "} chatLanguageModels.json {" "} block for VS Code GitHub Copilot using the Azure vendor pattern. Select the models you want, then copy the JSON into your config file.

{/* Version compatibility warning */}
warning

This configuration uses the Azure vendor workaround for custom model lists. Tested with VS Code ≥ 1.109 and{" "} GitHub Copilot Chat ≥ v0.37. Future extension updates may change this behavior.

{/* Step 2: API Key (if cloud enabled) */} {cloudEnabled && apiKeys?.length > 0 && (
1
{t("copilotApiKey")}
)} {/* Step 3: Model Selection */}
{cloudEnabled && apiKeys?.length > 0 ? "2" : "1"}
Select Models ({selectedModels.size}/{availableModels.length})
{/* Search filter */}
setSearchFilter(e.target.value)} placeholder={t("copilotFilterModelsPlaceholder")} className="w-full px-3 py-1.5 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />
{!modelsLoaded && allModels.length === 0 ? (
progress_activity Loading models...
) : availableModels.length === 0 && allModels.length === 0 ? (
warning

{t("noActiveProviders")}

) : (
{availableModels.map((model) => ( ))}
)}
{/* Step 4: Advanced options (collapsible) */}
chevron_right Advanced Options
setMaxInputTokens(Number(e.target.value) || 128000)} className="w-full px-3 py-1.5 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />
setMaxOutputTokens(Number(e.target.value) || 16000)} className="w-full px-3 py-1.5 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />
{/* Step 5: Generated config */} {selectedModels.size > 0 && (
{cloudEnabled && apiKeys?.length > 0 ? "3" : "2"}
Copy Config ({selectedModels.size} model{selectedModels.size !== 1 ? "s" : ""} )
                  
                    {generateConfig()}
                  
                
{/* Usage instructions */}

{t("copilotPasteInto")} ~/.config/Code/User/chatLanguageModels.json

Then reload VS Code and set the API key in the input prompt.

)}
)}
); }