feat: v0.4.41 - cli-tools UI redesign + jcode integration
- Add jcode CLI tool (#1047) with auto-configuration - Redesign CLI Tools dashboard: grid 1/2/3 cols + per-tool detail page - Sync DeepSeek TUI card style + resize icon 1024->128 - Add official logos: amp, jcode, qwen - Bump version 0.4.39 -> 0.4.41 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
da00a25eef
commit
2190ff062d
16 files changed
+1202
-549
No files matched your search
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { CardSkeleton } from "@/shared/components";
|
||||
import { CLI_TOOLS } from "@/shared/constants/cliTools";
|
||||
import { getModelsByProviderId, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models";
|
||||
import {
|
||||
ClaudeToolCard, CodexToolCard, DroidToolCard, OpenClawToolCard,
|
||||
HermesToolCard, DefaultToolCard, OpenCodeToolCard, CoworkToolCard,
|
||||
CopilotToolCard, ClineToolCard, KiloToolCard, DeepSeekTuiToolCard,
|
||||
JcodeToolCard,
|
||||
} from "../components";
|
||||
|
||||
const CLOUD_URL = process.env.NEXT_PUBLIC_CLOUD_URL;
|
||||
|
||||
export default function ToolDetailClient({ toolId, machineId }) {
|
||||
const tool = CLI_TOOLS[toolId];
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [modelMappings, setModelMappings] = useState({});
|
||||
const [cloudEnabled, setCloudEnabled] = useState(false);
|
||||
const [tunnelEnabled, setTunnelEnabled] = useState(false);
|
||||
const [tunnelPublicUrl, setTunnelPublicUrl] = useState("");
|
||||
const [tailscaleEnabled, setTailscaleEnabled] = useState(false);
|
||||
const [tailscaleUrl, setTailscaleUrl] = useState("");
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
const [provRes, settingsRes, tunnelRes, keysRes] = await Promise.all([
|
||||
fetch("/api/providers"),
|
||||
fetch("/api/settings"),
|
||||
fetch("/api/tunnel/status"),
|
||||
fetch("/api/keys"),
|
||||
]);
|
||||
if (!mounted) return;
|
||||
if (provRes.ok) {
|
||||
const data = await provRes.json();
|
||||
setConnections(data.connections || []);
|
||||
}
|
||||
if (settingsRes.ok) {
|
||||
const data = await settingsRes.json();
|
||||
setCloudEnabled(data.cloudEnabled || false);
|
||||
}
|
||||
if (tunnelRes.ok) {
|
||||
const data = await tunnelRes.json();
|
||||
setTunnelEnabled(!!(data.tunnel?.enabled || data.tunnel?.settingsEnabled));
|
||||
setTunnelPublicUrl(data.tunnel?.publicUrl || "");
|
||||
setTailscaleEnabled(!!(data.tailscale?.enabled || data.tailscale?.settingsEnabled));
|
||||
setTailscaleUrl(data.tailscale?.tunnelUrl || "");
|
||||
}
|
||||
if (keysRes.ok) {
|
||||
const data = await keysRes.json();
|
||||
setApiKeys(data.keys || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error loading tool data:", error);
|
||||
} finally {
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
const getActiveProviders = () => connections.filter(c => c.isActive !== false);
|
||||
|
||||
const getAllAvailableModels = () => {
|
||||
const activeProviders = getActiveProviders();
|
||||
const models = [];
|
||||
const seenModels = new Set();
|
||||
activeProviders.forEach(conn => {
|
||||
const alias = PROVIDER_ID_TO_ALIAS[conn.provider] || conn.provider;
|
||||
const providerModels = getModelsByProviderId(conn.provider);
|
||||
providerModels.forEach(m => {
|
||||
const modelValue = `${alias}/${m.id}`;
|
||||
if (!seenModels.has(modelValue)) {
|
||||
seenModels.add(modelValue);
|
||||
models.push({ value: modelValue, label: `${alias}/${m.id}`, provider: conn.provider, alias, connectionName: conn.name, modelId: m.id });
|
||||
}
|
||||
});
|
||||
});
|
||||
return models;
|
||||
};
|
||||
|
||||
const handleModelMappingChange = useCallback((tId, alias, target) => {
|
||||
setModelMappings(prev => {
|
||||
if (prev[tId]?.[alias] === target) return prev;
|
||||
return { ...prev, [tId]: { ...prev[tId], [alias]: target } };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (tunnelEnabled && tunnelPublicUrl) return tunnelPublicUrl;
|
||||
if (cloudEnabled && CLOUD_URL) return CLOUD_URL;
|
||||
if (typeof window !== "undefined") return window.location.origin;
|
||||
return "http://localhost:20128";
|
||||
};
|
||||
|
||||
const renderToolCard = () => {
|
||||
const availableModels = getAllAvailableModels();
|
||||
const hasActiveProviders = availableModels.length > 0;
|
||||
const commonProps = {
|
||||
tool,
|
||||
isExpanded: true,
|
||||
onToggle: () => {},
|
||||
baseUrl: getBaseUrl(),
|
||||
apiKeys,
|
||||
tunnelEnabled,
|
||||
tunnelPublicUrl,
|
||||
tailscaleEnabled,
|
||||
tailscaleUrl,
|
||||
};
|
||||
|
||||
switch (toolId) {
|
||||
case "claude":
|
||||
return <ClaudeToolCard {...commonProps} activeProviders={getActiveProviders()} modelMappings={modelMappings[toolId] || {}} onModelMappingChange={(a, t) => handleModelMappingChange(toolId, a, t)} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
case "codex":
|
||||
return <CodexToolCard {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} />;
|
||||
case "opencode":
|
||||
return <OpenCodeToolCard {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} />;
|
||||
case "cowork":
|
||||
return <CoworkToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} cloudUrl={CLOUD_URL} tunnelEnabled={tunnelEnabled} tunnelPublicUrl={tunnelPublicUrl} tailscaleEnabled={tailscaleEnabled} tailscaleUrl={tailscaleUrl} />;
|
||||
case "droid":
|
||||
return <DroidToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
case "openclaw":
|
||||
return <OpenClawToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
case "hermes":
|
||||
return <HermesToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
case "copilot":
|
||||
return <CopilotToolCard {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} />;
|
||||
case "cline":
|
||||
return <ClineToolCard {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} />;
|
||||
case "kilo":
|
||||
return <KiloToolCard {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} />;
|
||||
case "deepseek-tui":
|
||||
return <DeepSeekTuiToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
case "jcode":
|
||||
return <JcodeToolCard {...commonProps} activeProviders={getActiveProviders()} hasActiveProviders={hasActiveProviders} cloudEnabled={cloudEnabled} />;
|
||||
default:
|
||||
return <DefaultToolCard toolId={toolId} {...commonProps} activeProviders={getActiveProviders()} cloudEnabled={cloudEnabled} tunnelEnabled={tunnelEnabled} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:px-0">
|
||||
<Link href="/dashboard/cli-tools" className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary w-fit">
|
||||
<span className="material-symbols-outlined text-[18px]">arrow_back</span>
|
||||
Back to CLI Tools
|
||||
</Link>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-xl font-semibold text-text-main sm:text-2xl">{tool.name}</h1>
|
||||
<p className="text-sm text-text-muted">{tool.description}</p>
|
||||
</div>
|
||||
{loading ? <CardSkeleton /> : renderToolCard()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user