This commit is contained in:
2025-08-06 11:32:29 +08:00
parent 90afd6e520
commit 351a79d54c
21 changed files with 915 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Toast, ToastContainer } from "@/components/ui/toast";
import { useTranslation } from "@/hooks/useTranslation";
import { api, type Agent } from "@/lib/api";
import { cn } from "@/lib/utils";
import MDEditor from "@uiw/react-md-editor";
@@ -43,6 +44,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 || "");
@@ -92,9 +94,9 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
onAgentCreated();
} catch (err) {
console.error("Failed to save agent:", err);
setError(isEditMode ? "Failed to update agent" : "Failed to create agent");
setError(isEditMode ? t('agents.updateFailed') : t('agents.createFailed'));
setToast({
message: isEditMode ? "Failed to update agent" : "Failed to create agent",
message: isEditMode ? t('agents.updateFailed') : t('agents.createFailed'),
type: "error"
});
} finally {
@@ -108,7 +110,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
systemPrompt !== (agent?.system_prompt || "") ||
defaultTask !== (agent?.default_task || "") ||
model !== (agent?.model || "sonnet")) &&
!confirm("You have unsaved changes. Are you sure you want to leave?")) {
!confirm(t('messages.unsavedChanges'))) {
return;
}
onBack();
@@ -135,10 +137,10 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
</Button>
<div>
<h2 className="text-lg font-semibold">
{isEditMode ? "Edit CC Agent" : "Create CC Agent"}
{isEditMode ? t('agents.editAgent') : t('agents.createAgent')}
</h2>
<p className="text-xs text-muted-foreground">
{isEditMode ? "Update your Claude Code agent" : "Create a new Claude Code agent"}
{isEditMode ? t('agents.updateAgentDescription') : t('agents.createAgentDescription')}
</p>
</div>
</div>
@@ -153,7 +155,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
) : (
<Save className="mr-2 h-4 w-4" />
)}
{saving ? "Saving..." : "Save"}
{saving ? t('app.loading') : t('app.save')}
</Button>
</motion.div>

View File

@@ -0,0 +1,85 @@
import React from 'react';
import { Globe, Check } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useTranslation } from '@/hooks/useTranslation';
import { api } from '@/lib/api';
import { cn } from '@/lib/utils';
interface LanguageSwitcherProps {
className?: string;
showText?: boolean;
}
/**
* 语言切换组件
*
* @example
* <LanguageSwitcher />
* <LanguageSwitcher showText={true} className="ml-2" />
*/
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
className,
showText = false
}) => {
const { changeLanguage, currentLanguage, supportedLanguages } = useTranslation();
const handleLanguageChange = async (languageCode: string) => {
try {
// 映射前端语言代码到后端格式
const backendLocale = languageCode === 'zh' ? 'zh-CN' : 'en-US';
// 同步到后端
await api.setLanguage(backendLocale);
// 更新前端
changeLanguage(languageCode);
} catch (error) {
console.error('Failed to change language:', error);
// 即使后端同步失败,也要尝试更新前端
changeLanguage(languageCode);
}
};
const getCurrentLanguageDisplay = () => {
const currentLang = supportedLanguages.find(lang => lang.code === currentLanguage);
return currentLang?.nativeName || currentLanguage.toUpperCase();
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className={cn("gap-2", className)}
>
<Globe className="h-4 w-4" />
{showText && <span className="hidden sm:inline">{getCurrentLanguageDisplay()}</span>}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
{supportedLanguages.map((language) => (
<DropdownMenuItem
key={language.code}
onClick={() => handleLanguageChange(language.code)}
className="flex items-center justify-between cursor-pointer"
>
<div className="flex flex-col">
<span className="font-medium">{language.nativeName}</span>
<span className="text-xs text-muted-foreground">{language.name}</span>
</div>
{currentLanguage === language.code && (
<Check className="h-4 w-4 text-primary" />
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -3,6 +3,8 @@ import { motion } from "framer-motion";
import { Circle, FileText, Settings, ExternalLink, BarChart3, Network, Info, Bot } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Popover } from "@/components/ui/popover";
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
import { useTranslation } from "@/hooks/useTranslation";
import { api, type ClaudeVersionStatus } from "@/lib/api";
import { cn } from "@/lib/utils";
@@ -57,6 +59,7 @@ export const Topbar: React.FC<TopbarProps> = ({
onAgentsClick,
className,
}) => {
const { t } = useTranslation();
const [versionStatus, setVersionStatus] = useState<ClaudeVersionStatus | null>(null);
const [checking, setChecking] = useState(true);
@@ -130,7 +133,7 @@ export const Topbar: React.FC<TopbarProps> = ({
trigger={statusContent}
content={
<div className="space-y-3 max-w-xs">
<p className="text-sm font-medium">Claude Code not found</p>
<p className="text-sm font-medium">{t('messages.claudeCodeNotFound')}</p>
<div className="rounded-md bg-muted p-3">
<pre className="text-xs font-mono whitespace-pre-wrap">
{versionStatus.output}
@@ -142,7 +145,7 @@ export const Topbar: React.FC<TopbarProps> = ({
className="w-full"
onClick={onSettingsClick}
>
Select Claude Installation
{t('messages.selectClaudeInstallation')}
</Button>
<a
href="https://www.anthropic.com/claude-code"
@@ -150,7 +153,7 @@ export const Topbar: React.FC<TopbarProps> = ({
rel="noopener noreferrer"
className="flex items-center space-x-1 text-xs text-primary hover:underline"
>
<span>Install Claude Code</span>
<span>{t('messages.installClaudeCode')}</span>
<ExternalLink className="h-3 w-3" />
</a>
</div>
@@ -186,7 +189,7 @@ export const Topbar: React.FC<TopbarProps> = ({
className="text-xs"
>
<Bot className="mr-2 h-3 w-3" />
Agents
{t('navigation.agents')}
</Button>
)}
@@ -197,7 +200,7 @@ export const Topbar: React.FC<TopbarProps> = ({
className="text-xs"
>
<BarChart3 className="mr-2 h-3 w-3" />
Usage Dashboard
{t('navigation.usage')}
</Button>
<Button
@@ -217,7 +220,7 @@ export const Topbar: React.FC<TopbarProps> = ({
className="text-xs"
>
<Network className="mr-2 h-3 w-3" />
MCP
{t('navigation.mcp')}
</Button>
<Button
@@ -227,15 +230,18 @@ export const Topbar: React.FC<TopbarProps> = ({
className="text-xs"
>
<Settings className="mr-2 h-3 w-3" />
Settings
{t('navigation.settings')}
</Button>
{/* Language Switcher */}
<LanguageSwitcher />
<Button
variant="ghost"
size="icon"
onClick={onInfoClick}
className="h-8 w-8"
title="About"
title={t('navigation.about')}
>
<Info className="h-4 w-4" />
</Button>