"use client"; import { useMemo, useState, useCallback } from "react"; import { Card, Button } from "@/shared/components"; import { useTranslations } from "next-intl"; import { copyToClipboard } from "@/shared/utils/clipboard"; import { buildCustomCliEnvScript, buildCustomCliJsonConfig, normalizeOpenAiBaseUrl, } from "./customCliConfig"; import { DEFAULT_DISPLAY_BASE_URL } from "@/shared/hooks"; interface ModelOption { value: string; label: string; } interface CustomCliMappingRow { id: string; alias: string; model: string; } export default function CustomCliCard({ tool, isExpanded = false, onToggle = () => {}, baseUrl, apiKeys, availableModels = [], cloudEnabled = false, hasActiveProviders = false, }) { const t = useTranslations("cliTools"); const translateOrFallback = useCallback( (key: string, fallback: string, values?: Record) => { try { const translated = t(key, values); return translated === key || translated === `cliTools.${key}` ? fallback : translated; } catch { return fallback; } }, [t] ); const [copiedField, setCopiedField] = useState(null); const [cliName, setCliName] = useState("Custom CLI"); const [defaultModel, setDefaultModel] = useState(""); const [selectedApiKeyId, setSelectedApiKeyId] = useState(() => apiKeys?.length > 0 ? apiKeys[0].id : "" ); const [aliasMappings, setAliasMappings] = useState([]); const effectiveSelectedApiKeyId = selectedApiKeyId || apiKeys?.[0]?.id || ""; const effectiveDefaultModel = defaultModel || availableModels[0]?.value || ""; const selectedKeyObj = apiKeys?.find((key) => key.id === effectiveSelectedApiKeyId); const keyToUse = selectedKeyObj?.key || (!cloudEnabled ? "sk_omniroute" : translateOrFallback("yourApiKeyPlaceholder", "sk-your-omniroute-key")); const baseUrlWithV1 = normalizeOpenAiBaseUrl(baseUrl || DEFAULT_DISPLAY_BASE_URL); const chatCompletionsEndpoint = `${baseUrlWithV1}/chat/completions`; const envScript = useMemo( () => buildCustomCliEnvScript({ cliName, baseUrl: baseUrlWithV1, apiKey: keyToUse, defaultModel: effectiveDefaultModel, aliasMappings, }), [aliasMappings, baseUrlWithV1, cliName, effectiveDefaultModel, keyToUse] ); const jsonConfig = useMemo( () => buildCustomCliJsonConfig({ cliName, baseUrl: baseUrlWithV1, apiKey: keyToUse, defaultModel: effectiveDefaultModel, aliasMappings, }), [aliasMappings, baseUrlWithV1, cliName, effectiveDefaultModel, keyToUse] ); const handleCopy = async (text: string, field: string) => { await copyToClipboard(text); setCopiedField(field); setTimeout(() => setCopiedField(null), 2000); }; const handleAddMapping = () => { setAliasMappings((prev) => [ ...prev, { id: `mapping_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, alias: "", model: effectiveDefaultModel, }, ]); }; const handleUpdateMapping = (id: string, field: "alias" | "model", value: string) => { setAliasMappings((prev) => prev.map((mapping) => (mapping.id === id ? { ...mapping, [field]: value } : mapping)) ); }; const handleRemoveMapping = (id: string) => { setAliasMappings((prev) => prev.filter((mapping) => mapping.id !== id)); }; const codeBlockClass = "rounded-lg border border-border bg-bg-secondary/70 p-4 text-xs font-mono whitespace-pre-wrap break-all"; return (
{tool.icon || "terminal"}

{tool.name}

{translateOrFallback("custom", "Custom")}

{tool.description}

expand_more
{isExpanded && (
tips_and_updates

{translateOrFallback("customCliBuilderTitle", "OpenAI-compatible CLI builder")}

{translateOrFallback( "customCliBuilderDescription", "Generate env vars and JSON snippets for any CLI or SDK that accepts an OpenAI-compatible base URL, API key, and model ID." )}

{!hasActiveProviders && (
warning

{translateOrFallback("noActiveProviders", "No active providers")}

{translateOrFallback( "customCliNoModels", "Connect at least one provider to populate the model selectors." )}

)}
setCliName(e.target.value)} placeholder={translateOrFallback("customCliNamePlaceholder", "e.g. My Team CLI")} className="w-full px-3 py-2 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />

{translateOrFallback( "customCliDefaultModelHelp", "Use any OmniRoute model ID or combo. Most OpenAI-compatible CLIs only need the /v1 base URL plus a model string." )}

{apiKeys && apiKeys.length > 0 ? ( ) : (
{keyToUse}
)}

{translateOrFallback( "customCliKeyHelper", "For local installs OmniRoute can use sk_omniroute. In cloud mode, pick one of your management API keys." )}

{translateOrFallback("customCliAliasMappingsLabel", "Alias mappings")}

{translateOrFallback( "customCliAliasMappingsHelp", "Optional helper aliases for wrapper scripts or config files that want stable shorthand names." )}

{aliasMappings.length === 0 ? (
{translateOrFallback( "customCliNoMappings", "No alias mappings yet. Add one if your wrapper or team scripts use stable short names." )}
) : (
{aliasMappings.map((mapping) => (
handleUpdateMapping(mapping.id, "alias", e.target.value)} placeholder={translateOrFallback( "customCliAliasPlaceholder", "e.g. review" )} className="px-3 py-2 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" />
))}
)}

{translateOrFallback("customCliEndpointHintLabel", "How to wire the endpoint")}

{translateOrFallback( "customCliEndpointHint", "Point any OpenAI-compatible client to the OmniRoute /v1 base URL. The raw chat completions endpoint is {endpoint}. Use the JSON block when the tool wants a provider object, or the env script when it reads OPENAI_* variables.", { endpoint: chatCompletionsEndpoint } )}

{translateOrFallback("customCliEnvBlockTitle", "Env / shell snippet")}

{envScript}

{translateOrFallback("customCliJsonBlockTitle", "Provider JSON block")}

{jsonConfig}
)}
); }