修复缺失的汉化
Some checks are pending
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Linux os:ubuntu-latest rust-target:x86_64-unknown-linux-gnu]) (push) Waiting to run
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Windows os:windows-latest rust-target:x86_64-pc-windows-msvc]) (push) Waiting to run
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:macOS os:macos-latest rust-target:x86_64-apple-darwin]) (push) Waiting to run
Build Test / Build Test Summary (push) Blocked by required conditions
Some checks are pending
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Linux os:ubuntu-latest rust-target:x86_64-unknown-linux-gnu]) (push) Waiting to run
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Windows os:windows-latest rust-target:x86_64-pc-windows-msvc]) (push) Waiting to run
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:macOS os:macos-latest rust-target:x86_64-apple-darwin]) (push) Waiting to run
Build Test / Build Test Summary (push) Blocked by required conditions
This commit is contained in:
@@ -25,6 +25,7 @@ import { StreamMessage } from "./StreamMessage";
|
||||
import { ExecutionControlBar } from "./ExecutionControlBar";
|
||||
import { ErrorBoundary } from "./ErrorBoundary";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface AgentExecutionProps {
|
||||
/**
|
||||
@@ -69,6 +70,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [projectPath, setProjectPath] = useState("");
|
||||
const [task, setTask] = useState(agent.default_task || "");
|
||||
const [model, setModel] = useState(agent.model || "sonnet");
|
||||
@@ -251,7 +253,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "选择项目目录"
|
||||
title: t('agentExecution.selectProjectDirectory')
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
@@ -262,7 +264,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
console.error("Failed to select directory:", err);
|
||||
// More detailed error logging
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
setError(`选择目录失败: ${errorMessage}`);
|
||||
setError(`${t('agentExecution.selectDirectoryFailed')}: ${errorMessage}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -306,14 +308,14 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
setIsRunning(false);
|
||||
setExecutionStartTime(null);
|
||||
if (!event.payload) {
|
||||
setError("智能体执行失败");
|
||||
setError(t('agentExecution.executionFailed'));
|
||||
}
|
||||
});
|
||||
|
||||
const cancelUnlisten = await listen<boolean>(`agent-cancelled:${executionRunId}`, () => {
|
||||
setIsRunning(false);
|
||||
setExecutionStartTime(null);
|
||||
setError("智能体执行已取消");
|
||||
setError(t('agentExecution.executionCancelled'));
|
||||
});
|
||||
|
||||
unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
|
||||
@@ -327,7 +329,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
type: "result",
|
||||
subtype: "error",
|
||||
is_error: true,
|
||||
result: `智能体执行失败: ${err instanceof Error ? err.message : '未知错误'}`,
|
||||
result: `${t('agentExecution.executionFailed')}: ${err instanceof Error ? err.message : t('common.unknown')}`,
|
||||
duration_ms: 0,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
@@ -366,7 +368,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
type: "result",
|
||||
subtype: "error",
|
||||
is_error: true,
|
||||
result: "执行已被用户停止",
|
||||
result: t('agentExecution.executionStopped'),
|
||||
duration_ms: elapsedTime * 1000,
|
||||
usage: {
|
||||
input_tokens: totalTokens,
|
||||
@@ -384,7 +386,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
type: "result",
|
||||
subtype: "error",
|
||||
is_error: true,
|
||||
result: `停止执行失败: ${err instanceof Error ? err.message : '未知错误'}`,
|
||||
result: `${t('agentExecution.stopFailed')}: ${err instanceof Error ? err.message : t('common.unknown')}`,
|
||||
duration_ms: elapsedTime * 1000,
|
||||
usage: {
|
||||
input_tokens: totalTokens,
|
||||
@@ -398,7 +400,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
if (isRunning) {
|
||||
// Show confirmation dialog before navigating away during execution
|
||||
const shouldLeave = window.confirm(
|
||||
"智能体正在运行中。如果您离开此页面,智能体将继续在后台运行。您可以在 CC Agents 的 '运行中的会话' 标签页中查看运行状态。\n\n您要继续吗?"
|
||||
t('agentExecution.navigationWarning')
|
||||
);
|
||||
if (!shouldLeave) {
|
||||
return;
|
||||
@@ -520,12 +522,12 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
{isRunning && (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-xs text-green-600 font-medium">运行中</span>
|
||||
<span className="text-xs text-green-600 font-medium">{t('agentExecution.running')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isRunning ? "点击返回主菜单 - 在 CC Agents > 运行中的会话 中查看" : "执行 CC 智能体"}
|
||||
{isRunning ? t('agentExecution.output') + " - " + t('agentExecution.running') : t('agentExecution.title')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -541,7 +543,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
全屏
|
||||
{t('agentExecution.fullscreen')}
|
||||
</Button>
|
||||
<Popover
|
||||
trigger={
|
||||
@@ -551,7 +553,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
复制输出
|
||||
{t('agentExecution.copyOutput')}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
}
|
||||
@@ -563,7 +565,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="w-full justify-start"
|
||||
onClick={handleCopyAsJsonl}
|
||||
>
|
||||
复制为 JSONL
|
||||
{t('agentExecution.copyAsJsonl')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -571,7 +573,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="w-full justify-start"
|
||||
onClick={handleCopyAsMarkdown}
|
||||
>
|
||||
复制为 Markdown
|
||||
{t('agentExecution.copyAsMarkdown')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -603,12 +605,12 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
|
||||
{/* Project Path */}
|
||||
<div className="space-y-2">
|
||||
<Label>项目路径</Label>
|
||||
<Label>{t('agentExecution.projectPath')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={projectPath}
|
||||
onChange={(e) => setProjectPath(e.target.value)}
|
||||
placeholder="选择或输入项目路径"
|
||||
placeholder={t('agentExecution.selectProjectPath')}
|
||||
disabled={isRunning}
|
||||
className="flex-1"
|
||||
/>
|
||||
@@ -625,7 +627,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label>模型</Label>
|
||||
<Label>{t('models.model')}</Label>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -683,12 +685,12 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
|
||||
{/* Task Input */}
|
||||
<div className="space-y-2">
|
||||
<Label>任务</Label>
|
||||
<Label>{t('agentExecution.task')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={task}
|
||||
onChange={(e) => setTask(e.target.value)}
|
||||
placeholder="输入智能体的任务"
|
||||
placeholder={t('agentExecution.enterTask')}
|
||||
disabled={isRunning}
|
||||
className="flex-1"
|
||||
onKeyPress={(e) => {
|
||||
@@ -705,12 +707,12 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
{isRunning ? (
|
||||
<>
|
||||
<StopCircle className="mr-2 h-4 w-4" />
|
||||
停止
|
||||
{t('agentExecution.stop')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
执行
|
||||
{t('agentExecution.execute')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -741,9 +743,9 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
{messages.length === 0 && !isRunning && (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<Terminal className="h-16 w-16 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">准备执行</h3>
|
||||
<h3 className="text-lg font-medium mb-2">{t('agentExecution.readyToExecute')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
选择项目路径并输入任务以运行智能体
|
||||
{t('agentExecution.selectProject')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -752,7 +754,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="flex items-center gap-3">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
<span className="text-sm text-muted-foreground">初始化智能体中...</span>
|
||||
<span className="text-sm text-muted-foreground">{t('agentExecution.initializing')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -806,11 +808,11 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<div className="flex items-center gap-2">
|
||||
{renderIcon()}
|
||||
<h2 className="text-lg font-semibold">{agent.name} - 输出</h2>
|
||||
<h2 className="text-lg font-semibold">{agent.name} - {t('agentExecution.output')}</h2>
|
||||
{isRunning && (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<span className="text-xs text-green-600 font-medium">运行中</span>
|
||||
<span className="text-xs text-green-600 font-medium">{t('agentExecution.running')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -823,7 +825,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
复制输出
|
||||
{t('agentExecution.copyOutput')}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
}
|
||||
@@ -835,7 +837,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="w-full justify-start"
|
||||
onClick={handleCopyAsJsonl}
|
||||
>
|
||||
复制为 JSONL
|
||||
{t('agentExecution.copyAsJsonl')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -843,7 +845,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="w-full justify-start"
|
||||
onClick={handleCopyAsMarkdown}
|
||||
>
|
||||
复制为 Markdown
|
||||
{t('agentExecution.copyAsMarkdown')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -858,7 +860,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
关闭
|
||||
{t('agentExecution.close')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -883,9 +885,9 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
{messages.length === 0 && !isRunning && (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<Terminal className="h-16 w-16 text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">准备执行</h3>
|
||||
<h3 className="text-lg font-medium mb-2">{t('agentExecution.readyToExecute')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
选择项目路径并输入任务以运行智能体
|
||||
{t('agentExecution.selectProject')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -894,7 +896,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="flex items-center gap-3">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
<span className="text-sm text-muted-foreground">初始化智能体中...</span>
|
||||
<span className="text-sm text-muted-foreground">{t('agentExecution.initializing')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ExternalLink, FileQuestion, Terminal, AlertCircle, Loader2 } from "lucide-react";
|
||||
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ClaudeBinaryDialogProps {
|
||||
open: boolean;
|
||||
@@ -13,6 +14,7 @@ interface ClaudeBinaryDialogProps {
|
||||
}
|
||||
|
||||
export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ClaudeBinaryDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [hasInstallations, setHasInstallations] = useState(true);
|
||||
@@ -39,7 +41,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedInstallation) {
|
||||
onError("请选择一个 Claude 安装");
|
||||
onError(t('claudeBinary.selectInstallation'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +52,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to save Claude binary path:", error);
|
||||
onError(error instanceof Error ? error.message : "保存 Claude 二进制路径失败");
|
||||
onError(error instanceof Error ? error.message : t('claudeBinary.saveFailed'));
|
||||
} finally {
|
||||
setIsValidating(false);
|
||||
}
|
||||
@@ -62,29 +64,27 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileQuestion className="w-5 h-5" />
|
||||
选择 Claude Code 安装
|
||||
{t('claudeBinary.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-3 mt-4">
|
||||
{checkingInstallations ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">正在搜索 Claude 安装...</span>
|
||||
<span className="ml-2 text-sm text-muted-foreground">{t('claudeBinary.searching')}</span>
|
||||
</div>
|
||||
) : hasInstallations ? (
|
||||
<p>
|
||||
在您的系统中发现了多个 Claude Code 安装。
|
||||
请选择您想要使用的版本。
|
||||
{t('claudeBinary.multipleFound')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
在常见安装位置未找到 Claude Code。
|
||||
请安装 Claude Code 以继续。
|
||||
{t('claudeBinary.notFound')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<AlertCircle className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">搜索位置:</span> PATH, /usr/local/bin,
|
||||
<span className="font-medium">{t('claudeBinary.searchedLocations')}</span> PATH, /usr/local/bin,
|
||||
/opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin
|
||||
</p>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<Terminal className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">提示:</span> 您可以使用以下命令安装 Claude Code{" "}
|
||||
<span className="font-medium">{t('claudeBinary.tip')}</span> 您可以使用以下命令安装 Claude Code{" "}
|
||||
<code className="px-1 py-0.5 bg-black/10 dark:bg-white/10 rounded">npm install -g @claude</code>
|
||||
</p>
|
||||
</div>
|
||||
@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
className="mr-auto"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
安装指南
|
||||
{t('claudeBinary.installationGuide')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isValidating}
|
||||
>
|
||||
取消
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isValidating || !selectedInstallation || !hasInstallations}
|
||||
>
|
||||
{isValidating ? "验证中..." : hasInstallations ? "保存选择" : "未找到安装"}
|
||||
{isValidating ? t('claudeBinary.validating') : hasInstallations ? t('claudeBinary.saveSelection') : t('claudeBinary.noInstallations')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
@@ -32,6 +32,7 @@ import { SplitPane } from "@/components/ui/split-pane";
|
||||
import { WebviewPreview } from "./WebviewPreview";
|
||||
import type { ClaudeStreamMessage } from "./AgentExecution";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ClaudeCodeSessionProps {
|
||||
/**
|
||||
@@ -69,6 +70,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className,
|
||||
onStreamingChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [projectPath, setProjectPath] = useState(initialProjectPath || session?.project_path || "");
|
||||
const [messages, setMessages] = useState<ClaudeStreamMessage[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -275,7 +277,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
setIsFirstPrompt(false);
|
||||
} catch (err) {
|
||||
console.error("Failed to load session history:", err);
|
||||
setError("加载会话历史失败");
|
||||
setError(t('sessions.loadHistoryFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -377,7 +379,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "选择项目目录"
|
||||
title: t('sessions.selectProjectDirectory')
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
@@ -387,7 +389,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
} catch (err) {
|
||||
console.error("Failed to select directory:", err);
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
setError(`选择目录失败: ${errorMessage}`);
|
||||
setError(t('sessions.selectDirectoryFailed', { error: errorMessage }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -395,7 +397,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
console.log('[ClaudeCodeSession] handleSendPrompt called with:', { prompt, model, projectPath, claudeSessionId, effectiveSession });
|
||||
|
||||
if (!projectPath) {
|
||||
setError("请先选择项目目录");
|
||||
setError(t('sessions.selectProjectFirst'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,7 +599,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to send prompt:", err);
|
||||
setError("发送提示失败");
|
||||
setError(t('sessions.sendPromptFailed'));
|
||||
setIsLoading(false);
|
||||
hasActiveSessionRef.current = false;
|
||||
}
|
||||
@@ -711,7 +713,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
const cancelMessage: ClaudeStreamMessage = {
|
||||
type: "system",
|
||||
subtype: "info",
|
||||
result: "会话已被用户取消",
|
||||
result: t('sessions.sessionCancelled'),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
setMessages(prev => [...prev, cancelMessage]);
|
||||
@@ -723,7 +725,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
const errorMessage: ClaudeStreamMessage = {
|
||||
type: "system",
|
||||
subtype: "error",
|
||||
result: `取消执行失败: ${err instanceof Error ? err.message : '未知错误'}. 该进程可能仍在后台运行。`,
|
||||
result: t('sessions.cancelSessionFailed', { error: err instanceof Error ? err.message : t('common.unknown') }),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
@@ -772,7 +774,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
setForkSessionName("");
|
||||
} catch (err) {
|
||||
console.error("Failed to fork checkpoint:", err);
|
||||
setError("分叉检查点失败");
|
||||
setError(t('sessions.forkFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -904,7 +906,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className="p-4 border-b border-border flex-shrink-0"
|
||||
>
|
||||
<Label htmlFor="project-path" className="text-sm font-medium">
|
||||
项目目录
|
||||
{t('sessions.projectDirectory')}
|
||||
</Label>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Input
|
||||
@@ -973,9 +975,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<Terminal className="h-5 w-5" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Claude Code 会话</h2>
|
||||
<h2 className="text-lg font-semibold">{t('sessions.claudeCodeSession')}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{session ? `恢复会话 ${session.id.slice(0, 8)}...` : '交互式会话'}
|
||||
{session ? t('sessions.resumingSession', { sessionId: session.id.slice(0, 8) }) : t('sessions.interactiveSession')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -991,7 +993,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
设置
|
||||
{t('common.settings')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -1000,7 +1002,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
时间线
|
||||
{t('sessions.timeline')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -1023,13 +1025,13 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
{showPreview ? "关闭预览" : "预览"}
|
||||
{showPreview ? t('sessions.closePreview') : t('sessions.preview')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{showPreview
|
||||
? "关闭预览面板"
|
||||
: "打开浏览器预览以测试您的Web应用程序"
|
||||
? t('sessions.closePreviewTooltip')
|
||||
: t('sessions.openPreviewTooltip')
|
||||
}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -1044,7 +1046,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
复制输出
|
||||
{t('sessions.copyOutput')}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
}
|
||||
@@ -1056,7 +1058,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
onClick={handleCopyAsMarkdown}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
复制为Markdown
|
||||
{t('sessions.copyAsMarkdown')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -1064,7 +1066,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
onClick={handleCopyAsJsonl}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
复制为JSONL
|
||||
{t('sessions.copyAsJsonl')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -1115,7 +1117,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rotating-symbol text-primary text-2xl" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{session ? "正在加载会话历史..." : "正在初始化Claude Code..."}
|
||||
{session ? t('sessions.loadingHistory') : t('sessions.initializingClaude')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1138,7 +1140,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<div className="bg-background/95 backdrop-blur-md border rounded-lg shadow-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-1">
|
||||
队列提示 ({queuedPrompts.length})
|
||||
{t('sessions.queuedPrompts', { count: queuedPrompts.length })}
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={() => setQueuedPromptsCollapsed(prev => !prev)}>
|
||||
{queuedPromptsCollapsed ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
||||
@@ -1214,7 +1216,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 hover:bg-accent rounded-none"
|
||||
title="滚动到顶部"
|
||||
title={t('sessions.scrollToTop')}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -1236,7 +1238,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 hover:bg-accent rounded-none"
|
||||
title="滚动到底部"
|
||||
title={t('sessions.scrollToBottom')}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -1272,7 +1274,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<Hash className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="font-mono">{totalTokens.toLocaleString()}</span>
|
||||
<span className="text-muted-foreground">标记</span>
|
||||
<span className="text-muted-foreground">{t('sessions.tokens')}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
@@ -1294,7 +1296,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Timeline Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h3 className="text-lg font-semibold">会话时间线</h3>
|
||||
<h3 className="text-lg font-semibold">{t('sessions.sessionTimeline')}</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -1327,18 +1329,18 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
<Dialog open={showForkDialog} onOpenChange={setShowForkDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>分叉会话</DialogTitle>
|
||||
<DialogTitle>{t('sessions.forkSession')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
从选定的检查点创建新的会话分支。
|
||||
{t('sessions.createBranch')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fork-name">新会话名称</Label>
|
||||
<Label htmlFor="fork-name">{t('sessions.newSessionName')}</Label>
|
||||
<Input
|
||||
id="fork-name"
|
||||
placeholder="例如:备用方案"
|
||||
placeholder={t('sessions.sessionNamePlaceholder')}
|
||||
value={forkSessionName}
|
||||
onChange={(e) => setForkSessionName(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
@@ -1356,13 +1358,13 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
onClick={() => setShowForkDialog(false)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
取消
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmFork}
|
||||
disabled={isLoading || !forkSessionName.trim()}
|
||||
>
|
||||
创建分叉
|
||||
{t('sessions.createFork')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
@@ -10,6 +10,7 @@ import { cn } from "@/lib/utils";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import { type AgentIconName } from "./CCAgents";
|
||||
import { IconPicker, ICON_MAP } from "./IconPicker";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface CreateAgentProps {
|
||||
/**
|
||||
@@ -42,6 +43,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
onAgentCreated,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState(agent?.name || "");
|
||||
const [selectedIcon, setSelectedIcon] = useState<AgentIconName>((agent?.icon as AgentIconName) || "bot");
|
||||
const [systemPrompt, setSystemPrompt] = useState(agent?.system_prompt || "");
|
||||
@@ -56,12 +58,12 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name.trim()) {
|
||||
setError("智能体名称为必填项");
|
||||
setError(t('agents.nameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!systemPrompt.trim()) {
|
||||
setError("系统提示词为必填项");
|
||||
setError(t('agents.systemPromptRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,9 +93,9 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
onAgentCreated();
|
||||
} catch (err) {
|
||||
console.error("Failed to save agent:", err);
|
||||
setError(isEditMode ? "更新智能体失败" : "创建智能体失败");
|
||||
setError(isEditMode ? t('agents.updateFailed') : t('agents.createFailed'));
|
||||
setToast({
|
||||
message: isEditMode ? "更新智能体失败" : "创建智能体失败",
|
||||
message: isEditMode ? t('agents.updateFailed') : t('agents.createFailed'),
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
@@ -107,7 +109,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
systemPrompt !== (agent?.system_prompt || "") ||
|
||||
defaultTask !== (agent?.default_task || "") ||
|
||||
model !== (agent?.model || "sonnet")) &&
|
||||
!confirm("您有未保存的更改。您确定要离开吗?")) {
|
||||
!confirm(t('agents.unsavedChanges'))) {
|
||||
return;
|
||||
}
|
||||
onBack();
|
||||
@@ -134,10 +136,10 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</Button>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{isEditMode ? "编辑 CC 智能体" : "创建 CC 智能体"}
|
||||
{isEditMode ? t('agents.editAgent') : t('agents.createAgent')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isEditMode ? "更新您的 Claude Code 智能体" : "创建一个新的 Claude Code 智能体"}
|
||||
{isEditMode ? t('agents.updateAgentDesc') : t('agents.createAgentDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,7 +154,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "保存中..." : "保存"}
|
||||
{saving ? t('common.saving') : t('common.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
@@ -178,24 +180,24 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
{/* Basic Information */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-4">基本信息</h3>
|
||||
<h3 className="text-sm font-medium mb-4">{t('agents.basicInformation')}</h3>
|
||||
</div>
|
||||
|
||||
{/* Name and Icon */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">智能体名称</Label>
|
||||
<Label htmlFor="name">{t('agents.agentName')}</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="例如:代码助手"
|
||||
placeholder={t('agents.agentNamePlaceholder')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>智能体图标</Label>
|
||||
<Label>{t('agents.agentIcon')}</Label>
|
||||
<div
|
||||
onClick={() => setShowIconPicker(true)}
|
||||
className="h-10 px-3 py-2 bg-background border border-input rounded-md cursor-pointer hover:bg-accent hover:text-accent-foreground transition-colors flex items-center justify-between"
|
||||
@@ -218,7 +220,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label>模型</Label>
|
||||
<Label>{t('agents.model')}</Label>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -242,7 +244,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Sonnet</div>
|
||||
<div className="text-xs opacity-80">速度快,适用于大多数任务</div>
|
||||
<div className="text-xs opacity-80">{t('models.sonnetDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -269,7 +271,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Opus</div>
|
||||
<div className="text-xs opacity-80">功能更强,适用于复杂任务</div>
|
||||
<div className="text-xs opacity-80">{t('models.opusDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -278,17 +280,17 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* Default Task */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="default-task">默认任务(可选)</Label>
|
||||
<Label htmlFor="default-task">{t('agents.defaultTask')}</Label>
|
||||
<Input
|
||||
id="default-task"
|
||||
type="text"
|
||||
placeholder="例如:检查代码安全问题"
|
||||
placeholder={t('agents.defaultTaskPlaceholder')}
|
||||
value={defaultTask}
|
||||
onChange={(e) => setDefaultTask(e.target.value)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
这将作为执行智能体时的默认任务占位符
|
||||
{t('agents.placeholderTask')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -296,9 +298,9 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* System Prompt Editor */}
|
||||
<div className="space-y-2">
|
||||
<Label>系统提示词</Label>
|
||||
<Label>{t('agents.systemPrompt')}</Label>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
定义您的 CC 智能体的行为和能力
|
||||
{t('agents.definePrompt')}
|
||||
</p>
|
||||
<div className="rounded-lg border border-border overflow-hidden shadow-sm" data-color-mode="dark">
|
||||
<MDEditor
|
||||
|
@@ -19,6 +19,7 @@ import { FilePicker } from "./FilePicker";
|
||||
import { ImagePreview } from "./ImagePreview";
|
||||
import { type FileEntry } from "@/lib/api";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface FloatingPromptInputProps {
|
||||
/**
|
||||
@@ -71,46 +72,6 @@ type ThinkingModeConfig = {
|
||||
phrase?: string; // The phrase to append
|
||||
};
|
||||
|
||||
const THINKING_MODES: ThinkingModeConfig[] = [
|
||||
{
|
||||
id: "auto",
|
||||
name: "自动",
|
||||
description: "让 Claude 决定",
|
||||
level: 0
|
||||
},
|
||||
{
|
||||
id: "think",
|
||||
name: "基础思考",
|
||||
description: "基本推理",
|
||||
level: 1,
|
||||
phrase: "think"
|
||||
},
|
||||
{
|
||||
id: "think_hard",
|
||||
name: "深度思考",
|
||||
description: "更深入的分析",
|
||||
level: 2,
|
||||
phrase: "think hard"
|
||||
},
|
||||
{
|
||||
id: "think_harder",
|
||||
name: "专业思考",
|
||||
description: "广泛的推理",
|
||||
level: 3,
|
||||
phrase: "think harder"
|
||||
},
|
||||
{
|
||||
id: "ultrathink",
|
||||
name: "超级思考",
|
||||
description: "最大计算能力",
|
||||
level: 4,
|
||||
phrase: "ultrathink"
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
|
||||
*/
|
||||
const ThinkingModeIndicator: React.FC<{ level: number }> = ({ level }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-0.5">
|
||||
@@ -134,32 +95,6 @@ type Model = {
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
const MODELS: Model[] = [
|
||||
{
|
||||
id: "sonnet",
|
||||
name: "Claude 4 Sonnet",
|
||||
description: "更快速,适用于大多数任务",
|
||||
icon: <Zap className="h-4 w-4" />
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Claude 4 Opus",
|
||||
description: "更强大,适用于复杂任务",
|
||||
icon: <Sparkles className="h-4 w-4" />
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* FloatingPromptInput component - Fixed position prompt input with model picker
|
||||
*
|
||||
* @example
|
||||
* const promptRef = useRef<FloatingPromptInputRef>(null);
|
||||
* <FloatingPromptInput
|
||||
* ref={promptRef}
|
||||
* onSend={(prompt, model) => console.log('Send:', prompt, model)}
|
||||
* isLoading={false}
|
||||
* />
|
||||
*/
|
||||
const FloatingPromptInputInner = (
|
||||
{
|
||||
onSend,
|
||||
@@ -172,6 +107,7 @@ const FloatingPromptInputInner = (
|
||||
}: FloatingPromptInputProps,
|
||||
ref: React.Ref<FloatingPromptInputRef>,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
||||
const [selectedThinkingMode, setSelectedThinkingMode] = useState<ThinkingMode>("auto");
|
||||
@@ -184,6 +120,58 @@ const FloatingPromptInputInner = (
|
||||
const [embeddedImages, setEmbeddedImages] = useState<string[]>([]);
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
|
||||
const THINKING_MODES: ThinkingModeConfig[] = [
|
||||
{
|
||||
id: "auto",
|
||||
name: t('thinking.auto'),
|
||||
description: t('thinking.autoDesc'),
|
||||
level: 0
|
||||
},
|
||||
{
|
||||
id: "think",
|
||||
name: t('thinking.think'),
|
||||
description: t('thinking.thinkDesc'),
|
||||
level: 1,
|
||||
phrase: "think"
|
||||
},
|
||||
{
|
||||
id: "think_hard",
|
||||
name: t('thinking.thinkHard'),
|
||||
description: t('thinking.thinkHardDesc'),
|
||||
level: 2,
|
||||
phrase: "think hard"
|
||||
},
|
||||
{
|
||||
id: "think_harder",
|
||||
name: t('thinking.thinkHarder'),
|
||||
description: t('thinking.thinkHarderDesc'),
|
||||
level: 3,
|
||||
phrase: "think harder"
|
||||
},
|
||||
{
|
||||
id: "ultrathink",
|
||||
name: t('thinking.ultrathink'),
|
||||
description: t('thinking.ultrathinkDesc'),
|
||||
level: 4,
|
||||
phrase: "ultrathink"
|
||||
}
|
||||
];
|
||||
|
||||
const MODELS: Model[] = [
|
||||
{
|
||||
id: "sonnet",
|
||||
name: t('models.sonnet'),
|
||||
description: t('prompts.faster'),
|
||||
icon: <Zap className="h-4 w-4" />
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: t('models.opus'),
|
||||
description: t('prompts.stronger'),
|
||||
icon: <Sparkles className="h-4 w-4" />
|
||||
}
|
||||
];
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const expandedTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const unlistenDragDropRef = useRef<(() => void) | null>(null);
|
||||
@@ -490,7 +478,7 @@ const FloatingPromptInputInner = (
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium">编写您的提示词</h3>
|
||||
<h3 className="text-sm font-medium">{t('prompts.composePrompt')}</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -514,7 +502,7 @@ const FloatingPromptInputInner = (
|
||||
ref={expandedTextareaRef}
|
||||
value={prompt}
|
||||
onChange={handleTextChange}
|
||||
placeholder="请输入您的提示词..."
|
||||
placeholder={t('prompts.typePrompt')}
|
||||
className="min-h-[200px] resize-none"
|
||||
disabled={disabled}
|
||||
onDragEnter={handleDrag}
|
||||
@@ -526,7 +514,7 @@ const FloatingPromptInputInner = (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">模型:</span>
|
||||
<span className="text-xs text-muted-foreground">{t('prompts.model')}</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -539,7 +527,7 @@ const FloatingPromptInputInner = (
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">思考:</span>
|
||||
<span className="text-xs text-muted-foreground">{t('prompts.thinking')}</span>
|
||||
<Popover
|
||||
trigger={
|
||||
<TooltipProvider>
|
||||
@@ -558,7 +546,7 @@ const FloatingPromptInputInner = (
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="font-medium">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || "自动"}</p>
|
||||
<p className="font-medium">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.name || t('prompts.auto')}</p>
|
||||
<p className="text-xs text-muted-foreground">{THINKING_MODES.find(m => m.id === selectedThinkingMode)?.description}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -756,7 +744,7 @@ const FloatingPromptInputInner = (
|
||||
value={prompt}
|
||||
onChange={handleTextChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={dragActive ? "将图片拖放至此..." : "向 Claude 提问任何问题..."}
|
||||
placeholder={dragActive ? t('prompts.dropImages') : t('prompts.askClaude')}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"min-h-[44px] max-h-[120px] resize-none pr-10",
|
||||
@@ -799,7 +787,7 @@ const FloatingPromptInputInner = (
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Square className="h-4 w-4 mr-1" />
|
||||
停止
|
||||
{t('prompts.stop')}
|
||||
</>
|
||||
) : (
|
||||
<Send className="h-4 w-4" />
|
||||
@@ -808,7 +796,7 @@ const FloatingPromptInputInner = (
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-xs text-muted-foreground">
|
||||
按回车发送,Shift+回车换行{projectPath?.trim() && ",@ 符号提及文件,拖放图片"}
|
||||
{t('prompts.pressEnterToSend')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -7,6 +7,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { SelectComponent } from "@/components/ui/select";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { api } from "@/lib/api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface MCPAddServerProps {
|
||||
/**
|
||||
@@ -33,6 +34,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onServerAdded,
|
||||
onError,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [transport, setTransport] = useState<"stdio" | "sse">("stdio");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -97,12 +99,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
*/
|
||||
const handleAddStdioServer = async () => {
|
||||
if (!stdioName.trim()) {
|
||||
onError("服务器名称为必填项");
|
||||
onError(t('mcp.serverNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stdioCommand.trim()) {
|
||||
onError("命令为必填项");
|
||||
onError(t('mcp.commandRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,7 +144,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError("添加服务器失败");
|
||||
onError(t('mcp.addServerFailed'));
|
||||
console.error("Failed to add stdio server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -154,12 +156,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
*/
|
||||
const handleAddSseServer = async () => {
|
||||
if (!sseName.trim()) {
|
||||
onError("服务器名称为必填项");
|
||||
onError(t('mcp.serverNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sseUrl.trim()) {
|
||||
onError("URL 为必填项");
|
||||
onError(t('mcp.urlRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,7 +197,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError("添加服务器失败");
|
||||
onError(t('mcp.addServerFailed'));
|
||||
console.error("Failed to add SSE server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -209,7 +211,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium">环境变量</Label>
|
||||
<Label className="text-sm font-medium">{t('mcp.environmentVariables')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -217,7 +219,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
添加变量
|
||||
{t('mcp.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -257,9 +259,9 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">添加 MCP 服务器</h3>
|
||||
<h3 className="text-base font-semibold">{t('mcp.addServer')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
配置新的 Model Context Protocol 服务器
|
||||
{t('mcp.configureNewServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -280,7 +282,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-name">服务器名称</Label>
|
||||
<Label htmlFor="stdio-name">{t('mcp.serverName')}</Label>
|
||||
<Input
|
||||
id="stdio-name"
|
||||
placeholder="my-server"
|
||||
@@ -288,12 +290,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setStdioName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
用于标识此服务器的唯一名称
|
||||
{t('mcp.serverNameDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-command">命令</Label>
|
||||
<Label htmlFor="stdio-command">{t('mcp.command')}</Label>
|
||||
<Input
|
||||
id="stdio-command"
|
||||
placeholder="/path/to/server"
|
||||
@@ -302,12 +304,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
执行服务器的命令
|
||||
{t('mcp.commandDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-args">参数(可选)</Label>
|
||||
<Label htmlFor="stdio-args">{t('mcp.arguments')}</Label>
|
||||
<Input
|
||||
id="stdio-args"
|
||||
placeholder="arg1 arg2 arg3"
|
||||
@@ -316,19 +318,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
空格分隔的命令参数
|
||||
{t('mcp.argumentsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-scope">作用域</Label>
|
||||
<Label htmlFor="stdio-scope">{t('mcp.scope')}</Label>
|
||||
<SelectComponent
|
||||
value={stdioScope}
|
||||
onValueChange={(value: string) => setStdioScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: "本地(仅限此项目)" },
|
||||
{ value: "project", label: "项目(通过 .mcp.json 共享)" },
|
||||
{ value: "user", label: "用户(所有项目)" },
|
||||
{ value: "local", label: t('mcp.localScope') },
|
||||
{ value: "project", label: t('mcp.projectScope') },
|
||||
{ value: "user", label: t('mcp.userScope') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -345,12 +347,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
正在添加服务器...
|
||||
{t('mcp.addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
添加 Stdio 服务器
|
||||
{t('mcp.addStdioServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -363,7 +365,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-name">服务器名称</Label>
|
||||
<Label htmlFor="sse-name">{t('mcp.serverName')}</Label>
|
||||
<Input
|
||||
id="sse-name"
|
||||
placeholder="sse-server"
|
||||
@@ -371,7 +373,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setSseName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
用于标识此服务器的唯一名称
|
||||
{t('mcp.serverNameDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -385,19 +387,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
SSE 端点 URL
|
||||
{t('mcp.sseUrlDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-scope">作用域</Label>
|
||||
<Label htmlFor="sse-scope">{t('mcp.scope')}</Label>
|
||||
<SelectComponent
|
||||
value={sseScope}
|
||||
onValueChange={(value: string) => setSseScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: "本地(仅限此项目)" },
|
||||
{ value: "project", label: "项目(通过 .mcp.json 共享)" },
|
||||
{ value: "user", label: "用户(所有项目)" },
|
||||
{ value: "local", label: t('mcp.localScope') },
|
||||
{ value: "project", label: t('mcp.projectScope') },
|
||||
{ value: "user", label: t('mcp.userScope') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -414,12 +416,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
正在添加服务器...
|
||||
{t('mcp.addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
添加 SSE 服务器
|
||||
{t('mcp.addSseServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -433,7 +435,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Info className="h-4 w-4 text-primary" />
|
||||
<span>命令示例</span>
|
||||
<span>{t('mcp.exampleCommands')}</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs text-muted-foreground">
|
||||
<div className="font-mono bg-background p-2 rounded">
|
||||
|
@@ -9,6 +9,7 @@ import { api, type MCPServer } from "@/lib/api";
|
||||
import { MCPServerList } from "./MCPServerList";
|
||||
import { MCPAddServer } from "./MCPAddServer";
|
||||
import { MCPImportExport } from "./MCPImportExport";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface MCPManagerProps {
|
||||
/**
|
||||
@@ -29,6 +30,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState("servers");
|
||||
const [servers, setServers] = useState<MCPServer[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -54,7 +56,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
setServers(serverList);
|
||||
} catch (err) {
|
||||
console.error("MCPManager: Failed to load MCP servers:", err);
|
||||
setError("无法加载 MCP 服务器。请确保已安装 Claude Code。");
|
||||
setError(t('mcp.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -65,7 +67,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
*/
|
||||
const handleServerAdded = () => {
|
||||
loadServers();
|
||||
setToast({ message: "MCP 服务器添加成功!", type: "success" });
|
||||
setToast({ message: t('mcp.serverAddSuccess'), type: "success" });
|
||||
setActiveTab("servers");
|
||||
};
|
||||
|
||||
@@ -74,7 +76,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
*/
|
||||
const handleServerRemoved = (name: string) => {
|
||||
setServers(prev => prev.filter(s => s.name !== name));
|
||||
setToast({ message: `服务器 "${name}" 删除成功!`, type: "success" });
|
||||
setToast({ message: t('mcp.serverRemoveSuccess'), type: "success" });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,12 +86,12 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
loadServers();
|
||||
if (failed === 0) {
|
||||
setToast({
|
||||
message: `成功导入 ${imported} 个服务器!`,
|
||||
message: t('mcp.importSuccess', { count: imported }),
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
message: `已导入 ${imported} 个服务器,${failed} 个失败`,
|
||||
message: t('mcp.importPartialSuccess', { imported, failed }),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
@@ -117,10 +119,10 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Network className="h-5 w-5 text-blue-500" />
|
||||
MCP 服务器
|
||||
{t('mcp.title')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
管理 Model Context Protocol 服务器
|
||||
{t('mcp.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,15 +154,15 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
<TabsList className="grid w-full max-w-md grid-cols-3">
|
||||
<TabsTrigger value="servers" className="gap-2">
|
||||
<Network className="h-4 w-4 text-blue-500" />
|
||||
服务器
|
||||
{t('mcp.servers')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="add" className="gap-2">
|
||||
<Plus className="h-4 w-4 text-green-500" />
|
||||
添加服务器
|
||||
{t('mcp.addServer')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="import" className="gap-2">
|
||||
<Download className="h-4 w-4 text-purple-500" />
|
||||
导入/导出
|
||||
{t('mcp.importExport')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { api, type MCPServer } from "@/lib/api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface MCPServerListProps {
|
||||
/**
|
||||
@@ -49,6 +50,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
onServerRemoved,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [removingServer, setRemovingServer] = useState<string | null>(null);
|
||||
const [testingServer, setTestingServer] = useState<string | null>(null);
|
||||
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
||||
@@ -157,11 +159,11 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
const getScopeDisplayName = (scope: string) => {
|
||||
switch (scope) {
|
||||
case "local":
|
||||
return "本地(项目特定)";
|
||||
return t('mcp.localScope');
|
||||
case "project":
|
||||
return "项目(通过 .mcp.json 共享)";
|
||||
return t('mcp.projectScope');
|
||||
case "user":
|
||||
return "用户(所有项目)";
|
||||
return t('mcp.userScope');
|
||||
default:
|
||||
return scope;
|
||||
}
|
||||
@@ -193,7 +195,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{server.status?.running && (
|
||||
<Badge variant="outline" className="gap-1 flex-shrink-0 border-green-500/50 text-green-600 bg-green-500/10">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
运行中
|
||||
{t('mcp.running')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -210,7 +212,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
展开全部
|
||||
{t('mcp.showFull')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -225,7 +227,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{Object.keys(server.env).length > 0 && !isExpanded && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground pl-9">
|
||||
<span>环境变量:{Object.keys(server.env).length}</span>
|
||||
<span>{t('mcp.environmentVars', { count: Object.keys(server.env).length })}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -272,7 +274,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{server.command && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-medium text-muted-foreground">命令</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.command')}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -281,7 +283,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<Copy className="h-3 w-3 mr-1" />
|
||||
{isCopied ? "已复制!" : "复制"}
|
||||
{isCopied ? t('common.copy') + '!' : t('common.copy')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -290,7 +292,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
隐藏
|
||||
{t('mcp.hide')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,7 +304,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">参数</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.arguments')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<div key={idx} className="break-all">
|
||||
@@ -325,7 +327,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{Object.keys(server.env).length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">环境变量</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.environmentVariables')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="break-all">
|
||||
@@ -357,9 +359,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">已配置的服务器</h3>
|
||||
<h3 className="text-base font-semibold">{t('mcp.configuredServers')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
已配置 {servers.length} 个服务器
|
||||
{t('mcp.configuredCount', { count: servers.length })}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -369,7 +371,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="gap-2 hover:bg-primary/10 hover:text-primary hover:border-primary/50"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
刷新
|
||||
{t('common.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -379,9 +381,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
<div className="p-4 bg-primary/10 rounded-full mb-4">
|
||||
<Network className="h-12 w-12 text-primary" />
|
||||
</div>
|
||||
<p className="text-muted-foreground mb-2 font-medium">没有配置 MCP 服务器</p>
|
||||
<p className="text-muted-foreground mb-2 font-medium">{t('mcp.noServers')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
添加服务器以开始使用 Model Context Protocol
|
||||
{t('mcp.getStartedText')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
@@ -8,6 +8,7 @@ import { Toast, ToastContainer } from '@/components/ui/toast';
|
||||
import { SessionOutputViewer } from './SessionOutputViewer';
|
||||
import { api } from '@/lib/api';
|
||||
import type { AgentRun } from '@/lib/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface RunningSessionsViewProps {
|
||||
className?: string;
|
||||
@@ -16,6 +17,7 @@ interface RunningSessionsViewProps {
|
||||
}
|
||||
|
||||
export function RunningSessionsView({ className, showBackButton = false, onBack }: RunningSessionsViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const [runningSessions, setRunningSessions] = useState<AgentRun[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@@ -28,7 +30,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
setRunningSessions(sessions);
|
||||
} catch (error) {
|
||||
console.error('Failed to load running sessions:', error);
|
||||
setToast({ message: '加载运行中的会话失败', type: 'error' });
|
||||
setToast({ message: t('runningSessions.loadFailed'), type: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -41,10 +43,10 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
await api.cleanupFinishedProcesses();
|
||||
// Then reload the list
|
||||
await loadRunningSessions();
|
||||
setToast({ message: '运行中的会话列表已更新', type: 'success' });
|
||||
setToast({ message: t('runningSessions.refreshSuccess'), type: 'success' });
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh sessions:', error);
|
||||
setToast({ message: '刷新会话失败', type: 'error' });
|
||||
setToast({ message: t('runningSessions.refreshFailed'), type: 'error' });
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
@@ -54,15 +56,15 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
try {
|
||||
const success = await api.killAgentSession(runId);
|
||||
if (success) {
|
||||
setToast({ message: `${agentName} 会话已停止`, type: 'success' });
|
||||
setToast({ message: t('runningSessions.sessionStopped', { session: agentName }), type: 'success' });
|
||||
// Refresh the list after killing
|
||||
await loadRunningSessions();
|
||||
} else {
|
||||
setToast({ message: '会话可能已经结束', type: 'error' });
|
||||
setToast({ message: t('runningSessions.sessionMayFinished'), type: 'error' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to kill session:', error);
|
||||
setToast({ message: '终止会话失败', type: 'error' });
|
||||
setToast({ message: t('runningSessions.terminateFailed'), type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,9 +80,9 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'running':
|
||||
return <Badge variant="default" className="bg-green-100 text-green-800 border-green-200">运行中</Badge>;
|
||||
return <Badge variant="default" className="bg-green-100 text-green-800 border-green-200">{t('runningSessions.running')}</Badge>;
|
||||
case 'pending':
|
||||
return <Badge variant="secondary">等待中</Badge>;
|
||||
return <Badge variant="secondary">{t('runningSessions.pending')}</Badge>;
|
||||
default:
|
||||
return <Badge variant="outline">{status}</Badge>;
|
||||
}
|
||||
@@ -104,7 +106,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
<div className={`flex items-center justify-center p-8 ${className}`}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
<span>正在加载运行中的会话...</span>
|
||||
<span>{t('runningSessions.loadingSessions')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -125,7 +127,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
</Button>
|
||||
)}
|
||||
<Play className="h-5 w-5" />
|
||||
<h2 className="text-lg font-semibold">运行中的代理会话</h2>
|
||||
<h2 className="text-lg font-semibold">{t('runningSessions.title')}</h2>
|
||||
<Badge variant="secondary">{runningSessions.length}</Badge>
|
||||
</div>
|
||||
<Button
|
||||
@@ -136,7 +138,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
<span>刷新</span>
|
||||
<span>{t('common.refresh')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -145,7 +147,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
<CardContent className="flex items-center justify-center p-8">
|
||||
<div className="text-center space-y-2">
|
||||
<Clock className="h-8 w-8 mx-auto text-muted-foreground" />
|
||||
<p className="text-muted-foreground">当前没有正在运行的代理会话</p>
|
||||
<p className="text-muted-foreground">{t('runningSessions.noSessions')}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -187,7 +189,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
<span>查看输出</span>
|
||||
<span>{t('runningSessions.viewOutput')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -196,7 +198,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Square className="h-4 w-4" />
|
||||
<span>停止</span>
|
||||
<span>{t('common.stop')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,28 +206,28 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
<CardContent className="pt-0">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">任务</p>
|
||||
<p className="text-sm text-muted-foreground">{t('runningSessions.task')}</p>
|
||||
<p className="text-sm font-medium truncate">{session.task}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">模型</p>
|
||||
<p className="text-muted-foreground">{t('models.model')}</p>
|
||||
<p className="font-medium">{session.model}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">持续时间</p>
|
||||
<p className="text-muted-foreground">{t('runningSessions.duration')}</p>
|
||||
<p className="font-medium">
|
||||
{session.process_started_at
|
||||
? formatDuration(session.process_started_at)
|
||||
: '未知'
|
||||
: t('common.unknown')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">项目路径</p>
|
||||
<p className="text-sm text-muted-foreground">{t('runningSessions.projectPath')}</p>
|
||||
<p className="text-xs font-mono bg-muted px-2 py-1 rounded truncate">
|
||||
{session.project_path}
|
||||
</p>
|
||||
@@ -233,7 +235,7 @@ export function RunningSessionsView({ className, showBackButton = false, onBack
|
||||
|
||||
{session.session_id && (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">会话ID</p>
|
||||
<p className="text-sm text-muted-foreground">{t('runningSessions.sessionId')}</p>
|
||||
<p className="text-xs font-mono bg-muted px-2 py-1 rounded truncate">
|
||||
{session.session_id}
|
||||
</p>
|
||||
|
@@ -26,6 +26,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface SettingsProps {
|
||||
/**
|
||||
@@ -57,6 +58,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState("general");
|
||||
const [settings, setSettings] = useState<ClaudeSettings>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -145,7 +147,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load settings:", err);
|
||||
setError("加载设置失败。请确保 ~/.claude 目录存在。");
|
||||
setError(t('settings.loadFailed'));
|
||||
setSettings({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -187,11 +189,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
setBinaryPathChanged(false);
|
||||
}
|
||||
|
||||
setToast({ message: "设置保存成功!", type: "success" });
|
||||
setToast({ message: t('settings.saveSuccess'), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save settings:", err);
|
||||
setError("保存设置失败。");
|
||||
setToast({ message: "保存设置失败", type: "error" });
|
||||
setError(t('settings.saveFailed'));
|
||||
setToast({ message: t('settings.saveFailed'), type: "error" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -302,9 +304,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">设置</h2>
|
||||
<h2 className="text-lg font-semibold">{t('settings.title')}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
配置 Claude Code 偏好设置
|
||||
{t('settings.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -318,12 +320,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
保存中...
|
||||
{t('common.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
保存设置
|
||||
{t('settings.saveSettings')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -355,19 +357,19 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<TabsList className="mb-6">
|
||||
<TabsTrigger value="general" className="gap-2">
|
||||
<Settings2 className="h-4 w-4 text-slate-500" />
|
||||
常规
|
||||
{t('settings.general')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="permissions" className="gap-2">
|
||||
<Shield className="h-4 w-4 text-amber-500" />
|
||||
权限
|
||||
{t('settings.permissions')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="environment" className="gap-2">
|
||||
<Terminal className="h-4 w-4 text-blue-500" />
|
||||
环境
|
||||
{t('settings.environment')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="advanced" className="gap-2">
|
||||
<Code className="h-4 w-4 text-purple-500" />
|
||||
高级
|
||||
{t('settings.advanced')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -375,15 +377,15 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<TabsContent value="general" className="space-y-6">
|
||||
<Card className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-4">常规设置</h3>
|
||||
<h3 className="text-base font-semibold mb-4">{t('settings.generalSettings')}</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Include Co-authored By */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label htmlFor="coauthored">包含"Co-authored by Claude"</Label>
|
||||
<Label htmlFor="coauthored">{t('settings.includeCoAuthor')}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
在 git 提交和拉取请求中添加 Claude 署名
|
||||
{t('settings.includeCoAuthorDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -396,9 +398,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Verbose Output */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label htmlFor="verbose">详细输出</Label>
|
||||
<Label htmlFor="verbose">{t('settings.verboseOutput')}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
显示完整的 bash 和命令输出
|
||||
{t('settings.verboseOutputDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -410,7 +412,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Cleanup Period */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cleanup">聊天记录保留期(天)</Label>
|
||||
<Label htmlFor="cleanup">{t('settings.chatRetention')}</Label>
|
||||
<Input
|
||||
id="cleanup"
|
||||
type="number"
|
||||
@@ -423,16 +425,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
本地保留聊天记录的时长(默认:30 天)
|
||||
{t('settings.chatRetentionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Claude Binary Path Selector */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium mb-2 block">Claude Code 安装</Label>
|
||||
<Label className="text-sm font-medium mb-2 block">{t('settings.claudeInstallation')}</Label>
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
选择要使用的 Claude Code 安装版本
|
||||
{t('settings.claudeInstallationDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<ClaudeVersionSelector
|
||||
@@ -441,7 +443,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
/>
|
||||
{binaryPathChanged && (
|
||||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||
⚠️ Claude 二进制路径已更改。请记得保存您的设置。
|
||||
{t('settings.binaryPathChanged')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -455,16 +457,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-2">权限规则</h3>
|
||||
<h3 className="text-base font-semibold mb-2">{t('settings.permissionRules')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
控制 Claude Code 可以在无需手动批准的情况下使用哪些工具
|
||||
{t('settings.permissionRulesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Allow Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-green-500">允许规则</Label>
|
||||
<Label className="text-sm font-medium text-green-500">{t('settings.allowRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -472,13 +474,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-green-500/50 hover:text-green-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
添加规则
|
||||
{t('settings.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{allowRules.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
未配置允许规则。Claude 将对所有工具请求批准。
|
||||
{t('settings.noAllowRules')}
|
||||
</p>
|
||||
) : (
|
||||
allowRules.map((rule) => (
|
||||
@@ -489,7 +491,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="例如:Bash(npm run test:*)"
|
||||
placeholder={t('settings.allowRulesPlaceholder')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("allow", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -511,7 +513,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Deny Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-red-500">拒绝规则</Label>
|
||||
<Label className="text-sm font-medium text-red-500">{t('settings.denyRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -519,13 +521,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-red-500/50 hover:text-red-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
添加规则
|
||||
{t('settings.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{denyRules.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
未配置拒绝规则。
|
||||
{t('settings.noDenyRules')}
|
||||
</p>
|
||||
) : (
|
||||
denyRules.map((rule) => (
|
||||
@@ -536,7 +538,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="例如:Bash(curl:*)"
|
||||
placeholder={t('settings.denyRulesPlaceholder')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("deny", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -557,14 +559,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>示例:</strong>
|
||||
<strong>{t('settings.examplesLabel')}:</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash</code> - 允许所有 bash 命令</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run build)</code> - 允许精确命令</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run test:*)</code> - 允许带前缀的命令</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Read(~/.zshrc)</code> - 允许读取特定文件</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Edit(docs/**)</code> - 允许编辑 docs 目录中的文件</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash</code> - {t('settings.bashAllExample')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run build)</code> - {t('settings.bashExactExample')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash(npm run test:*)</code> - {t('settings.bashPrefixExample')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Read(~/.zshrc)</code> - {t('settings.readFileExample')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Edit(docs/**)</code> - {t('settings.editDocsExample')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -577,9 +579,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">环境变量</h3>
|
||||
<h3 className="text-base font-semibold">{t('settings.environmentVariables')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
应用于每个 Claude Code 会话的环境变量
|
||||
{t('settings.environmentVariablesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -589,14 +591,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
添加变量
|
||||
{t('settings.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{envVars.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
未配置环境变量。
|
||||
{t('settings.noEnvironmentVars')}
|
||||
</p>
|
||||
) : (
|
||||
envVars.map((envVar) => (
|
||||
@@ -607,14 +609,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="键名"
|
||||
placeholder={t('settings.envKeyPlaceholder')}
|
||||
value={envVar.key}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "key", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<Input
|
||||
placeholder="值"
|
||||
placeholder={t('settings.envValuePlaceholder')}
|
||||
value={envVar.value}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "value", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
@@ -634,12 +636,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>常用变量:</strong>
|
||||
<strong>{t('settings.commonVariables')}:</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">CLAUDE_CODE_ENABLE_TELEMETRY</code> - 启用/禁用遥测 (0 或 1)</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">ANTHROPIC_MODEL</code> - 自定义模型名称</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">DISABLE_COST_WARNINGS</code> - 禁用成本警告 (1)</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">CLAUDE_CODE_ENABLE_TELEMETRY</code> - {t('settings.telemetryDesc')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">ANTHROPIC_MODEL</code> - {t('settings.modelNameDesc')}</li>
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">DISABLE_COST_WARNINGS</code> - {t('settings.costWarningsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -650,15 +652,15 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-4">高级设置</h3>
|
||||
<h3 className="text-base font-semibold mb-4">{t('settings.advancedSettings')}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
为高级用户提供的额外配置选项
|
||||
{t('settings.advancedSettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Key Helper */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKeyHelper">API 密钥辅助脚本</Label>
|
||||
<Label htmlFor="apiKeyHelper">{t('settings.apiKeyHelper')}</Label>
|
||||
<Input
|
||||
id="apiKeyHelper"
|
||||
placeholder="/path/to/generate_api_key.sh"
|
||||
@@ -666,18 +668,18 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
用于为 API 请求生成身份验证值的自定义脚本
|
||||
{t('settings.apiKeyHelperDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Raw JSON Editor */}
|
||||
<div className="space-y-2">
|
||||
<Label>原始设置 (JSON)</Label>
|
||||
<Label>{t('settings.rawSettings')}</Label>
|
||||
<div className="p-3 rounded-md bg-muted font-mono text-xs overflow-x-auto whitespace-pre-wrap">
|
||||
<pre>{JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
这显示了将保存到 ~/.claude/settings.json 的原始 JSON 数据
|
||||
{t('settings.rawSettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
Briefcase
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface UsageDashboardProps {
|
||||
/**
|
||||
@@ -32,6 +33,7 @@ interface UsageDashboardProps {
|
||||
* <UsageDashboard onBack={() => setView('welcome')} />
|
||||
*/
|
||||
export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [stats, setStats] = useState<UsageStats | null>(null);
|
||||
@@ -82,7 +84,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
setSessionStats(sessionData);
|
||||
} catch (err) {
|
||||
console.error("Failed to load usage stats:", err);
|
||||
setError("加载使用统计失败,请重试。");
|
||||
setError(t('usageDashboard.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -146,9 +148,9 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold">使用情况仪表板</h1>
|
||||
<h1 className="text-lg font-semibold">{t('usageDashboard.title')}</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
跟踪您的 Claude Code 使用情况和费用
|
||||
{t('usageDashboard.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
onClick={() => setSelectedDateRange(range)}
|
||||
className="text-xs"
|
||||
>
|
||||
{range === "all" ? "全部时间" : range === "7d" ? "最近 7 天" : "最近 30 天"}
|
||||
{range === "all" ? t('usageDashboard.allTime') : range === "7d" ? t('usageDashboard.last7Days') : t('usageDashboard.last30Days')}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-sm text-muted-foreground">正在加载使用统计...</p>
|
||||
<p className="text-sm text-muted-foreground">{t('usageDashboard.loadingStats')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
@@ -187,7 +189,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="text-center max-w-md">
|
||||
<p className="text-sm text-destructive mb-4">{error}</p>
|
||||
<Button onClick={loadUsageStats} size="sm">
|
||||
重试
|
||||
{t('usageDashboard.tryAgain')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,7 +206,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">总费用</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.totalCost')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(stats.total_cost)}
|
||||
</p>
|
||||
@@ -217,7 +219,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">总会话数</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.totalSessions')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatNumber(stats.total_sessions)}
|
||||
</p>
|
||||
@@ -230,7 +232,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">总令牌数</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.totalTokens')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatTokens(stats.total_tokens)}
|
||||
</p>
|
||||
@@ -243,7 +245,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">平均会话费用</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.avgCostPerSession')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(
|
||||
stats.total_sessions > 0
|
||||
@@ -260,32 +262,32 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Tabs for different views */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="overview">概览</TabsTrigger>
|
||||
<TabsTrigger value="models">按模型</TabsTrigger>
|
||||
<TabsTrigger value="projects">按项目</TabsTrigger>
|
||||
<TabsTrigger value="sessions">按会话</TabsTrigger>
|
||||
<TabsTrigger value="timeline">时间线</TabsTrigger>
|
||||
<TabsTrigger value="overview">{t('usageDashboard.overview')}</TabsTrigger>
|
||||
<TabsTrigger value="models">{t('usageDashboard.byModel')}</TabsTrigger>
|
||||
<TabsTrigger value="projects">{t('usageDashboard.byProject')}</TabsTrigger>
|
||||
<TabsTrigger value="sessions">{t('usageDashboard.bySession')}</TabsTrigger>
|
||||
<TabsTrigger value="timeline">{t('usageDashboard.timeline')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Overview Tab */}
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">令牌分布</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.tokenBreakdown')}</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">输入令牌</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.inputTokens')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_input_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">输出令牌</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.outputTokens')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_output_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">缓存写入</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.cacheWrite')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_creation_tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">缓存读取</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usageDashboard.cacheRead')}</p>
|
||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_read_tokens)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +296,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">常用模型</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.mostUsedModels')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_model.slice(0, 3).map((model) => (
|
||||
<div key={model.model} className="flex items-center justify-between">
|
||||
@@ -303,7 +305,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{model.session_count} 个会话
|
||||
{model.session_count} {t('usageDashboard.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">热门项目</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.topProjects')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.slice(0, 3).map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between">
|
||||
@@ -324,7 +326,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{project.project_path}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} 个会话
|
||||
{project.session_count} {t('usageDashboard.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Models Tab */}
|
||||
<TabsContent value="models">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">按模型使用情况</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.byModel')}</h3>
|
||||
<div className="space-y-4">
|
||||
{stats.by_model.map((model) => (
|
||||
<div key={model.model} className="space-y-2">
|
||||
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{model.session_count} 个会话
|
||||
{model.session_count} {t('usageDashboard.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-semibold">
|
||||
@@ -362,19 +364,19 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-muted-foreground">输入: </span>
|
||||
<span className="text-muted-foreground">{t('usageDashboard.input')} </span>
|
||||
<span className="font-medium">{formatTokens(model.input_tokens)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">输出: </span>
|
||||
<span className="text-muted-foreground">{t('usageDashboard.output')} </span>
|
||||
<span className="font-medium">{formatTokens(model.output_tokens)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">缓存写: </span>
|
||||
<span className="text-muted-foreground">{t('usageDashboard.cacheW')} </span>
|
||||
<span className="font-medium">{formatTokens(model.cache_creation_tokens)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">缓存读: </span>
|
||||
<span className="text-muted-foreground">{t('usageDashboard.cacheR')} </span>
|
||||
<span className="font-medium">{formatTokens(model.cache_read_tokens)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Projects Tab */}
|
||||
<TabsContent value="projects">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">按项目使用情况</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.byProject')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -397,17 +399,17 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</span>
|
||||
<div className="flex items-center space-x-3 mt-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} 个会话
|
||||
{project.session_count} {t('usageDashboard.sessions')}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatTokens(project.total_tokens)} 个令牌
|
||||
{formatTokens(project.total_tokens)} {t('usageDashboard.tokens')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-semibold">{formatCurrency(project.total_cost)}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatCurrency(project.total_cost / project.session_count)}/会话
|
||||
{formatCurrency(project.total_cost / project.session_count)}{t('usageDashboard.perSession')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,7 +421,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Sessions Tab */}
|
||||
<TabsContent value="sessions">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">按会话使用情况</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usageDashboard.bySession')}</h3>
|
||||
<div className="space-y-3">
|
||||
{sessionStats?.map((session) => (
|
||||
<div key={`${session.project_path}-${session.project_name}`} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -451,7 +453,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-6 flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>每日使用情况</span>
|
||||
<span>{t('usageDashboard.dailyUsage')}</span>
|
||||
</h3>
|
||||
{stats.by_date.length > 0 ? (() => {
|
||||
const maxCost = Math.max(...stats.by_date.map(d => d.total_cost), 0);
|
||||
@@ -484,13 +486,13 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="bg-background border border-border rounded-lg shadow-lg p-3 whitespace-nowrap">
|
||||
<p className="text-sm font-semibold">{formattedDate}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
费用: {formatCurrency(day.total_cost)}
|
||||
{t('usageDashboard.cost')}: {formatCurrency(day.total_cost)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatTokens(day.total_tokens)} 个令牌
|
||||
{formatTokens(day.total_tokens)} {t('usageDashboard.tokens')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{day.models_used.length} 个模型
|
||||
{day.models_used.length} {t('usageDashboard.models')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1">
|
||||
@@ -517,13 +519,13 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
|
||||
{/* X-axis label */}
|
||||
<div className="mt-8 text-center text-xs text-muted-foreground">
|
||||
每日使用趋势
|
||||
{t('usageDashboard.dailyUsageOverTime')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})() : (
|
||||
<div className="text-center py-8 text-sm text-muted-foreground">
|
||||
所选时间段内没有可用的使用数据
|
||||
{t('usageDashboard.noDataAvailable')}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface PaginationProps {
|
||||
/**
|
||||
@@ -38,6 +39,7 @@ export const Pagination: React.FC<PaginationProps> = ({
|
||||
onPageChange,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
if (totalPages <= 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -55,7 +57,7 @@ export const Pagination: React.FC<PaginationProps> = ({
|
||||
</Button>
|
||||
|
||||
<span className="text-sm text-muted-foreground">
|
||||
第 {currentPage} 页,共 {totalPages} 页
|
||||
{t('common.page', { current: currentPage, total: totalPages })}
|
||||
</span>
|
||||
|
||||
<Button
|
||||
|
@@ -109,13 +109,19 @@
|
||||
"executionStopped": "Execution stopped by user",
|
||||
"stopFailed": "Failed to stop execution",
|
||||
"selectDirectoryFailed": "Failed to select directory",
|
||||
"navigationWarning": "An agent is currently running. If you navigate away, the agent will continue running in the background. You can view running sessions in the 'Running Sessions' tab within CC Agents.\n\nDo you want to continue?"
|
||||
"navigationWarning": "An agent is currently running. If you navigate away, the agent will continue running in the background. You can view running sessions in the 'Running Sessions' tab within CC Agents.\n\nDo you want to continue?",
|
||||
"selectProjectDirectory": "Select Project Directory",
|
||||
"output": "Output",
|
||||
"close": "Close",
|
||||
"stop": "Stop",
|
||||
"execute": "Execute"
|
||||
},
|
||||
"models": {
|
||||
"sonnet": "Claude 4 Sonnet",
|
||||
"opus": "Claude 4 Opus",
|
||||
"sonnetDescription": "Faster, efficient for most tasks",
|
||||
"opusDescription": "More capable, better for complex tasks"
|
||||
"opusDescription": "More capable, better for complex tasks",
|
||||
"model": "Model"
|
||||
},
|
||||
"projects": {
|
||||
"sessions": "{{count}} session",
|
||||
@@ -291,7 +297,8 @@
|
||||
"exportFunctionalityComingSoon": "Export functionality coming soon!",
|
||||
"mcpServerStarted": "Claude Code MCP server started. You can now connect to it from other applications.",
|
||||
"failedToStartMcpServer": "Failed to start Claude Code as MCP server",
|
||||
"enterServerName": "Enter a name for this server:"
|
||||
"enterServerName": "Enter a name for this server:",
|
||||
"configuredCount": "{{count}} servers configured"
|
||||
},
|
||||
"topbar": {
|
||||
"checking": "Checking...",
|
||||
@@ -363,7 +370,14 @@
|
||||
"pressEnterToSend": "Press Enter to send, Shift+Enter for new line, @ to mention files, drag & drop images",
|
||||
"send": "Send",
|
||||
"model": "Model:",
|
||||
"thinking": "Thinking:"
|
||||
"thinking": "Thinking:",
|
||||
"stop": "Stop",
|
||||
"faster": "Faster, efficient for most tasks",
|
||||
"stronger": "More capable, better for complex tasks",
|
||||
"selectProjectPath": "Select or enter project path",
|
||||
"enterTask": "Enter the task for the agent",
|
||||
"auto": "Auto",
|
||||
"fullscreen": "Fullscreen"
|
||||
},
|
||||
"errors": {
|
||||
"loadProjectsFailed": "Failed to load projects. Please ensure ~/.claude directory exists.",
|
||||
|
@@ -88,7 +88,14 @@
|
||||
"exportFailed": "导出智能体失败",
|
||||
"placeholderTask": "这将作为执行智能体时的默认任务占位符",
|
||||
"definePrompt": "定义您的 CC 智能体的行为和能力",
|
||||
"unsavedChanges": "您有未保存的更改。您确定要离开吗?"
|
||||
"unsavedChanges": "您有未保存的更改。您确定要离开吗?",
|
||||
"editAgent": "编辑 CC 智能体",
|
||||
"updateAgentDesc": "更新您的 Claude Code 智能体",
|
||||
"createAgentDesc": "创建一个新的 Claude Code 智能体",
|
||||
"updateFailed": "更新智能体失败",
|
||||
"createFailed": "创建智能体失败",
|
||||
"agentNamePlaceholder": "例如:代码助手",
|
||||
"defaultTaskPlaceholder": "例如:检查代码安全问题"
|
||||
},
|
||||
"agentExecution": {
|
||||
"title": "执行 CC 智能体",
|
||||
@@ -109,13 +116,19 @@
|
||||
"executionStopped": "执行已被用户停止",
|
||||
"stopFailed": "停止执行失败",
|
||||
"selectDirectoryFailed": "选择目录失败",
|
||||
"navigationWarning": "智能体正在运行中。如果您离开此页面,智能体将在后台继续运行。您可以在 CC 智能体的「运行中的会话」标签页中查看运行中的会话。\n\n您确定要继续吗?"
|
||||
"navigationWarning": "智能体正在运行中。如果您离开此页面,智能体将在后台继续运行。您可以在 CC 智能体的「运行中的会话」标签页中查看运行中的会话。\n\n您确定要继续吗?",
|
||||
"selectProjectDirectory": "选择项目目录",
|
||||
"output": "输出",
|
||||
"close": "关闭",
|
||||
"stop": "停止",
|
||||
"execute": "执行"
|
||||
},
|
||||
"models": {
|
||||
"sonnet": "Claude 4 Sonnet",
|
||||
"opus": "Claude 4 Opus",
|
||||
"sonnetDescription": "速度快,适用于大多数任务",
|
||||
"opusDescription": "功能更强,适用于复杂任务"
|
||||
"opusDescription": "功能更强,适用于复杂任务",
|
||||
"model": "模型"
|
||||
},
|
||||
"projects": {
|
||||
"sessions": "{{count}} 个会话",
|
||||
@@ -148,7 +161,21 @@
|
||||
"closePreview": "关闭预览",
|
||||
"outputCopiedAsJsonl": "输出已复制为 JSONL",
|
||||
"outputRefreshed": "输出已刷新",
|
||||
"agentExecutionCompleted": "智能体执行完成"
|
||||
"agentExecutionCompleted": "智能体执行完成",
|
||||
"queuedPrompts": "排队提示({{count}})",
|
||||
"scrollToTop": "滚动到顶部",
|
||||
"scrollToBottom": "滚动到底部",
|
||||
"tokens": "标记",
|
||||
"sessionTimeline": "会话时间线",
|
||||
"sessionNamePlaceholder": "例如:备用方案",
|
||||
"selectDirectoryFailed": "选择目录失败:{{error}}",
|
||||
"sendPromptFailed": "发送提示失败",
|
||||
"cancelSessionFailed": "取消会话失败:{{error}}",
|
||||
"copyAsMarkdown": "复制为 Markdown",
|
||||
"copyAsJsonl": "复制为 JSONL",
|
||||
"copyOutput": "复制输出",
|
||||
"openPreviewTooltip": "在侧边栏中打开网页预览",
|
||||
"closePreviewTooltip": "关闭网页预览"
|
||||
},
|
||||
"usageDashboard": {
|
||||
"title": "使用情况仪表板",
|
||||
@@ -225,7 +252,21 @@
|
||||
"saveSuccess": "设置保存成功!",
|
||||
"saveFailed": "保存设置失败",
|
||||
"loadFailed": "加载设置失败。请确保 ~/.claude 目录存在。",
|
||||
"binaryPathChanged": "⚠️ Claude 二进制文件路径已更改。请记得保存您的设置。"
|
||||
"binaryPathChanged": "⚠️ Claude 二进制文件路径已更改。请记得保存您的设置。",
|
||||
"allowRulesPlaceholder": "例如:Bash(npm run test:*)",
|
||||
"denyRulesPlaceholder": "例如:Bash(curl:*)",
|
||||
"examplesLabel": "示例",
|
||||
"bashAllExample": "允许所有 bash 命令",
|
||||
"bashExactExample": "允许精确命令",
|
||||
"bashPrefixExample": "允许带前缀的命令",
|
||||
"readFileExample": "允许读取特定文件",
|
||||
"editDocsExample": "允许编辑 docs 目录中的文件",
|
||||
"envKeyPlaceholder": "键名",
|
||||
"envValuePlaceholder": "值",
|
||||
"commonVariables": "常用变量",
|
||||
"telemetryDesc": "启用/禁用遥测 (0 或 1)",
|
||||
"modelNameDesc": "自定义模型名称",
|
||||
"costWarningsDesc": "禁用成本警告 (1)"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP 服务器",
|
||||
@@ -291,7 +332,16 @@
|
||||
"exportFunctionalityComingSoon": "导出功能即将推出!",
|
||||
"mcpServerStarted": "Claude Code MCP 服务器已启动。您现在可以从其他应用程序连接到它。",
|
||||
"failedToStartMcpServer": "启动 Claude Code 作为 MCP 服务器失败",
|
||||
"enterServerName": "为此服务器输入名称:"
|
||||
"enterServerName": "为此服务器输入名称:",
|
||||
"configuredCount": "已配置 {{count}} 个服务器",
|
||||
"addServer": "添加 MCP 服务器",
|
||||
"configureNewServer": "配置新的 Model Context Protocol 服务器",
|
||||
"serverNameDesc": "用于标识此服务器的唯一名称",
|
||||
"commandDesc": "执行服务器的命令",
|
||||
"argumentsDesc": "空格分隔的命令参数",
|
||||
"sseUrlDesc": "SSE 端点 URL",
|
||||
"urlRequired": "URL 为必填项",
|
||||
"importPartialSuccess": "已导入 {{imported}} 个服务器,{{failed}} 个失败"
|
||||
},
|
||||
"topbar": {
|
||||
"checking": "检查中...",
|
||||
@@ -314,7 +364,8 @@
|
||||
"projectPath": "项目路径",
|
||||
"sessionId": "会话ID",
|
||||
"viewOutput": "查看输出",
|
||||
"pending": "等待中"
|
||||
"pending": "等待中",
|
||||
"running": "运行中"
|
||||
},
|
||||
"output": {
|
||||
"title": "输出",
|
||||
@@ -363,7 +414,14 @@
|
||||
"pressEnterToSend": "按回车发送,Shift+回车换行,@ 符号提及文件,拖放图片",
|
||||
"send": "发送",
|
||||
"model": "模型:",
|
||||
"thinking": "思考:"
|
||||
"thinking": "思考:",
|
||||
"stop": "停止",
|
||||
"faster": "更快速,适用于大多数任务",
|
||||
"stronger": "更强大,适用于复杂任务",
|
||||
"selectProjectPath": "选择或输入项目路径",
|
||||
"enterTask": "输入智能体的任务",
|
||||
"auto": "自动",
|
||||
"fullscreen": "全屏"
|
||||
},
|
||||
"errors": {
|
||||
"loadProjectsFailed": "加载项目失败。请确保 ~/.claude 目录存在。",
|
||||
|
Reference in New Issue
Block a user