"use client"; import { useState, useEffect, useRef } from "react"; import { Card, Button, ModelSelectModal } from "@/shared/components"; import Image from "next/image"; import BaseUrlSelect from "./BaseUrlSelect"; import ApiKeySelect from "./ApiKeySelect"; import { matchKnownEndpoint } from "./cliEndpointMatch"; const ENDPOINT = "/api/cli-tools/deepseek-tui-settings"; export default function DeepSeekTuiToolCard({ tool, isExpanded, onToggle, baseUrl, hasActiveProviders, apiKeys, activeProviders, cloudEnabled, initialStatus, tunnelEnabled, tunnelPublicUrl, tailscaleEnabled, tailscaleUrl, }) { const [deepseekStatus, setDeepseekStatus] = 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 [selectedModel, setSelectedModel] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [modelAliases, setModelAliases] = useState({}); const [customBaseUrl, setCustomBaseUrl] = useState(""); const hasInitializedModel = useRef(false); const getConfigStatus = () => { if (!deepseekStatus?.installed) return null; const cfg = deepseekStatus.settings; if (!cfg) return "not_configured"; const openaiSection = cfg["providers.openai"]; if (!openaiSection?.base_url) return "not_configured"; if (matchKnownEndpoint(openaiSection.base_url, { tunnelPublicUrl, tailscaleUrl })) return "configured"; return "other"; }; const configStatus = getConfigStatus(); useEffect(() => { if (apiKeys?.length > 0 && !selectedApiKey) { setSelectedApiKey(apiKeys[0].key); } }, [apiKeys, selectedApiKey]); useEffect(() => { if (initialStatus) setDeepseekStatus(initialStatus); }, [initialStatus]); useEffect(() => { if (isExpanded && !deepseekStatus) { checkStatus(); 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 (deepseekStatus?.installed && !hasInitializedModel.current) { hasInitializedModel.current = true; const cfg = deepseekStatus.settings; const openaiSection = cfg?.["providers.openai"]; if (openaiSection?.model) setSelectedModel(openaiSection.model); } }, [deepseekStatus]); const checkStatus = async () => { setChecking(true); try { const res = await fetch(ENDPOINT); const data = await res.json(); setDeepseekStatus(data); } catch (error) { setDeepseekStatus({ installed: false, error: error.message }); } finally { setChecking(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 handleApply = 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(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel, }), }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "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(ENDPOINT, { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset to defaults!" }); checkStatus(); } else { setMessage({ type: "error", text: data.error || "Failed to reset settings" }); } } catch (error) { setMessage({ type: "error", text: error.message }); } finally { setRestoring(false); } }; const handleSelectModel = (model) => { setSelectedModel(model.value); setModalOpen(false); }; const renderIcon = () => { if (tool.image) { return ( {tool.name} { e.target.style.display = "none"; }} /> ); } if (tool.icon) { return {tool.icon}; } return ( {tool.name} { e.target.style.display = "none"; }} /> ); }; const renderStatusBadge = () => { if (!deepseekStatus?.installed) { return ( close Not Installed ); } if (configStatus === "configured") { return ( check_circle Configured ); } if (configStatus === "other") { return ( settings Other Config ); } return ( info Not Configured ); }; return (
{renderIcon()}

{tool.name}

{renderStatusBadge()}

{tool.description}

expand_more
{isExpanded && (
{/* Notes */} {tool.notes && tool.notes.length > 0 && (
{tool.notes.map((note, index) => { const isWarning = note.type === "warning"; const isError = note.type === "error"; let bgClass = "bg-blue-500/10 border-blue-500/30"; let textClass = "text-blue-600 dark:text-blue-400"; let iconClass = "text-blue-500"; let icon = "info"; if (isWarning) { bgClass = "bg-yellow-500/10 border-yellow-500/30"; textClass = "text-yellow-600 dark:text-yellow-400"; iconClass = "text-yellow-500"; icon = "warning"; } else if (isError) { bgClass = "bg-red-500/10 border-red-500/30"; textClass = "text-red-600 dark:text-red-400"; iconClass = "text-red-500"; icon = "error"; } return (
{icon}

{note.text}

); })}
)} {/* Install check */} {!deepseekStatus?.installed && (

DeepSeek TUI is not detected on your system.

Install via npm:

npm install -g deepseek-tui
)} {/* Config section */} {deepseekStatus?.installed && (
{/* Config path */}
folder {deepseekStatus.configPath}
{/* Base URL */}
{/* API Key */}
{/* Model */}
setSelectedModel(e.target.value)} placeholder="ollama/gpt-oss:120b" className="w-full sm:w-auto flex-1 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 && (
{message.text}
)} {/* Actions */}
)}
)} setModalOpen(false)} onSelect={handleSelectModel} selectedModel={selectedModel} activeProviders={activeProviders} title="Select Model" />
); }