This commit is contained in:
2025-09-05 22:16:06 +08:00
parent 6e834a1a7c
commit 71adf8416a
12 changed files with 1068 additions and 84 deletions

View File

@@ -29,6 +29,7 @@ import { useAppLifecycle, useTrackEvent } from "@/hooks";
import { useTranslation } from "@/hooks/useTranslation";
import { WelcomePage } from "@/components/WelcomePage";
import RelayStationManager from "@/components/RelayStationManager";
import { CcrRouterManager } from "@/components/CcrRouterManager";
import i18n from "@/lib/i18n";
type View =
@@ -44,6 +45,7 @@ type View =
| "agent-run-view"
| "mcp"
| "relay-stations"
| "ccr-router"
| "usage-dashboard"
| "project-settings"
| "tabs"; // New view for tab-based interface
@@ -282,6 +284,11 @@ function AppContent() {
<RelayStationManager onBack={() => handleViewChange("welcome")} />
);
case "ccr-router":
return (
<CcrRouterManager onBack={() => handleViewChange("welcome")} />
);
case "cc-agents":
return (
<CCAgents

View File

@@ -0,0 +1,463 @@
import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { ArrowLeft, Play, Square, RotateCcw, ExternalLink, Download, AlertCircle, CheckCircle, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Toast, ToastContainer } from "@/components/ui/toast";
import { ccrApi, type CcrServiceStatus } from "@/lib/api";
import { open } from '@tauri-apps/plugin-shell';
interface CcrRouterManagerProps {
onBack: () => void;
}
export function CcrRouterManager({ onBack }: CcrRouterManagerProps) {
const [serviceStatus, setServiceStatus] = useState<CcrServiceStatus | null>(null);
const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState(false);
const [toast, setToast] = useState<{ message: string; type: "success" | "error" | "info" } | null>(null);
const [configPath, setConfigPath] = useState<string>("");
useEffect(() => {
loadServiceStatus();
loadConfigPath();
}, []);
const loadServiceStatus = async () => {
try {
setLoading(true);
const status = await ccrApi.getServiceStatus();
setServiceStatus(status);
} catch (error) {
console.error("Failed to load CCR service status:", error);
setToast({
message: `加载CCR服务状态失败: ${error}`,
type: "error"
});
} finally {
setLoading(false);
}
};
const loadConfigPath = async () => {
try {
const path = await ccrApi.getConfigPath();
setConfigPath(path);
} catch (error) {
console.error("Failed to get config path:", error);
}
};
const handleStartService = async () => {
try {
setActionLoading(true);
const result = await ccrApi.startService();
setServiceStatus(result.status);
setToast({
message: result.message,
type: "success"
});
} catch (error) {
console.error("Failed to start CCR service:", error);
setToast({
message: `启动CCR服务失败: ${error}`,
type: "error"
});
} finally {
setActionLoading(false);
}
};
const handleStopService = async () => {
try {
setActionLoading(true);
const result = await ccrApi.stopService();
setServiceStatus(result.status);
setToast({
message: result.message,
type: "success"
});
} catch (error) {
console.error("Failed to stop CCR service:", error);
setToast({
message: `停止CCR服务失败: ${error}`,
type: "error"
});
} finally {
setActionLoading(false);
}
};
const handleRestartService = async () => {
try {
setActionLoading(true);
const result = await ccrApi.restartService();
setServiceStatus(result.status);
setToast({
message: result.message,
type: "success"
});
} catch (error) {
console.error("Failed to restart CCR service:", error);
setToast({
message: `重启CCR服务失败: ${error}`,
type: "error"
});
} finally {
setActionLoading(false);
}
};
const handleOpenUI = async () => {
try {
setActionLoading(true);
// 如果服务未运行,先尝试启动
if (!serviceStatus?.is_running) {
setToast({
message: "检测到服务未运行,正在启动...",
type: "info"
});
const startResult = await ccrApi.startService();
setServiceStatus(startResult.status);
if (!startResult.status.is_running) {
throw new Error("服务启动失败");
}
// 等待服务完全启动
await new Promise(resolve => setTimeout(resolve, 3000));
}
await ccrApi.openUI();
setToast({
message: "正在打开CCR UI...",
type: "info"
});
// 刷新服务状态
setTimeout(() => {
loadServiceStatus();
}, 2000);
} catch (error) {
console.error("Failed to open CCR UI:", error);
setToast({
message: `打开CCR UI失败: ${error}`,
type: "error"
});
} finally {
setActionLoading(false);
}
};
const handleOpenInBrowser = async () => {
try {
// 如果服务未运行,先尝试启动
if (!serviceStatus?.is_running) {
setActionLoading(true);
setToast({
message: "检测到服务未运行,正在启动...",
type: "info"
});
const startResult = await ccrApi.startService();
setServiceStatus(startResult.status);
if (!startResult.status.is_running) {
throw new Error("服务启动失败");
}
// 等待服务完全启动
await new Promise(resolve => setTimeout(resolve, 2000));
setActionLoading(false);
}
if (serviceStatus?.endpoint) {
open(`${serviceStatus.endpoint}/ui/`);
setToast({
message: "正在打开CCR管理界面...",
type: "info"
});
}
} catch (error) {
console.error("Failed to open CCR UI in browser:", error);
setToast({
message: `打开管理界面失败: ${error}`,
type: "error"
});
setActionLoading(false);
}
};
const renderServiceStatus = () => {
if (!serviceStatus) return null;
const statusColor = serviceStatus.is_running ? "bg-green-500" : "bg-red-500";
const statusText = serviceStatus.is_running ? "运行中" : "已停止";
return (
<div className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full ${statusColor}`}></div>
<span className="font-medium">{statusText}</span>
{serviceStatus.is_running && serviceStatus.port && (
<Badge variant="secondary"> {serviceStatus.port}</Badge>
)}
</div>
);
};
const renderInstallationStatus = () => {
if (!serviceStatus) return null;
return (
<div className="flex items-center gap-2">
{serviceStatus.has_ccr_binary ? (
<>
<CheckCircle className="w-4 h-4 text-green-500" />
<span className="text-green-600"></span>
{serviceStatus.ccr_version && (
<Badge variant="outline">{serviceStatus.ccr_version}</Badge>
)}
</>
) : (
<>
<AlertCircle className="w-4 h-4 text-red-500" />
<span className="text-red-600"></span>
</>
)}
</div>
);
};
if (loading) {
return (
<div className="flex-1 flex items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
);
}
return (
<div className="flex-1 overflow-y-auto">
<div className="container mx-auto p-6 max-w-4xl">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-6"
>
<div className="flex items-center gap-3 mb-4">
<Button variant="ghost" size="sm" onClick={onBack}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h1 className="text-3xl font-bold tracking-tight">CCR </h1>
<p className="mt-1 text-sm text-muted-foreground">
Claude Code Router
</p>
</div>
</div>
</motion.div>
{/* Service Status Card */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="mb-6"
>
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span></span>
<Button
variant="outline"
size="sm"
onClick={loadServiceStatus}
disabled={loading}
>
</Button>
</CardTitle>
<CardDescription>
CCR
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">:</span>
{renderInstallationStatus()}
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">:</span>
{renderServiceStatus()}
</div>
{serviceStatus?.endpoint && (
<div className="flex items-center justify-between">
<span className="text-sm font-medium">:</span>
<Button
variant="link"
size="sm"
onClick={handleOpenInBrowser}
className="p-0 h-auto"
>
{serviceStatus.endpoint}/ui/
<ExternalLink className="w-3 h-3 ml-1" />
</Button>
</div>
)}
{serviceStatus?.process_id && (
<div className="flex items-center justify-between">
<span className="text-sm font-medium"> ID:</span>
<Badge variant="outline">{serviceStatus.process_id}</Badge>
</div>
)}
{configPath && (
<div className="flex items-center justify-between">
<span className="text-sm font-medium">:</span>
<span className="text-xs text-muted-foreground font-mono">
{configPath}
</span>
</div>
)}
</CardContent>
</Card>
</motion.div>
{/* Control Panel */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mb-6"
>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
CCR
</CardDescription>
</CardHeader>
<CardContent>
{serviceStatus?.has_ccr_binary ? (
<div className="flex gap-3 flex-wrap">
{!serviceStatus.is_running ? (
<Button
onClick={handleStartService}
disabled={actionLoading}
className="gap-2"
>
{actionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Play className="w-4 h-4" />
)}
</Button>
) : (
<Button
onClick={handleStopService}
disabled={actionLoading}
variant="destructive"
className="gap-2"
>
{actionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Square className="w-4 h-4" />
)}
</Button>
)}
<Button
onClick={handleRestartService}
disabled={actionLoading}
variant="outline"
className="gap-2"
>
{actionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<RotateCcw className="w-4 h-4" />
)}
</Button>
<Button
onClick={handleOpenUI}
disabled={actionLoading}
className="gap-2"
>
{actionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<ExternalLink className="w-4 h-4" />
)}
{serviceStatus.is_running ? "打开管理界面" : "启动并打开管理界面"}
</Button>
</div>
) : (
<div className="text-center py-8">
<AlertCircle className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium mb-2">CCR </h3>
<p className="text-muted-foreground mb-4">
Claude Code Router 使
</p>
<Button
onClick={() => open("https://www.npmjs.com/package/@musistudio/claude-code-router")}
className="gap-2"
>
<Download className="w-4 h-4" />
CCR
</Button>
</div>
)}
</CardContent>
</Card>
</motion.div>
{/* Information Card */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Card>
<CardHeader>
<CardTitle> CCR </CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>
Claude Code Router (CCR) Claude Code LLM
</p>
<ul className="list-disc list-inside space-y-1">
<li> LLM OpenRouterDeepSeekGemini </li>
<li></li>
<li>Web UI 便</li>
<li> Anthropic 使 Claude Code</li>
</ul>
</CardContent>
</Card>
</motion.div>
</div>
{/* Toast Container */}
<ToastContainer>
{toast && (
<Toast
message={toast.message}
type={toast.type}
onDismiss={() => setToast(null)}
/>
)}
</ToastContainer>
</div>
);
}

View File

@@ -1,11 +1,11 @@
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Network,
Globe,
Terminal,
Trash2,
Play,
import {
Network,
Globe,
Terminal,
Trash2,
Play,
CheckCircle,
Loader2,
RefreshCw,
@@ -57,7 +57,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
const [copiedServer, setCopiedServer] = useState<string | null>(null);
const [connectedServers] = useState<string[]>([]);
// Analytics tracking
const trackEvent = useTrackEvent();
@@ -103,18 +103,18 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
const handleRemoveServer = async (name: string) => {
try {
setRemovingServer(name);
// Check if server was connected
const wasConnected = connectedServers.includes(name);
await api.mcpRemove(name);
// Track server removal
trackEvent.mcpServerRemoved({
server_name: name,
was_connected: wasConnected
});
onServerRemoved(name);
} catch (error) {
console.error("Failed to remove server:", error);
@@ -131,15 +131,15 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
setTestingServer(name);
const result = await api.mcpTestConnection(name);
const server = servers.find(s => s.name === name);
// Track connection result - result is a string message
trackEvent.mcpServerConnected(name, true, server?.transport || 'unknown');
// TODO: Show result in a toast or modal
console.log("Test result:", result);
} catch (error) {
console.error("Failed to test connection:", error);
console.error("Failed to test.md connection:", error);
trackEvent.mcpConnectionError({
server_name: name,
error_type: 'test_failed',
@@ -202,7 +202,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
const renderServerItem = (server: MCPServer) => {
const isExpanded = expandedServers.has(server.name);
const isCopied = copiedServer === server.name;
return (
<motion.div
key={server.name}
@@ -226,7 +226,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</Badge>
)}
</div>
{server.command && !isExpanded && (
<div className="flex items-center gap-2">
<p className="text-xs text-muted-foreground font-mono truncate pl-9 flex-1" title={server.command}>
@@ -243,7 +243,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</Button>
</div>
)}
{server.transport === "sse" && server.url && !isExpanded && (
<div className="overflow-hidden">
<p className="text-xs text-muted-foreground font-mono truncate pl-9" title={server.url}>
@@ -251,14 +251,14 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</p>
</div>
)}
{Object.keys(server.env).length > 0 && !isExpanded && (
<div className="flex items-center gap-1 text-xs text-muted-foreground pl-9">
<span>{t('mcp.environmentVariablesCount', { count: Object.keys(server.env).length })}</span>
</div>
)}
</div>
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0">
<Button
variant="ghost"
@@ -288,7 +288,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</Button>
</div>
</div>
{/* Expanded Details */}
{isExpanded && (
<motion.div
@@ -328,7 +328,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</p>
</div>
)}
{server.args && server.args.length > 0 && (
<div className="space-y-1">
<p className="text-xs font-medium text-muted-foreground">{t('mcp.arguments')}</p>
@@ -342,7 +342,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</div>
</div>
)}
{server.transport === "sse" && server.url && (
<div className="space-y-1">
<p className="text-xs font-medium text-muted-foreground">{t('mcp.url')}</p>
@@ -351,7 +351,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
</p>
</div>
)}
{Object.keys(server.env).length > 0 && (
<div className="space-y-1">
<p className="text-xs font-medium text-muted-foreground">{t('mcp.environmentVariables')}</p>
@@ -433,4 +433,4 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
)}
</div>
);
};
};

View File

@@ -1,5 +1,5 @@
import { motion } from "framer-motion";
import { Bot, FolderCode, BarChart, ServerCog, FileText, Settings, Network } from "lucide-react";
import { Bot, FolderCode, BarChart, ServerCog, FileText, Settings, Network, Router } from "lucide-react";
import { useTranslation } from "@/hooks/useTranslation";
import { Button } from "@/components/ui/button";
import { ClaudiaLogoMinimal } from "@/components/ClaudiaLogo";
@@ -61,6 +61,15 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
bgColor: "bg-orange-500/10",
view: "mcp"
},
{
id: "ccr-router",
icon: Router,
title: t("welcome.ccrRouter"),
subtitle: t("welcome.ccrRouterDesc"),
color: "text-orange-500",
bgColor: "bg-orange-500/10",
view: "ccr-router"
},
{
id: "claude-md",
icon: FileText,
@@ -147,7 +156,7 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
</div>
{/* Bottom Feature Cards */}
<div className="grid grid-cols-4 gap-6 mb-10">
<div className="grid grid-cols-5 gap-6 mb-10">
{bottomFeatures.map((feature, index) => (
<motion.div
key={feature.id}

View File

@@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
import type { HooksConfiguration } from '@/types/hooks';
/** Process type for tracking in ProcessRegistry */
export type ProcessType =
export type ProcessType =
| { AgentRun: { agent_id: number; agent_name: string } }
| { ClaudeSession: { session_id: string } };
@@ -451,7 +451,7 @@ export interface ImportServerResult {
// ================================
/** 中转站适配器类型 */
export type RelayStationAdapter =
export type RelayStationAdapter =
| 'packycode' // PackyCode 平台(默认)
| 'newapi' // NewAPI 兼容平台
| 'oneapi' // OneAPI 兼容平台
@@ -459,7 +459,7 @@ export type RelayStationAdapter =
| 'custom'; // 自定义简单配置
/** 认证方式 */
export type AuthMethod =
export type AuthMethod =
| 'bearer_token' // Bearer Token 认证(推荐)
| 'api_key' // API Key 认证
| 'custom'; // 自定义认证方式
@@ -560,7 +560,7 @@ export interface TokenPaginationResponse {
// ============= PackyCode Nodes =============
/** PackyCode 节点类型 */
export type NodeType =
export type NodeType =
| 'direct' // 直连节点
| 'backup' // 备用节点
| 'emergency'; // 紧急节点
@@ -678,13 +678,13 @@ export const api = {
try {
const result = await invoke<{ data: ClaudeSettings }>("get_claude_settings");
console.log("Raw result from get_claude_settings:", result);
// The Rust backend returns ClaudeSettings { data: ... }
// We need to extract the data field
if (result && typeof result === 'object' && 'data' in result) {
return result.data;
}
// If the result is already the settings object, return it
return result as ClaudeSettings;
} catch (error) {
@@ -805,7 +805,7 @@ export const api = {
},
// Agent API methods
/**
* Lists all CC agents
* @returns Promise resolving to an array of agents
@@ -830,17 +830,17 @@ export const api = {
* @returns Promise resolving to the created agent
*/
async createAgent(
name: string,
icon: string,
system_prompt: string,
default_task?: string,
name: string,
icon: string,
system_prompt: string,
default_task?: string,
model?: string,
hooks?: string
): Promise<Agent> {
try {
return await invoke<Agent>('create_agent', {
name,
icon,
return await invoke<Agent>('create_agent', {
name,
icon,
systemPrompt: system_prompt,
defaultTask: default_task,
model,
@@ -864,19 +864,19 @@ export const api = {
* @returns Promise resolving to the updated agent
*/
async updateAgent(
id: number,
name: string,
icon: string,
system_prompt: string,
default_task?: string,
id: number,
name: string,
icon: string,
system_prompt: string,
default_task?: string,
model?: string,
hooks?: string
): Promise<Agent> {
try {
return await invoke<Agent>('update_agent', {
id,
name,
icon,
return await invoke<Agent>('update_agent', {
id,
name,
icon,
systemPrompt: system_prompt,
defaultTask: default_task,
model,
@@ -1545,9 +1545,9 @@ export const api = {
* Tracks a batch of messages for a session for checkpointing
*/
trackSessionMessages: (
sessionId: string,
projectId: string,
projectPath: string,
sessionId: string,
projectId: string,
projectPath: string,
messages: string[]
): Promise<void> =>
invoke("track_session_messages", { sessionId, projectId, projectPath, messages }),
@@ -1662,7 +1662,7 @@ export const api = {
try {
return await invoke<string>("mcp_test_connection", { name });
} catch (error) {
console.error("Failed to test MCP connection:", error);
console.error("Failed to test.md MCP connection:", error);
throw error;
}
},
@@ -2289,13 +2289,13 @@ export const api = {
/**
* Tests relay station connection
* @param stationId - The relay station ID
* @returns Promise resolving to connection test result
* @returns Promise resolving to connection test.md result
*/
async relayStationTestConnection(stationId: string): Promise<ConnectionTestResult> {
try {
return await invoke<ConnectionTestResult>("relay_station_test_connection", { stationId });
} catch (error) {
console.error("Failed to test connection:", error);
console.error("Failed to test.md connection:", error);
throw error;
}
},
@@ -2309,9 +2309,9 @@ export const api = {
* @returns Promise resolving to usage logs
*/
async relayStationGetUsageLogs(
stationId: string,
userId: string,
page?: number,
stationId: string,
userId: string,
page?: number,
size?: number
): Promise<any> {
try {
@@ -2330,8 +2330,8 @@ export const api = {
* @returns Promise resolving to token pagination response
*/
async relayStationListTokens(
stationId: string,
page?: number,
stationId: string,
page?: number,
size?: number
): Promise<TokenPaginationResponse> {
try {
@@ -2350,8 +2350,8 @@ export const api = {
* @returns Promise resolving to created token info
*/
async relayStationCreateToken(
stationId: string,
name: string,
stationId: string,
name: string,
quota?: number
): Promise<TokenInfo> {
try {
@@ -2371,9 +2371,9 @@ export const api = {
* @returns Promise resolving to updated token info
*/
async relayStationUpdateToken(
stationId: string,
tokenId: string,
name?: string,
stationId: string,
tokenId: string,
name?: string,
quota?: number
): Promise<TokenInfo> {
try {
@@ -2400,16 +2400,16 @@ export const api = {
},
// ============= PackyCode Nodes =============
/**
* Tests all PackyCode nodes and returns speed test results
* @returns Promise resolving to array of node speed test results
* Tests all PackyCode nodes and returns speed test.md results
* @returns Promise resolving to array of node speed test.md results
*/
async testAllPackycodeNodes(): Promise<NodeSpeedTestResult[]> {
try {
return await invoke<NodeSpeedTestResult[]>("test_all_packycode_nodes");
} catch (error) {
console.error("Failed to test PackyCode nodes:", error);
console.error("Failed to test.md PackyCode nodes:", error);
throw error;
}
},
@@ -2455,7 +2455,7 @@ export const api = {
},
// ============= File System Watching =============
/**
* Starts watching a directory for file system changes
* @param directoryPath - The directory path to watch
@@ -2486,7 +2486,7 @@ export const api = {
},
// ============= Claude Project Directory Watching =============
/**
* Starts watching Claude project directory for the given project path
* @param projectPath - The project path to find the corresponding Claude directory
@@ -2516,7 +2516,7 @@ export const api = {
},
// ============= Terminal API =============
/**
* Creates a new terminal session using Zellij
* @param workingDirectory - The working directory for the terminal session
@@ -2621,3 +2621,117 @@ export const api = {
}
}
};
// CCR (Claude Code Router) Related Interfaces
export interface CcrServiceStatus {
is_running: boolean;
port?: number;
endpoint?: string;
has_ccr_binary: boolean;
ccr_version?: string;
process_id?: number;
}
export interface CcrServiceInfo {
status: CcrServiceStatus;
message: string;
}
// CCR API methods
export const ccrApi = {
/**
* Check if CCR is installed
*/
async checkInstallation(): Promise<boolean> {
try {
return await invoke<boolean>("check_ccr_installation");
} catch (error) {
console.error("Failed to check CCR installation:", error);
throw error;
}
},
/**
* Get CCR version
*/
async getVersion(): Promise<string> {
try {
return await invoke<string>("get_ccr_version");
} catch (error) {
console.error("Failed to get CCR version:", error);
throw error;
}
},
/**
* Get CCR service status
*/
async getServiceStatus(): Promise<CcrServiceStatus> {
try {
return await invoke<CcrServiceStatus>("get_ccr_service_status");
} catch (error) {
console.error("Failed to get CCR service status:", error);
throw error;
}
},
/**
* Start CCR service
*/
async startService(): Promise<CcrServiceInfo> {
try {
return await invoke<CcrServiceInfo>("start_ccr_service");
} catch (error) {
console.error("Failed to start CCR service:", error);
throw error;
}
},
/**
* Stop CCR service
*/
async stopService(): Promise<CcrServiceInfo> {
try {
return await invoke<CcrServiceInfo>("stop_ccr_service");
} catch (error) {
console.error("Failed to stop CCR service:", error);
throw error;
}
},
/**
* Restart CCR service
*/
async restartService(): Promise<CcrServiceInfo> {
try {
return await invoke<CcrServiceInfo>("restart_ccr_service");
} catch (error) {
console.error("Failed to restart CCR service:", error);
throw error;
}
},
/**
* Open CCR UI
*/
async openUI(): Promise<string> {
try {
return await invoke<string>("open_ccr_ui");
} catch (error) {
console.error("Failed to open CCR UI:", error);
throw error;
}
},
/**
* Get CCR config file path
*/
async getConfigPath(): Promise<string> {
try {
return await invoke<string>("get_ccr_config_path");
} catch (error) {
console.error("Failed to get CCR config path:", error);
throw error;
}
}
};

View File

@@ -125,7 +125,9 @@
"claudeMdDesc": "Edit Claude configuration files",
"settings": "Settings",
"settingsDesc": "App settings and configuration",
"quickStartSession": "Quick Start New Session"
"quickStartSession": "Quick Start New Session",
"ccrRouter": "CCR Router",
"ccrRouterDesc": "Claude Code Router configuration management"
},
"projects": {
"title": "Projects",

View File

@@ -122,7 +122,9 @@
"claudeMdDesc": "编辑 Claude 配置文件",
"settings": "设置",
"settingsDesc": "应用设置和配置",
"quickStartSession": "快速开始新会话"
"quickStartSession": "快速开始新会话",
"ccrRouter": "CCR 路由",
"ccrRouterDesc": "Claude Code Router 配置管理"
},
"projects": {
"title": "项目",