汉化
This commit is contained in:
@@ -30,6 +30,7 @@ impl SimpleI18n {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn t(&self, key: &str) -> String {
|
pub fn t(&self, key: &str) -> String {
|
||||||
let locale = self.get_current_locale();
|
let locale = self.get_current_locale();
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ fn get_i18n() -> &'static SimpleI18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 便捷函数用于全局访问
|
// 便捷函数用于全局访问
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn t(key: &str) -> String {
|
pub fn t(key: &str) -> String {
|
||||||
get_i18n().t(key)
|
get_i18n().t(key)
|
||||||
}
|
}
|
||||||
|
24
src/App.tsx
24
src/App.tsx
@@ -27,6 +27,7 @@ import { AgentsModal } from "@/components/AgentsModal";
|
|||||||
import { useTabState } from "@/hooks/useTabState";
|
import { useTabState } from "@/hooks/useTabState";
|
||||||
import { AnalyticsConsentBanner } from "@/components/AnalyticsConsent";
|
import { AnalyticsConsentBanner } from "@/components/AnalyticsConsent";
|
||||||
import { useAppLifecycle, useTrackEvent } from "@/hooks";
|
import { useAppLifecycle, useTrackEvent } from "@/hooks";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
type View =
|
type View =
|
||||||
| "welcome"
|
| "welcome"
|
||||||
@@ -48,6 +49,7 @@ type View =
|
|||||||
* AppContent component - Contains the main app logic, wrapped by providers
|
* AppContent component - Contains the main app logic, wrapped by providers
|
||||||
*/
|
*/
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [view, setView] = useState<View>("tabs");
|
const [view, setView] = useState<View>("tabs");
|
||||||
const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState();
|
const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState();
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
@@ -158,7 +160,7 @@ function AppContent() {
|
|||||||
setProjects(projectList);
|
setProjects(projectList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load projects:", err);
|
console.error("Failed to load projects:", err);
|
||||||
setError("Failed to load projects. Please ensure ~/.claude directory exists.");
|
setError(t('failedToLoadProjects'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -176,7 +178,7 @@ function AppContent() {
|
|||||||
setSelectedProject(project);
|
setSelectedProject(project);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load sessions:", err);
|
console.error("Failed to load sessions:", err);
|
||||||
setError("Failed to load sessions for this project.");
|
setError(t('failedToLoadSessions'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ function AppContent() {
|
|||||||
>
|
>
|
||||||
<h1 className="text-4xl font-bold tracking-tight">
|
<h1 className="text-4xl font-bold tracking-tight">
|
||||||
<span className="rotating-symbol"></span>
|
<span className="rotating-symbol"></span>
|
||||||
Welcome to Claudia
|
{t('welcomeToClaudia')}
|
||||||
</h1>
|
</h1>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -264,7 +266,7 @@ function AppContent() {
|
|||||||
>
|
>
|
||||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||||
<Bot className="h-16 w-16 mb-4 text-primary" />
|
<Bot className="h-16 w-16 mb-4 text-primary" />
|
||||||
<h2 className="text-xl font-semibold">CC Agents</h2>
|
<h2 className="text-xl font-semibold">{t('ccAgents')}</h2>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -281,7 +283,7 @@ function AppContent() {
|
|||||||
>
|
>
|
||||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||||
<FolderCode className="h-16 w-16 mb-4 text-primary" />
|
<FolderCode className="h-16 w-16 mb-4 text-primary" />
|
||||||
<h2 className="text-xl font-semibold">CC Projects</h2>
|
<h2 className="text-xl font-semibold">{t('ccProjects')}</h2>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -329,12 +331,12 @@ function AppContent() {
|
|||||||
onClick={() => handleViewChange("welcome")}
|
onClick={() => handleViewChange("welcome")}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
>
|
>
|
||||||
← Back to Home
|
{t('backToHome')}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h1 className="text-3xl font-bold tracking-tight">CC Projects</h1>
|
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
Browse your Claude Code sessions
|
{t('browseClaudeCodeSessions')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -396,7 +398,7 @@ function AppContent() {
|
|||||||
className="w-full max-w-md"
|
className="w-full max-w-md"
|
||||||
>
|
>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
New Claude Code session
|
{t('newClaudeCodeSession')}
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -415,7 +417,7 @@ function AppContent() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="py-8 text-center">
|
<div className="py-8 text-center">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
No projects found in ~/.claude/projects
|
{t('noProjectsFound')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -508,7 +510,7 @@ function AppContent() {
|
|||||||
open={showClaudeBinaryDialog}
|
open={showClaudeBinaryDialog}
|
||||||
onOpenChange={setShowClaudeBinaryDialog}
|
onOpenChange={setShowClaudeBinaryDialog}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
setToast({ message: "Claude binary path saved successfully", type: "success" });
|
setToast({ message: t('claudeBinaryPathSaved'), type: "success" });
|
||||||
// Trigger a refresh of the Claude version check
|
// Trigger a refresh of the Claude version check
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}}
|
}}
|
||||||
|
@@ -21,6 +21,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
|||||||
import { Toast } from '@/components/ui/toast';
|
import { Toast } from '@/components/ui/toast';
|
||||||
import { api, type Agent, type AgentRunWithMetrics } from '@/lib/api';
|
import { api, type Agent, type AgentRunWithMetrics } from '@/lib/api';
|
||||||
import { useTabState } from '@/hooks/useTabState';
|
import { useTabState } from '@/hooks/useTabState';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import { formatISOTimestamp } from '@/lib/date-utils';
|
import { formatISOTimestamp } from '@/lib/date-utils';
|
||||||
import { open as openDialog, save } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog, save } from '@tauri-apps/plugin-dialog';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
@@ -32,6 +33,7 @@ interface AgentsModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange }) => {
|
export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState('agents');
|
const [activeTab, setActiveTab] = useState('agents');
|
||||||
const [agents, setAgents] = useState<Agent[]>([]);
|
const [agents, setAgents] = useState<Agent[]>([]);
|
||||||
const [runningAgents, setRunningAgents] = useState<AgentRunWithMetrics[]>([]);
|
const [runningAgents, setRunningAgents] = useState<AgentRunWithMetrics[]>([]);
|
||||||
@@ -201,18 +203,18 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
|||||||
<DialogHeader className="px-6 pt-6">
|
<DialogHeader className="px-6 pt-6">
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Bot className="w-5 h-5" />
|
<Bot className="w-5 h-5" />
|
||||||
Agent Management
|
{t('agents.agentManagement')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Create new agents or manage running agent executions
|
{t('agents.createNewOrManageAgents')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||||
<TabsList className="mx-6">
|
<TabsList className="mx-6">
|
||||||
<TabsTrigger value="agents">Available Agents</TabsTrigger>
|
<TabsTrigger value="agents">{t('agents.availableAgents')}</TabsTrigger>
|
||||||
<TabsTrigger value="running" className="relative">
|
<TabsTrigger value="running" className="relative">
|
||||||
Running Agents
|
{t('agents.runningAgents')}
|
||||||
{runningAgents.length > 0 && (
|
{runningAgents.length > 0 && (
|
||||||
<Badge variant="secondary" className="ml-2 h-5 px-1.5">
|
<Badge variant="secondary" className="ml-2 h-5 px-1.5">
|
||||||
{runningAgents.length}
|
{runningAgents.length}
|
||||||
@@ -257,9 +259,9 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
|||||||
) : agents.length === 0 ? (
|
) : agents.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||||
<Bot className="w-12 h-12 text-muted-foreground mb-4" />
|
<Bot className="w-12 h-12 text-muted-foreground mb-4" />
|
||||||
<p className="text-lg font-medium mb-2">No agents available</p>
|
<p className="text-lg font-medium mb-2">{t('agents.noAgentsAvailable')}</p>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Create your first agent to get started
|
{t('agents.createFirstAgentToGetStarted')}
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => {
|
<Button onClick={() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
|
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { analytics } from '@/lib/analytics';
|
import { analytics } from '@/lib/analytics';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface AnalyticsConsentProps {
|
interface AnalyticsConsentProps {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
@@ -18,6 +19,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
onComplete,
|
onComplete,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [internalOpen, setInternalOpen] = useState(false);
|
const [internalOpen, setInternalOpen] = useState(false);
|
||||||
const [hasShownConsent, setHasShownConsent] = useState(false);
|
const [hasShownConsent, setHasShownConsent] = useState(false);
|
||||||
|
|
||||||
@@ -70,10 +72,10 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
<div className="p-2 bg-purple-100 dark:bg-purple-900/20 rounded-lg">
|
<div className="p-2 bg-purple-100 dark:bg-purple-900/20 rounded-lg">
|
||||||
<BarChart3 className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
<BarChart3 className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<DialogTitle className="text-2xl">Help Improve Claudia</DialogTitle>
|
<DialogTitle className="text-2xl">{t('analytics.helpImproveClaudia')}</DialogTitle>
|
||||||
</div>
|
</div>
|
||||||
<DialogDescription className="text-base mt-2">
|
<DialogDescription className="text-base mt-2">
|
||||||
We'd like to collect anonymous usage data to improve your experience.
|
{t('analytics.collectAnonymousData')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,12 +86,12 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Check className="h-5 w-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
<Check className="h-5 w-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="font-medium text-green-900 dark:text-green-100">What we collect:</p>
|
<p className="font-medium text-green-900 dark:text-green-100">{t('analytics.whatWeCollect')}</p>
|
||||||
<ul className="text-sm text-green-800 dark:text-green-200 space-y-1">
|
<ul className="text-sm text-green-800 dark:text-green-200 space-y-1">
|
||||||
<li>• Feature usage (which tools and commands you use)</li>
|
<li>• {t('analytics.featureUsageDesc')}</li>
|
||||||
<li>• Performance metrics (app speed and reliability)</li>
|
<li>• {t('analytics.performanceMetricsDesc')}</li>
|
||||||
<li>• Error reports (to fix bugs and improve stability)</li>
|
<li>• {t('analytics.errorReportsDesc')}</li>
|
||||||
<li>• General usage patterns (session frequency and duration)</li>
|
<li>• {t('analytics.usagePatternsDesc')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,13 +101,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="font-medium text-blue-900 dark:text-blue-100">Your privacy is protected:</p>
|
<p className="font-medium text-blue-900 dark:text-blue-100">{t('analytics.privacyProtected')}</p>
|
||||||
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||||
<li>• No personal information is collected</li>
|
<li>• {t('analytics.noPersonalInfo')}</li>
|
||||||
<li>• No file contents, paths, or project names</li>
|
<li>• {t('analytics.noFileContents')}</li>
|
||||||
<li>• No API keys or sensitive data</li>
|
<li>• {t('analytics.noApiKeys')}</li>
|
||||||
<li>• Completely anonymous with random IDs</li>
|
<li>• {t('analytics.anonymousData')}</li>
|
||||||
<li>• You can opt-out anytime in Settings</li>
|
<li>• {t('analytics.canOptOut')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,8 +118,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
<div className="flex gap-2 items-start">
|
<div className="flex gap-2 items-start">
|
||||||
<Info className="h-4 w-4 text-gray-500 flex-shrink-0 mt-0.5" />
|
<Info className="h-4 w-4 text-gray-500 flex-shrink-0 mt-0.5" />
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
This data helps us understand which features are most valuable, identify performance
|
{t('analytics.dataHelpsUs')}
|
||||||
issues, and prioritize improvements. Your choice won't affect any functionality.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,13 +130,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
No Thanks
|
{t('analytics.noThanks')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAccept}
|
onClick={handleAccept}
|
||||||
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
||||||
>
|
>
|
||||||
Allow Analytics
|
{t('analytics.allowAnalytics')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
@@ -40,6 +40,7 @@ import { AgentExecution } from "./AgentExecution";
|
|||||||
import { AgentRunsList } from "./AgentRunsList";
|
import { AgentRunsList } from "./AgentRunsList";
|
||||||
import { GitHubAgentBrowser } from "./GitHubAgentBrowser";
|
import { GitHubAgentBrowser } from "./GitHubAgentBrowser";
|
||||||
import { ICON_MAP } from "./IconPicker";
|
import { ICON_MAP } from "./IconPicker";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface CCAgentsProps {
|
interface CCAgentsProps {
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +65,7 @@ export type AgentIconName = keyof typeof AGENT_ICONS;
|
|||||||
* <CCAgents onBack={() => setView('home')} />
|
* <CCAgents onBack={() => setView('home')} />
|
||||||
*/
|
*/
|
||||||
export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [agents, setAgents] = useState<Agent[]>([]);
|
const [agents, setAgents] = useState<Agent[]>([]);
|
||||||
const [runs, setRuns] = useState<AgentRunWithMetrics[]>([]);
|
const [runs, setRuns] = useState<AgentRunWithMetrics[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -94,8 +96,8 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
setAgents(agentsList);
|
setAgents(agentsList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load agents:", err);
|
console.error("Failed to load agents:", err);
|
||||||
setError("Failed to load agents");
|
setError(t('agents.loadAgentsFailed'));
|
||||||
setToast({ message: "Failed to load agents", type: "error" });
|
setToast({ message: t('agents.loadAgentsFailed'), type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -132,12 +134,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
try {
|
try {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
await api.deleteAgent(agentToDelete.id);
|
await api.deleteAgent(agentToDelete.id);
|
||||||
setToast({ message: "Agent deleted successfully", type: "success" });
|
setToast({ message: t('agents.agentDeleted'), type: "success" });
|
||||||
await loadAgents();
|
await loadAgents();
|
||||||
await loadRuns(); // Reload runs as they might be affected
|
await loadRuns(); // Reload runs as they might be affected
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to delete agent:", err);
|
console.error("Failed to delete agent:", err);
|
||||||
setToast({ message: "Failed to delete agent", type: "error" });
|
setToast({ message: t('agents.deleteFailed'), type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setShowDeleteDialog(false);
|
setShowDeleteDialog(false);
|
||||||
@@ -166,13 +168,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
const handleAgentCreated = async () => {
|
const handleAgentCreated = async () => {
|
||||||
setView("list");
|
setView("list");
|
||||||
await loadAgents();
|
await loadAgents();
|
||||||
setToast({ message: "Agent created successfully", type: "success" });
|
setToast({ message: t('agents.agentCreated'), type: "success" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAgentUpdated = async () => {
|
const handleAgentUpdated = async () => {
|
||||||
setView("list");
|
setView("list");
|
||||||
await loadAgents();
|
await loadAgents();
|
||||||
setToast({ message: "Agent updated successfully", type: "success" });
|
setToast({ message: t('agents.agentUpdated'), type: "success" });
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleRunClick = (run: AgentRunWithMetrics) => {
|
// const handleRunClick = (run: AgentRunWithMetrics) => {
|
||||||
@@ -209,10 +211,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
filePath
|
filePath
|
||||||
});
|
});
|
||||||
|
|
||||||
setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" });
|
setToast({ message: t('agents.exportedSuccessfully', { name: agent.name }), type: "success" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to export agent:", err);
|
console.error("Failed to export agent:", err);
|
||||||
setToast({ message: "Failed to export agent", type: "error" });
|
setToast({ message: t('agents.exportFailed'), type: "error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,11 +237,11 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
// Import the agent from the selected file
|
// Import the agent from the selected file
|
||||||
await api.importAgentFromFile(filePath as string);
|
await api.importAgentFromFile(filePath as string);
|
||||||
|
|
||||||
setToast({ message: "Agent imported successfully", type: "success" });
|
setToast({ message: t('agents.importedSuccessfully'), type: "success" });
|
||||||
await loadAgents();
|
await loadAgents();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to import agent:", err);
|
console.error("Failed to import agent:", err);
|
||||||
const errorMessage = err instanceof Error ? err.message : "Failed to import agent";
|
const errorMessage = err instanceof Error ? err.message : t('agents.importFailed');
|
||||||
setToast({ message: errorMessage, type: "error" });
|
setToast({ message: errorMessage, type: "error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -308,9 +310,9 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">CC Agents</h1>
|
<h1 className="text-2xl font-bold">{t('navigation.agents')}</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Manage your Claude Code agents
|
{t('agents.manageAgents')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,18 +325,18 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
Import
|
{t('agents.import')}
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem onClick={handleImportAgent}>
|
<DropdownMenuItem onClick={handleImportAgent}>
|
||||||
<FileJson className="h-4 w-4 mr-2" />
|
<FileJson className="h-4 w-4 mr-2" />
|
||||||
From File
|
{t('agents.importFromFile')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setShowGitHubBrowser(true)}>
|
<DropdownMenuItem onClick={() => setShowGitHubBrowser(true)}>
|
||||||
<Globe className="h-4 w-4 mr-2" />
|
<Globe className="h-4 w-4 mr-2" />
|
||||||
From GitHub
|
{t('agents.importFromGitHub')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@@ -344,7 +346,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Create CC Agent
|
{t('agents.createAgent')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,13 +383,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
) : agents.length === 0 ? (
|
) : agents.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||||
<Bot className="h-16 w-16 text-muted-foreground mb-4" />
|
<Bot className="h-16 w-16 text-muted-foreground mb-4" />
|
||||||
<h3 className="text-lg font-medium mb-2">No agents yet</h3>
|
<h3 className="text-lg font-medium mb-2">{t('agents.noAgentsYet')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Create your first CC Agent to get started
|
{t('agents.createFirstAgent')}
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => setView("create")} size="default">
|
<Button onClick={() => setView("create")} size="default">
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Create CC Agent
|
{t('agents.createAgent')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -411,7 +413,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
{agent.name}
|
{agent.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Created: {new Date(agent.created_at).toLocaleDateString()}
|
{t('agents.created')}: {new Date(agent.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="p-4 pt-0 flex justify-center gap-1 flex-wrap">
|
<CardFooter className="p-4 pt-0 flex justify-center gap-1 flex-wrap">
|
||||||
@@ -420,20 +422,20 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleExecuteAgent(agent)}
|
onClick={() => handleExecuteAgent(agent)}
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
title="Execute agent"
|
title={t('agents.executeAgentTitle')}
|
||||||
>
|
>
|
||||||
<Play className="h-3 w-3" />
|
<Play className="h-3 w-3" />
|
||||||
Execute
|
{t('agents.execute')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleEditAgent(agent)}
|
onClick={() => handleEditAgent(agent)}
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
title="Edit agent"
|
title={t('agents.editAgent')}
|
||||||
>
|
>
|
||||||
<Edit className="h-3 w-3" />
|
<Edit className="h-3 w-3" />
|
||||||
Edit
|
{t('app.edit')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -443,17 +445,17 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
title="Export agent to .claudia.json"
|
title="Export agent to .claudia.json"
|
||||||
>
|
>
|
||||||
<Upload className="h-3 w-3" />
|
<Upload className="h-3 w-3" />
|
||||||
Export
|
{t('agents.export')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleDeleteAgent(agent)}
|
onClick={() => handleDeleteAgent(agent)}
|
||||||
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
||||||
title="Delete agent"
|
title={t('agents.deleteAgent')}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
Delete
|
{t('app.delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -471,10 +473,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
>
|
>
|
||||||
Previous
|
{t('app.previous')}
|
||||||
</Button>
|
</Button>
|
||||||
<span className="flex items-center px-3 text-sm">
|
<span className="flex items-center px-3 text-sm">
|
||||||
Page {currentPage} of {totalPages}
|
{t('app.page')} {currentPage} {t('app.of')} {totalPages}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -482,7 +484,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
>
|
>
|
||||||
Next
|
{t('app.next')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -495,7 +497,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<History className="h-5 w-5 text-muted-foreground" />
|
<History className="h-5 w-5 text-muted-foreground" />
|
||||||
<h2 className="text-lg font-semibold">Recent Executions</h2>
|
<h2 className="text-lg font-semibold">{t('agents.recentExecutions')}</h2>
|
||||||
</div>
|
</div>
|
||||||
{runsLoading ? (
|
{runsLoading ? (
|
||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
@@ -531,7 +533,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
onImportSuccess={async () => {
|
onImportSuccess={async () => {
|
||||||
setShowGitHubBrowser(false);
|
setShowGitHubBrowser(false);
|
||||||
await loadAgents();
|
await loadAgents();
|
||||||
setToast({ message: "Agent imported successfully from GitHub", type: "success" });
|
setToast({ message: t('agents.importFromGitHubSuccess'), type: "success" });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -541,11 +543,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Trash2 className="h-5 w-5 text-destructive" />
|
<Trash2 className="h-5 w-5 text-destructive" />
|
||||||
Delete Agent
|
{t('agents.deleteAgentTitle')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Are you sure you want to delete the agent "{agentToDelete?.name}"?
|
{t('agents.deleteConfirmation', { name: agentToDelete?.name })}
|
||||||
This action cannot be undone and will permanently remove the agent and all its associated data.
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2">
|
<DialogFooter className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2">
|
||||||
@@ -555,7 +556,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -566,12 +567,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
{isDeleting ? (
|
{isDeleting ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
|
||||||
Deleting...
|
{t('agents.deleting')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Trash2 className="h-4 w-4 mr-2" />
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
Delete Agent
|
{t('agents.deleteAgentButton')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -14,6 +14,7 @@ import { SelectComponent, type SelectOption } from "@/components/ui/select";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api, type CheckpointStrategy } from "@/lib/api";
|
import { api, type CheckpointStrategy } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface CheckpointSettingsProps {
|
interface CheckpointSettingsProps {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -40,6 +41,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [autoCheckpointEnabled, setAutoCheckpointEnabled] = useState(true);
|
const [autoCheckpointEnabled, setAutoCheckpointEnabled] = useState(true);
|
||||||
const [checkpointStrategy, setCheckpointStrategy] = useState<CheckpointStrategy>("smart");
|
const [checkpointStrategy, setCheckpointStrategy] = useState<CheckpointStrategy>("smart");
|
||||||
const [totalCheckpoints, setTotalCheckpoints] = useState(0);
|
const [totalCheckpoints, setTotalCheckpoints] = useState(0);
|
||||||
@@ -50,10 +52,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
const strategyOptions: SelectOption[] = [
|
const strategyOptions: SelectOption[] = [
|
||||||
{ value: "manual", label: "Manual Only" },
|
{ value: "manual", label: t('checkpoint.manualOnly') },
|
||||||
{ value: "per_prompt", label: "After Each Prompt" },
|
{ value: "per_prompt", label: t('checkpoint.afterEachPrompt') },
|
||||||
{ value: "per_tool_use", label: "After Tool Use" },
|
{ value: "per_tool_use", label: t('checkpoint.afterToolUse') },
|
||||||
{ value: "smart", label: "Smart (Recommended)" },
|
{ value: "smart", label: t('checkpoint.smart') },
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,7 +73,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
setTotalCheckpoints(settings.total_checkpoints);
|
setTotalCheckpoints(settings.total_checkpoints);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load checkpoint settings:", err);
|
console.error("Failed to load checkpoint settings:", err);
|
||||||
setError("Failed to load checkpoint settings");
|
setError(t('checkpoint.checkpointSettingsFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -91,11 +93,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
checkpointStrategy
|
checkpointStrategy
|
||||||
);
|
);
|
||||||
|
|
||||||
setSuccessMessage("Settings saved successfully");
|
setSuccessMessage(t('messages.saveSuccess'));
|
||||||
setTimeout(() => setSuccessMessage(null), 3000);
|
setTimeout(() => setSuccessMessage(null), 3000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save checkpoint settings:", err);
|
console.error("Failed to save checkpoint settings:", err);
|
||||||
setError("Failed to save checkpoint settings");
|
setError(t('checkpoint.saveCheckpointSettingsFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
@@ -114,14 +116,14 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
keepCount
|
keepCount
|
||||||
);
|
);
|
||||||
|
|
||||||
setSuccessMessage(`Removed ${removed} old checkpoints`);
|
setSuccessMessage(t('checkpoint.removedOldCheckpoints', { count: removed }));
|
||||||
setTimeout(() => setSuccessMessage(null), 3000);
|
setTimeout(() => setSuccessMessage(null), 3000);
|
||||||
|
|
||||||
// Reload settings to get updated count
|
// Reload settings to get updated count
|
||||||
await loadSettings();
|
await loadSettings();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to cleanup checkpoints:", err);
|
console.error("Failed to cleanup checkpoints:", err);
|
||||||
setError("Failed to cleanup checkpoints");
|
setError(t('checkpoint.cleanupCheckpointsFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -137,11 +139,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Settings className="h-5 w-5" />
|
<Settings className="h-5 w-5" />
|
||||||
<h3 className="text-lg font-semibold">Checkpoint Settings</h3>
|
<h3 className="text-lg font-semibold">{t('checkpoint.checkpointSettingsTitle')}</h3>
|
||||||
</div>
|
</div>
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||||
Close
|
{t('app.close')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -151,9 +153,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<AlertCircle className="h-4 w-4 text-yellow-600 mt-0.5" />
|
<AlertCircle className="h-4 w-4 text-yellow-600 mt-0.5" />
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
<p className="font-medium text-yellow-600">Experimental Feature</p>
|
<p className="font-medium text-yellow-600">{t('checkpoint.experimentalFeature')}</p>
|
||||||
<p className="text-yellow-600/80">
|
<p className="text-yellow-600/80">
|
||||||
Checkpointing may affect directory structure or cause data loss. Use with caution.
|
{t('checkpoint.checkpointWarning')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,9 +188,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
{/* Auto-checkpoint toggle */}
|
{/* Auto-checkpoint toggle */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="auto-checkpoint">Automatic Checkpoints</Label>
|
<Label htmlFor="auto-checkpoint">{t('checkpoint.automaticCheckpoints')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Automatically create checkpoints based on the selected strategy
|
{t('checkpoint.automaticCheckpointsDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -201,7 +203,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
|
|
||||||
{/* Checkpoint strategy */}
|
{/* Checkpoint strategy */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="strategy">Checkpoint Strategy</Label>
|
<Label htmlFor="strategy">{t('checkpoint.checkpointStrategy')}</Label>
|
||||||
<SelectComponent
|
<SelectComponent
|
||||||
value={checkpointStrategy}
|
value={checkpointStrategy}
|
||||||
onValueChange={(value: string) => setCheckpointStrategy(value as CheckpointStrategy)}
|
onValueChange={(value: string) => setCheckpointStrategy(value as CheckpointStrategy)}
|
||||||
@@ -209,10 +211,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
disabled={isLoading || !autoCheckpointEnabled}
|
disabled={isLoading || !autoCheckpointEnabled}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{checkpointStrategy === "manual" && "Checkpoints will only be created manually"}
|
{checkpointStrategy === "manual" && t('checkpoint.manualOnlyDesc')}
|
||||||
{checkpointStrategy === "per_prompt" && "A checkpoint will be created after each user prompt"}
|
{checkpointStrategy === "per_prompt" && t('checkpoint.afterEachPromptDesc')}
|
||||||
{checkpointStrategy === "per_tool_use" && "A checkpoint will be created after each tool use"}
|
{checkpointStrategy === "per_tool_use" && t('checkpoint.afterToolUseDesc')}
|
||||||
{checkpointStrategy === "smart" && "Checkpoints will be created after destructive operations"}
|
{checkpointStrategy === "smart" && t('checkpoint.smartDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -225,12 +227,12 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<>
|
<>
|
||||||
<Save className="h-4 w-4 mr-2 animate-spin" />
|
<Save className="h-4 w-4 mr-2 animate-spin" />
|
||||||
Saving...
|
{t('checkpoint.saving')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Save className="h-4 w-4 mr-2" />
|
<Save className="h-4 w-4 mr-2" />
|
||||||
Save Settings
|
{t('checkpoint.saveSettings')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -239,9 +241,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
<div className="border-t pt-6 space-y-4">
|
<div className="border-t pt-6 space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label>Storage Management</Label>
|
<Label>{t('checkpoint.storageManagement')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Total checkpoints: {totalCheckpoints}
|
{t('checkpoint.totalCheckpoints')}: {totalCheckpoints}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
||||||
@@ -249,7 +251,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
|
|
||||||
{/* Cleanup settings */}
|
{/* Cleanup settings */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="keep-count">Keep Recent Checkpoints</Label>
|
<Label htmlFor="keep-count">{t('checkpoint.keepRecentCheckpoints')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="keep-count"
|
id="keep-count"
|
||||||
@@ -267,11 +269,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
|||||||
disabled={isLoading || totalCheckpoints <= keepCount}
|
disabled={isLoading || totalCheckpoints <= keepCount}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 mr-2" />
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
Clean Up
|
{t('checkpoint.cleanUp')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Remove old checkpoints, keeping only the most recent {keepCount}
|
{t('checkpoint.removeOldCheckpoints', { count: keepCount })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
import { ExternalLink, FileQuestion, Terminal, AlertCircle, Loader2 } from "lucide-react";
|
import { ExternalLink, FileQuestion, Terminal, AlertCircle, Loader2 } from "lucide-react";
|
||||||
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface ClaudeBinaryDialogProps {
|
interface ClaudeBinaryDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -13,6 +14,7 @@ interface ClaudeBinaryDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ClaudeBinaryDialogProps) {
|
export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ClaudeBinaryDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
|
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
|
||||||
const [isValidating, setIsValidating] = useState(false);
|
const [isValidating, setIsValidating] = useState(false);
|
||||||
const [hasInstallations, setHasInstallations] = useState(true);
|
const [hasInstallations, setHasInstallations] = useState(true);
|
||||||
@@ -39,7 +41,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!selectedInstallation) {
|
if (!selectedInstallation) {
|
||||||
onError("Please select a Claude installation");
|
onError(t('pleaseSelectInstallation'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,29 +64,27 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<FileQuestion className="w-5 h-5" />
|
<FileQuestion className="w-5 h-5" />
|
||||||
Select Claude Code Installation
|
{t('selectClaudeCodeInstallation')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="space-y-3 mt-4">
|
<DialogDescription className="space-y-3 mt-4">
|
||||||
{checkingInstallations ? (
|
{checkingInstallations ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="flex items-center justify-center py-8">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
<span className="ml-2 text-sm text-muted-foreground">Searching for Claude installations...</span>
|
<span className="ml-2 text-sm text-muted-foreground">{t('searchingInstallations')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : hasInstallations ? (
|
) : hasInstallations ? (
|
||||||
<p>
|
<p>
|
||||||
Multiple Claude Code installations were found on your system.
|
{t('multipleInstallationsFound')}
|
||||||
Please select which one you'd like to use.
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Claude Code was not found in any of the common installation locations.
|
{t('claudeCodeNotFoundDialog')}
|
||||||
Please install Claude Code to continue.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||||
<AlertCircle className="w-4 h-4 text-muted-foreground" />
|
<AlertCircle className="w-4 h-4 text-muted-foreground" />
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="font-medium">Searched locations:</span> PATH, /usr/local/bin,
|
<span className="font-medium">{t('searchedLocations')}:</span> PATH, /usr/local/bin,
|
||||||
/opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin
|
/opt/homebrew/bin, ~/.nvm/versions/node/*/bin, ~/.claude/local, ~/.local/bin
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||||
<Terminal className="w-4 h-4 text-muted-foreground" />
|
<Terminal className="w-4 h-4 text-muted-foreground" />
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="font-medium">Tip:</span> You can install Claude Code using{" "}
|
<span className="font-medium">{t('validation.required')}:</span> {t('installationTip')}{" "}
|
||||||
<code className="px-1 py-0.5 bg-black/10 dark:bg-white/10 rounded">npm install -g @claude</code>
|
<code className="px-1 py-0.5 bg-black/10 dark:bg-white/10 rounded">npm install -g @claude</code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
|||||||
className="mr-auto"
|
className="mr-auto"
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4 mr-2" />
|
<ExternalLink className="w-4 h-4 mr-2" />
|
||||||
Installation Guide
|
{t('installationGuide')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onOpenChange(false)}
|
onClick={() => onOpenChange(false)}
|
||||||
disabled={isValidating}
|
disabled={isValidating}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isValidating || !selectedInstallation || !hasInstallations}
|
disabled={isValidating || !selectedInstallation || !hasInstallations}
|
||||||
>
|
>
|
||||||
{isValidating ? "Validating..." : hasInstallations ? "Save Selection" : "No Installations Found"}
|
{isValidating ? t('validating') : hasInstallations ? t('saveSelection') : t('noInstallationsFound')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||||
import { api, type ClaudeMdFile } from "@/lib/api";
|
import { api, type ClaudeMdFile } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface ClaudeFileEditorProps {
|
interface ClaudeFileEditorProps {
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +37,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [content, setContent] = useState<string>("");
|
const [content, setContent] = useState<string>("");
|
||||||
const [originalContent, setOriginalContent] = useState<string>("");
|
const [originalContent, setOriginalContent] = useState<string>("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -59,7 +61,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
setOriginalContent(fileContent);
|
setOriginalContent(fileContent);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load file:", err);
|
console.error("Failed to load file:", err);
|
||||||
setError("Failed to load CLAUDE.md file");
|
setError(t('loadFileFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -72,11 +74,11 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
setToast(null);
|
setToast(null);
|
||||||
await api.saveClaudeMdFile(file.absolute_path, content);
|
await api.saveClaudeMdFile(file.absolute_path, content);
|
||||||
setOriginalContent(content);
|
setOriginalContent(content);
|
||||||
setToast({ message: "File saved successfully", type: "success" });
|
setToast({ message: t('fileSavedSuccess'), type: "success" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save file:", err);
|
console.error("Failed to save file:", err);
|
||||||
setError("Failed to save CLAUDE.md file");
|
setError(t('saveFileFailed'));
|
||||||
setToast({ message: "Failed to save file", type: "error" });
|
setToast({ message: t('saveFileFailed'), type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -85,7 +87,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
const confirmLeave = window.confirm(
|
const confirmLeave = window.confirm(
|
||||||
"You have unsaved changes. Are you sure you want to leave?"
|
t('unsavedChangesConfirm')
|
||||||
);
|
);
|
||||||
if (!confirmLeave) return;
|
if (!confirmLeave) return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h2 className="text-lg font-semibold truncate">{file.relative_path}</h2>
|
<h2 className="text-lg font-semibold truncate">{file.relative_path}</h2>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Edit project-specific Claude Code system prompt
|
{t('editProjectSpecificPrompt')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +131,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<Save className="mr-2 h-4 w-4" />
|
<Save className="mr-2 h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
{saving ? "Saving..." : "Save"}
|
{saving ? t('saving') : t('app.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { api, type ClaudeInstallation } from "@/lib/api";
|
import { api, type ClaudeInstallation } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CheckCircle, HardDrive, Settings } from "lucide-react";
|
import { CheckCircle, HardDrive, Settings } from "lucide-react";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface ClaudeVersionSelectorProps {
|
interface ClaudeVersionSelectorProps {
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +54,7 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
|||||||
onSave,
|
onSave,
|
||||||
isSaving = false,
|
isSaving = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [installations, setInstallations] = useState<ClaudeInstallation[]>([]);
|
const [installations, setInstallations] = useState<ClaudeInstallation[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -132,8 +134,8 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Claude Code Installation</CardTitle>
|
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||||
<CardDescription>Loading available installations...</CardDescription>
|
<CardDescription>{t('settings.loadingAvailableInstallations')}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex items-center justify-center py-4">
|
<div className="flex items-center justify-center py-4">
|
||||||
@@ -148,13 +150,13 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Claude Code Installation</CardTitle>
|
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||||
<CardDescription>Error loading installations</CardDescription>
|
<CardDescription>{t('settings.errorLoadingInstallations')}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-sm text-destructive mb-4">{error}</div>
|
<div className="text-sm text-destructive mb-4">{error}</div>
|
||||||
<Button onClick={loadInstallations} variant="outline" size="sm">
|
<Button onClick={loadInstallations} variant="outline" size="sm">
|
||||||
Retry
|
{t('app.retry')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -169,16 +171,16 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<CheckCircle className="h-5 w-5" />
|
<CheckCircle className="h-5 w-5" />
|
||||||
Claude Code Installation
|
{t('settings.claudeCodeInstallation')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Choose your preferred Claude Code installation.
|
{t('settings.choosePreferredInstallation')}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Available Installations */}
|
{/* Available Installations */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Label className="text-sm font-medium">Available Installations</Label>
|
<Label className="text-sm font-medium">{t('settings.availableInstallations')}</Label>
|
||||||
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
|
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select Claude installation">
|
<SelectValue placeholder="Select Claude installation">
|
||||||
|
@@ -59,12 +59,12 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!name.trim()) {
|
if (!name.trim()) {
|
||||||
setError("Agent name is required");
|
setError(t('agents.agentNameRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!systemPrompt.trim()) {
|
if (!systemPrompt.trim()) {
|
||||||
setError("System prompt is required");
|
setError(t('agents.systemPromptRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,24 +181,24 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium mb-4">Basic Information</h3>
|
<h3 className="text-sm font-medium mb-4">{t('agents.basicInformation')}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Name and Icon */}
|
{/* Name and Icon */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="name">Agent Name</Label>
|
<Label htmlFor="name">{t('agents.agentName')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="e.g., Code Assistant"
|
placeholder={t('placeholders.enterAgentName')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Agent Icon</Label>
|
<Label>{t('agents.agentIcon')}</Label>
|
||||||
<div
|
<div
|
||||||
onClick={() => setShowIconPicker(true)}
|
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"
|
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"
|
||||||
@@ -221,7 +221,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
|
|
||||||
{/* Model Selection */}
|
{/* Model Selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Model</Label>
|
<Label>{t('agents.model')}</Label>
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<div className="flex flex-col sm:flex-row gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -245,7 +245,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-sm font-semibold">Claude 4 Sonnet</div>
|
<div className="text-sm font-semibold">Claude 4 Sonnet</div>
|
||||||
<div className="text-xs opacity-80">Faster, efficient for most tasks</div>
|
<div className="text-xs opacity-80">{t('agents.sonnetDescription')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -272,7 +272,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-sm font-semibold">Claude 4 Opus</div>
|
<div className="text-sm font-semibold">Claude 4 Opus</div>
|
||||||
<div className="text-xs opacity-80">More capable, better for complex tasks</div>
|
<div className="text-xs opacity-80">{t('agents.opusDescription')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -281,25 +281,25 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
|||||||
|
|
||||||
{/* Default Task */}
|
{/* Default Task */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="default-task">Default Task (Optional)</Label>
|
<Label htmlFor="default-task">{t('agents.defaultTask')} ({t('agents.optional')})</Label>
|
||||||
<Input
|
<Input
|
||||||
id="default-task"
|
id="default-task"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="e.g., Review this code for security issues"
|
placeholder={t('placeholders.enterDefaultTask')}
|
||||||
value={defaultTask}
|
value={defaultTask}
|
||||||
onChange={(e) => setDefaultTask(e.target.value)}
|
onChange={(e) => setDefaultTask(e.target.value)}
|
||||||
className="max-w-md"
|
className="max-w-md"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
This will be used as the default task placeholder when executing the agent
|
{t('agents.defaultTaskDescription')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* System Prompt Editor */}
|
{/* System Prompt Editor */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>System Prompt</Label>
|
<Label>{t('agents.systemPrompt')}</Label>
|
||||||
<p className="text-xs text-muted-foreground mb-2">
|
<p className="text-xs text-muted-foreground mb-2">
|
||||||
Define the behavior and capabilities of your CC Agent
|
{t('agents.systemPromptDescription')}
|
||||||
</p>
|
</p>
|
||||||
<div className="rounded-lg border border-border overflow-hidden shadow-sm" data-color-mode="dark">
|
<div className="rounded-lg border border-border overflow-hidden shadow-sm" data-color-mode="dark">
|
||||||
<MDEditor
|
<MDEditor
|
||||||
|
@@ -20,6 +20,7 @@ import { SlashCommandPicker } from "./SlashCommandPicker";
|
|||||||
import { ImagePreview } from "./ImagePreview";
|
import { ImagePreview } from "./ImagePreview";
|
||||||
import { type FileEntry, type SlashCommand } from "@/lib/api";
|
import { type FileEntry, type SlashCommand } from "@/lib/api";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface FloatingPromptInputProps {
|
interface FloatingPromptInputProps {
|
||||||
/**
|
/**
|
||||||
@@ -72,42 +73,7 @@ type ThinkingModeConfig = {
|
|||||||
phrase?: string; // The phrase to append
|
phrase?: string; // The phrase to append
|
||||||
};
|
};
|
||||||
|
|
||||||
const THINKING_MODES: ThinkingModeConfig[] = [
|
// Thinking modes configuration will be defined inside the component to use translations
|
||||||
{
|
|
||||||
id: "auto",
|
|
||||||
name: "Auto",
|
|
||||||
description: "Let Claude decide",
|
|
||||||
level: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "think",
|
|
||||||
name: "Think",
|
|
||||||
description: "Basic reasoning",
|
|
||||||
level: 1,
|
|
||||||
phrase: "think"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "think_hard",
|
|
||||||
name: "Think Hard",
|
|
||||||
description: "Deeper analysis",
|
|
||||||
level: 2,
|
|
||||||
phrase: "think hard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "think_harder",
|
|
||||||
name: "Think Harder",
|
|
||||||
description: "Extensive reasoning",
|
|
||||||
level: 3,
|
|
||||||
phrase: "think harder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ultrathink",
|
|
||||||
name: "Ultrathink",
|
|
||||||
description: "Maximum computation",
|
|
||||||
level: 4,
|
|
||||||
phrase: "ultrathink"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
|
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
|
||||||
@@ -173,6 +139,45 @@ const FloatingPromptInputInner = (
|
|||||||
}: FloatingPromptInputProps,
|
}: FloatingPromptInputProps,
|
||||||
ref: React.Ref<FloatingPromptInputRef>,
|
ref: React.Ref<FloatingPromptInputRef>,
|
||||||
) => {
|
) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// Define THINKING_MODES inside component to access translations
|
||||||
|
const THINKING_MODES: ThinkingModeConfig[] = [
|
||||||
|
{
|
||||||
|
id: "auto",
|
||||||
|
name: "Auto",
|
||||||
|
description: t('messages.letClaudeDecide'),
|
||||||
|
level: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "think",
|
||||||
|
name: "Think",
|
||||||
|
description: t('messages.basicReasoning'),
|
||||||
|
level: 1,
|
||||||
|
phrase: "think"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "think_hard",
|
||||||
|
name: "Think Hard",
|
||||||
|
description: t('messages.deeperAnalysis'),
|
||||||
|
level: 2,
|
||||||
|
phrase: "think hard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "think_harder",
|
||||||
|
name: "Think Harder",
|
||||||
|
description: t('messages.extensiveReasoning'),
|
||||||
|
level: 3,
|
||||||
|
phrase: "think harder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ultrathink",
|
||||||
|
name: "Ultrathink",
|
||||||
|
description: t('messages.maximumAnalysis'),
|
||||||
|
level: 4,
|
||||||
|
phrase: "ultrathink"
|
||||||
|
}
|
||||||
|
];
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
const [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
||||||
const [selectedThinkingMode, setSelectedThinkingMode] = useState<ThinkingMode>("auto");
|
const [selectedThinkingMode, setSelectedThinkingMode] = useState<ThinkingMode>("auto");
|
||||||
@@ -758,7 +763,7 @@ const FloatingPromptInputInner = (
|
|||||||
value={prompt}
|
value={prompt}
|
||||||
onChange={handleTextChange}
|
onChange={handleTextChange}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
placeholder="Type your prompt here..."
|
placeholder={t('messages.typeYourPromptHere')}
|
||||||
className="min-h-[200px] resize-none"
|
className="min-h-[200px] resize-none"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onDragEnter={handleDrag}
|
onDragEnter={handleDrag}
|
||||||
@@ -1001,7 +1006,7 @@ const FloatingPromptInputInner = (
|
|||||||
onChange={handleTextChange}
|
onChange={handleTextChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
placeholder={dragActive ? "Drop images here..." : "Ask Claude anything..."}
|
placeholder={dragActive ? t('messages.dropImagesHere') : t('messages.askClaudeAnything')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-h-[44px] max-h-[120px] resize-none pr-10",
|
"min-h-[44px] max-h-[120px] resize-none pr-10",
|
||||||
@@ -1065,7 +1070,7 @@ const FloatingPromptInputInner = (
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 text-xs text-muted-foreground">
|
<div className="mt-2 text-xs text-muted-foreground">
|
||||||
Press Enter to send, Shift+Enter for new line{projectPath?.trim() && ", @ to mention files, / for commands, drag & drop or paste images"}
|
{t('input.pressEnterToSend')}{projectPath?.trim() && t('input.withFileAndCommandSupport')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -51,6 +51,7 @@ import {
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { HooksManager } from '@/lib/hooksManager';
|
import { HooksManager } from '@/lib/hooksManager';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import {
|
import {
|
||||||
HooksConfiguration,
|
HooksConfiguration,
|
||||||
HookEvent,
|
HookEvent,
|
||||||
@@ -116,6 +117,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
hideActions = false
|
hideActions = false
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [selectedEvent, setSelectedEvent] = useState<HookEvent>('PreToolUse');
|
const [selectedEvent, setSelectedEvent] = useState<HookEvent>('PreToolUse');
|
||||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
||||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||||
@@ -754,7 +756,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold">Hooks Configuration</h3>
|
<h3 className="text-lg font-semibold">{t('hooks.hooksConfiguration')}</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant={scope === 'project' ? 'secondary' : scope === 'local' ? 'outline' : 'default'}>
|
<Badge variant={scope === 'project' ? 'secondary' : scope === 'local' ? 'outline' : 'default'}>
|
||||||
{scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope
|
{scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope
|
||||||
@@ -789,12 +791,12 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Configure shell commands to execute at various points in Claude Code's lifecycle.
|
{t('hooks.configureShellCommands')}
|
||||||
{scope === 'local' && ' These settings are not committed to version control.'}
|
{scope === 'local' && t('hooks.localSettingsNote')}
|
||||||
</p>
|
</p>
|
||||||
{hasUnsavedChanges && !readOnly && (
|
{hasUnsavedChanges && !readOnly && (
|
||||||
<p className="text-sm text-amber-600">
|
<p className="text-sm text-amber-600">
|
||||||
You have unsaved changes. Click Save to persist them.
|
{t('hooks.unsavedChanges')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -8,6 +8,7 @@ import { SelectComponent } from "@/components/ui/select";
|
|||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { useTrackEvent } from "@/hooks";
|
import { useTrackEvent } from "@/hooks";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface MCPAddServerProps {
|
interface MCPAddServerProps {
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +35,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
onServerAdded,
|
onServerAdded,
|
||||||
onError,
|
onError,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [transport, setTransport] = useState<"stdio" | "sse">("stdio");
|
const [transport, setTransport] = useState<"stdio" | "sse">("stdio");
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
@@ -101,12 +103,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
*/
|
*/
|
||||||
const handleAddStdioServer = async () => {
|
const handleAddStdioServer = async () => {
|
||||||
if (!stdioName.trim()) {
|
if (!stdioName.trim()) {
|
||||||
onError("Server name is required");
|
onError(t('serverNameRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stdioCommand.trim()) {
|
if (!stdioCommand.trim()) {
|
||||||
onError("Command is required");
|
onError(t('commandRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
onError(result.message);
|
onError(result.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError("Failed to add server");
|
onError(t('failedToAddServer'));
|
||||||
console.error("Failed to add stdio server:", error);
|
console.error("Failed to add stdio server:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
@@ -164,12 +166,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
*/
|
*/
|
||||||
const handleAddSseServer = async () => {
|
const handleAddSseServer = async () => {
|
||||||
if (!sseName.trim()) {
|
if (!sseName.trim()) {
|
||||||
onError("Server name is required");
|
onError(t('serverNameRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sseUrl.trim()) {
|
if (!sseUrl.trim()) {
|
||||||
onError("URL is required");
|
onError(t('urlRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +213,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
onError(result.message);
|
onError(result.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError("Failed to add server");
|
onError(t('failedToAddServer'));
|
||||||
console.error("Failed to add SSE server:", error);
|
console.error("Failed to add SSE server:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
@@ -225,7 +227,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium">Environment Variables</Label>
|
<Label className="text-sm font-medium">{t('environmentVariables')}</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -233,7 +235,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
Add Variable
|
{t('addVariable')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -273,9 +275,9 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold">Add MCP Server</h3>
|
<h3 className="text-base font-semibold">{t('addMcpServer')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Configure a new Model Context Protocol server
|
{t('configureNewMcpServer')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -296,7 +298,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
<Card className="p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="stdio-name">Server Name</Label>
|
<Label htmlFor="stdio-name">{t('serverName')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="stdio-name"
|
id="stdio-name"
|
||||||
placeholder="my-server"
|
placeholder="my-server"
|
||||||
@@ -304,12 +306,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
onChange={(e) => setStdioName(e.target.value)}
|
onChange={(e) => setStdioName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
A unique name to identify this server
|
{t('uniqueNameToIdentify')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="stdio-command">Command</Label>
|
<Label htmlFor="stdio-command">{t('command')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="stdio-command"
|
id="stdio-command"
|
||||||
placeholder="/path/to/server"
|
placeholder="/path/to/server"
|
||||||
@@ -318,12 +320,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
The command to execute the server
|
{t('commandToExecuteServer')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="stdio-args">Arguments (optional)</Label>
|
<Label htmlFor="stdio-args">{t('argumentsOptional')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="stdio-args"
|
id="stdio-args"
|
||||||
placeholder="arg1 arg2 arg3"
|
placeholder="arg1 arg2 arg3"
|
||||||
@@ -332,19 +334,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Space-separated command arguments
|
{t('spaceSeparatedArgs')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="stdio-scope">Scope</Label>
|
<Label htmlFor="stdio-scope">{t('scope')}</Label>
|
||||||
<SelectComponent
|
<SelectComponent
|
||||||
value={stdioScope}
|
value={stdioScope}
|
||||||
onValueChange={(value: string) => setStdioScope(value)}
|
onValueChange={(value: string) => setStdioScope(value)}
|
||||||
options={[
|
options={[
|
||||||
{ value: "local", label: "Local (this project only)" },
|
{ value: "local", label: t('localProjectOnly') },
|
||||||
{ value: "project", label: "Project (shared via .mcp.json)" },
|
{ value: "project", label: t('projectSharedViaMcp') },
|
||||||
{ value: "user", label: "User (all projects)" },
|
{ value: "user", label: t('userAllProjects') },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -361,12 +363,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
{saving ? (
|
{saving ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Adding Server...
|
{t('addingServer')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Stdio Server
|
{t('addStdioServer')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -379,7 +381,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
<Card className="p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sse-name">Server Name</Label>
|
<Label htmlFor="sse-name">{t('serverName')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="sse-name"
|
id="sse-name"
|
||||||
placeholder="sse-server"
|
placeholder="sse-server"
|
||||||
@@ -387,12 +389,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
onChange={(e) => setSseName(e.target.value)}
|
onChange={(e) => setSseName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
A unique name to identify this server
|
{t('uniqueNameToIdentify')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sse-url">URL</Label>
|
<Label htmlFor="sse-url">{t('url')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="sse-url"
|
id="sse-url"
|
||||||
placeholder="https://example.com/sse-endpoint"
|
placeholder="https://example.com/sse-endpoint"
|
||||||
@@ -401,19 +403,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
className="font-mono"
|
className="font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
The SSE endpoint URL
|
{t('sseEndpointUrl')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sse-scope">Scope</Label>
|
<Label htmlFor="sse-scope">{t('scope')}</Label>
|
||||||
<SelectComponent
|
<SelectComponent
|
||||||
value={sseScope}
|
value={sseScope}
|
||||||
onValueChange={(value: string) => setSseScope(value)}
|
onValueChange={(value: string) => setSseScope(value)}
|
||||||
options={[
|
options={[
|
||||||
{ value: "local", label: "Local (this project only)" },
|
{ value: "local", label: t('localProjectOnly') },
|
||||||
{ value: "project", label: "Project (shared via .mcp.json)" },
|
{ value: "project", label: t('projectSharedViaMcp') },
|
||||||
{ value: "user", label: "User (all projects)" },
|
{ value: "user", label: t('userAllProjects') },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -430,12 +432,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
{saving ? (
|
{saving ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Adding Server...
|
{t('addingServer')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add SSE Server
|
{t('addSseServer')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -449,7 +451,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
<Info className="h-4 w-4 text-primary" />
|
<Info className="h-4 w-4 text-primary" />
|
||||||
<span>Example Commands</span>
|
<span>{t('exampleCommands')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 text-xs text-muted-foreground">
|
<div className="space-y-2 text-xs text-muted-foreground">
|
||||||
<div className="font-mono bg-background p-2 rounded">
|
<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 { MCPServerList } from "./MCPServerList";
|
||||||
import { MCPAddServer } from "./MCPAddServer";
|
import { MCPAddServer } from "./MCPAddServer";
|
||||||
import { MCPImportExport } from "./MCPImportExport";
|
import { MCPImportExport } from "./MCPImportExport";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface MCPManagerProps {
|
interface MCPManagerProps {
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +30,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState("servers");
|
const [activeTab, setActiveTab] = useState("servers");
|
||||||
const [servers, setServers] = useState<MCPServer[]>([]);
|
const [servers, setServers] = useState<MCPServer[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -55,7 +57,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
setServers(serverList);
|
setServers(serverList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("MCPManager: Failed to load MCP servers:", err);
|
console.error("MCPManager: Failed to load MCP servers:", err);
|
||||||
setError("Failed to load MCP servers. Make sure Claude Code is installed.");
|
setError(t('loadMcpServersFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -66,7 +68,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
*/
|
*/
|
||||||
const handleServerAdded = () => {
|
const handleServerAdded = () => {
|
||||||
loadServers();
|
loadServers();
|
||||||
setToast({ message: "MCP server added successfully!", type: "success" });
|
setToast({ message: t('mcpServerAdded'), type: "success" });
|
||||||
setActiveTab("servers");
|
setActiveTab("servers");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
*/
|
*/
|
||||||
const handleServerRemoved = (name: string) => {
|
const handleServerRemoved = (name: string) => {
|
||||||
setServers(prev => prev.filter(s => s.name !== name));
|
setServers(prev => prev.filter(s => s.name !== name));
|
||||||
setToast({ message: `Server "${name}" removed successfully!`, type: "success" });
|
setToast({ message: t('serverRemovedSuccess', { name }), type: "success" });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,12 +87,12 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
loadServers();
|
loadServers();
|
||||||
if (failed === 0) {
|
if (failed === 0) {
|
||||||
setToast({
|
setToast({
|
||||||
message: `Successfully imported ${imported} server${imported > 1 ? 's' : ''}!`,
|
message: t('importedServersSuccess', { count: imported, plural: imported > 1 ? 's' : '' }),
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setToast({
|
setToast({
|
||||||
message: `Imported ${imported} server${imported > 1 ? 's' : ''}, ${failed} failed`,
|
message: t('importedServersFailed', { imported, importedPlural: imported > 1 ? 's' : '', failed }),
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -118,10 +120,10 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Network className="h-5 w-5 text-blue-500" />
|
<Network className="h-5 w-5 text-blue-500" />
|
||||||
MCP Servers
|
{t('mcpServers')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Manage Model Context Protocol servers
|
{t('manageMcpServers')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,15 +155,15 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
|||||||
<TabsList className="grid w-full max-w-md grid-cols-3">
|
<TabsList className="grid w-full max-w-md grid-cols-3">
|
||||||
<TabsTrigger value="servers" className="gap-2">
|
<TabsTrigger value="servers" className="gap-2">
|
||||||
<Network className="h-4 w-4 text-blue-500" />
|
<Network className="h-4 w-4 text-blue-500" />
|
||||||
Servers
|
{t('servers')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="add" className="gap-2">
|
<TabsTrigger value="add" className="gap-2">
|
||||||
<Plus className="h-4 w-4 text-green-500" />
|
<Plus className="h-4 w-4 text-green-500" />
|
||||||
Add Server
|
{t('addServer')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="import" className="gap-2">
|
<TabsTrigger value="import" className="gap-2">
|
||||||
<Download className="h-4 w-4 text-purple-500" />
|
<Download className="h-4 w-4 text-purple-500" />
|
||||||
Import/Export
|
{t('importExport')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { api, type MCPServer } from "@/lib/api";
|
import { api, type MCPServer } from "@/lib/api";
|
||||||
import { useTrackEvent } from "@/hooks";
|
import { useTrackEvent } from "@/hooks";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface MCPServerListProps {
|
interface MCPServerListProps {
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +51,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
onServerRemoved,
|
onServerRemoved,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [removingServer, setRemovingServer] = useState<string | null>(null);
|
const [removingServer, setRemovingServer] = useState<string | null>(null);
|
||||||
const [testingServer, setTestingServer] = useState<string | null>(null);
|
const [testingServer, setTestingServer] = useState<string | null>(null);
|
||||||
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
||||||
@@ -184,11 +186,11 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
const getScopeDisplayName = (scope: string) => {
|
const getScopeDisplayName = (scope: string) => {
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case "local":
|
case "local":
|
||||||
return "Local (Project-specific)";
|
return t('localProjectSpecific');
|
||||||
case "project":
|
case "project":
|
||||||
return "Project (Shared via .mcp.json)";
|
return t('projectSharedMcp');
|
||||||
case "user":
|
case "user":
|
||||||
return "User (All projects)";
|
return t('userAllProjects');
|
||||||
default:
|
default:
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
@@ -220,7 +222,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
{server.status?.running && (
|
{server.status?.running && (
|
||||||
<Badge variant="outline" className="gap-1 flex-shrink-0 border-green-500/50 text-green-600 bg-green-500/10">
|
<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" />
|
<CheckCircle className="h-3 w-3" />
|
||||||
Running
|
{t('running')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -237,7 +239,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||||
>
|
>
|
||||||
<ChevronDown className="h-3 w-3 mr-1" />
|
<ChevronDown className="h-3 w-3 mr-1" />
|
||||||
Show full
|
{t('showFull')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -299,7 +301,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
{server.command && (
|
{server.command && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-xs font-medium text-muted-foreground">Command</p>
|
<p className="text-xs font-medium text-muted-foreground">{t('command')}</p>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -308,7 +310,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||||
>
|
>
|
||||||
<Copy className="h-3 w-3 mr-1" />
|
<Copy className="h-3 w-3 mr-1" />
|
||||||
{isCopied ? "Copied!" : "Copy"}
|
{isCopied ? t('copied') : t('copy')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -317,7 +319,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||||
>
|
>
|
||||||
<ChevronUp className="h-3 w-3 mr-1" />
|
<ChevronUp className="h-3 w-3 mr-1" />
|
||||||
Hide
|
{t('hide')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +331,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
|
|
||||||
{server.args && server.args.length > 0 && (
|
{server.args && server.args.length > 0 && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-medium text-muted-foreground">Arguments</p>
|
<p className="text-xs font-medium text-muted-foreground">{t('arguments')}</p>
|
||||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||||
{server.args.map((arg, idx) => (
|
{server.args.map((arg, idx) => (
|
||||||
<div key={idx} className="break-all">
|
<div key={idx} className="break-all">
|
||||||
@@ -343,7 +345,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
|
|
||||||
{server.transport === "sse" && server.url && (
|
{server.transport === "sse" && server.url && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-medium text-muted-foreground">URL</p>
|
<p className="text-xs font-medium text-muted-foreground">{t('url')}</p>
|
||||||
<p className="text-xs font-mono bg-muted/50 p-2 rounded break-all">
|
<p className="text-xs font-mono bg-muted/50 p-2 rounded break-all">
|
||||||
{server.url}
|
{server.url}
|
||||||
</p>
|
</p>
|
||||||
@@ -352,7 +354,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
|
|
||||||
{Object.keys(server.env).length > 0 && (
|
{Object.keys(server.env).length > 0 && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-medium text-muted-foreground">Environment Variables</p>
|
<p className="text-xs font-medium text-muted-foreground">{t('environmentVariables')}</p>
|
||||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||||
{Object.entries(server.env).map(([key, value]) => (
|
{Object.entries(server.env).map(([key, value]) => (
|
||||||
<div key={key} className="break-all">
|
<div key={key} className="break-all">
|
||||||
@@ -384,9 +386,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold">Configured Servers</h3>
|
<h3 className="text-base font-semibold">{t('configuredServers')}</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{servers.length} server{servers.length !== 1 ? "s" : ""} configured
|
{servers.length} {servers.length !== 1 ? t('servers') : 'server'} {t('serversConfigured')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -396,7 +398,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
className="gap-2 hover:bg-primary/10 hover:text-primary hover:border-primary/50"
|
className="gap-2 hover:bg-primary/10 hover:text-primary hover:border-primary/50"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4" />
|
<RefreshCw className="h-4 w-4" />
|
||||||
Refresh
|
{t('app.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -406,9 +408,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
|||||||
<div className="p-4 bg-primary/10 rounded-full mb-4">
|
<div className="p-4 bg-primary/10 rounded-full mb-4">
|
||||||
<Network className="h-12 w-12 text-primary" />
|
<Network className="h-12 w-12 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground mb-2 font-medium">No MCP servers configured</p>
|
<p className="text-muted-foreground mb-2 font-medium">{t('noMcpServersConfigured')}</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Add a server to get started with Model Context Protocol
|
{t('addServerToGetStarted')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface MarkdownEditorProps {
|
interface MarkdownEditorProps {
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +29,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [content, setContent] = useState<string>("");
|
const [content, setContent] = useState<string>("");
|
||||||
const [originalContent, setOriginalContent] = useState<string>("");
|
const [originalContent, setOriginalContent] = useState<string>("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -51,7 +53,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
setOriginalContent(prompt);
|
setOriginalContent(prompt);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load system prompt:", err);
|
console.error("Failed to load system prompt:", err);
|
||||||
setError("Failed to load CLAUDE.md file");
|
setError(t('loadClaudemdFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -64,11 +66,11 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
setToast(null);
|
setToast(null);
|
||||||
await api.saveSystemPrompt(content);
|
await api.saveSystemPrompt(content);
|
||||||
setOriginalContent(content);
|
setOriginalContent(content);
|
||||||
setToast({ message: "CLAUDE.md saved successfully", type: "success" });
|
setToast({ message: t('claudemdSavedSuccess'), type: "success" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save system prompt:", err);
|
console.error("Failed to save system prompt:", err);
|
||||||
setError("Failed to save CLAUDE.md file");
|
setError(t('saveClaudemdFailed'));
|
||||||
setToast({ message: "Failed to save CLAUDE.md", type: "error" });
|
setToast({ message: t('saveClaudemdFailed'), type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
const confirmLeave = window.confirm(
|
const confirmLeave = window.confirm(
|
||||||
"You have unsaved changes. Are you sure you want to leave?"
|
t('unsavedChangesConfirm')
|
||||||
);
|
);
|
||||||
if (!confirmLeave) return;
|
if (!confirmLeave) return;
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold">CLAUDE.md</h2>
|
<h2 className="text-lg font-semibold">CLAUDE.md</h2>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Edit your Claude Code system prompt
|
{t('editSystemPrompt')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,7 +123,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<Save className="mr-2 h-4 w-4" />
|
<Save className="mr-2 h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
{saving ? "Saving..." : "Save"}
|
{saving ? t('app.saving') : t('app.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
import type { Project } from "@/lib/api";
|
import type { Project } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatTimeAgo } from "@/lib/date-utils";
|
import { formatTimeAgo } from "@/lib/date-utils";
|
||||||
@@ -70,6 +71,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
onProjectSettings,
|
onProjectSettings,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate pagination
|
||||||
@@ -150,7 +152,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
<Settings className="h-4 w-4 mr-2" />
|
||||||
Hooks
|
{t('settings.hooks')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export interface ProxySettings {
|
export interface ProxySettings {
|
||||||
http_proxy: string | null;
|
http_proxy: string | null;
|
||||||
@@ -18,6 +19,7 @@ interface ProxySettingsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [settings, setSettings] = useState<ProxySettings>({
|
const [settings, setSettings] = useState<ProxySettings>({
|
||||||
http_proxy: null,
|
http_proxy: null,
|
||||||
https_proxy: null,
|
https_proxy: null,
|
||||||
@@ -43,13 +45,13 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
await invoke('save_proxy_settings', { settings });
|
await invoke('save_proxy_settings', { settings });
|
||||||
setOriginalSettings(settings);
|
setOriginalSettings(settings);
|
||||||
setToast({
|
setToast({
|
||||||
message: 'Proxy settings saved and applied successfully.',
|
message: t('settings.proxySettingsSaved'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save proxy settings:', error);
|
console.error('Failed to save proxy settings:', error);
|
||||||
setToast({
|
setToast({
|
||||||
message: 'Failed to save proxy settings',
|
message: t('settings.saveProxySettingsFailed'),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
throw error; // Re-throw to let parent handle the error
|
throw error; // Re-throw to let parent handle the error
|
||||||
@@ -72,7 +74,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load proxy settings:', error);
|
console.error('Failed to load proxy settings:', error);
|
||||||
setToast({
|
setToast({
|
||||||
message: 'Failed to load proxy settings',
|
message: t('settings.loadProxySettingsFailed'),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,18 +91,18 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-medium">Proxy Settings</h3>
|
<h3 className="text-lg font-medium">{t('settings.proxySettingsTitle')}</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Configure proxy settings for Claude API requests
|
{t('settings.proxySettingsDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="proxy-enabled">Enable Proxy</Label>
|
<Label htmlFor="proxy-enabled">{t('settings.enableProxy')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Use proxy for all Claude API requests
|
{t('settings.enableProxyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -112,7 +114,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
|
|
||||||
<div className="space-y-4" style={{ opacity: settings.enabled ? 1 : 0.5 }}>
|
<div className="space-y-4" style={{ opacity: settings.enabled ? 1 : 0.5 }}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="http-proxy">HTTP Proxy</Label>
|
<Label htmlFor="http-proxy">{t('settings.httpProxy')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="http-proxy"
|
id="http-proxy"
|
||||||
placeholder="http://proxy.example.com:8080"
|
placeholder="http://proxy.example.com:8080"
|
||||||
@@ -123,7 +125,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="https-proxy">HTTPS Proxy</Label>
|
<Label htmlFor="https-proxy">{t('settings.httpsProxy')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="https-proxy"
|
id="https-proxy"
|
||||||
placeholder="http://proxy.example.com:8080"
|
placeholder="http://proxy.example.com:8080"
|
||||||
@@ -134,7 +136,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="no-proxy">No Proxy</Label>
|
<Label htmlFor="no-proxy">{t('settings.noProxy')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="no-proxy"
|
id="no-proxy"
|
||||||
placeholder="localhost,127.0.0.1,.example.com"
|
placeholder="localhost,127.0.0.1,.example.com"
|
||||||
@@ -143,12 +145,12 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
disabled={!settings.enabled}
|
disabled={!settings.enabled}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Comma-separated list of hosts that should bypass the proxy
|
{t('settings.noProxyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="all-proxy">All Proxy (Optional)</Label>
|
<Label htmlFor="all-proxy">All Proxy ({t('agents.optional')})</Label>
|
||||||
<Input
|
<Input
|
||||||
id="all-proxy"
|
id="all-proxy"
|
||||||
placeholder="socks5://proxy.example.com:1080"
|
placeholder="socks5://proxy.example.com:1080"
|
||||||
@@ -157,7 +159,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
|||||||
disabled={!settings.enabled}
|
disabled={!settings.enabled}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Proxy URL to use for all protocols if protocol-specific proxies are not set
|
{t('settings.allProxyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -31,7 +31,7 @@ import { HooksEditor } from "./HooksEditor";
|
|||||||
import { SlashCommandsManager } from "./SlashCommandsManager";
|
import { SlashCommandsManager } from "./SlashCommandsManager";
|
||||||
import { ProxySettings } from "./ProxySettings";
|
import { ProxySettings } from "./ProxySettings";
|
||||||
import { AnalyticsConsent } from "./AnalyticsConsent";
|
import { AnalyticsConsent } from "./AnalyticsConsent";
|
||||||
import { useTheme, useTrackEvent } from "@/hooks";
|
import { useTheme, useTrackEvent, useTranslation } from "@/hooks";
|
||||||
import { analytics } from "@/lib/analytics";
|
import { analytics } from "@/lib/analytics";
|
||||||
|
|
||||||
interface SettingsProps {
|
interface SettingsProps {
|
||||||
@@ -64,6 +64,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [settings, setSettings] = useState<ClaudeSettings | null>(null);
|
const [settings, setSettings] = useState<ClaudeSettings | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -178,7 +179,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load settings:", err);
|
console.error("Failed to load settings:", err);
|
||||||
setError("Failed to load settings. Please ensure ~/.claude directory exists.");
|
setError(t('settings.messages.loadFailed'));
|
||||||
setSettings({});
|
setSettings({});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -232,11 +233,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
setProxySettingsChanged(false);
|
setProxySettingsChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setToast({ message: "Settings saved successfully!", type: "success" });
|
setToast({ message: t('settings.saveButton.settingsSavedSuccess'), type: "success" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save settings:", err);
|
console.error("Failed to save settings:", err);
|
||||||
setError("Failed to save settings.");
|
setError(t('settings.messages.saveFailed'));
|
||||||
setToast({ message: "Failed to save settings", type: "error" });
|
setToast({ message: t('settings.saveButton.settingsSaveFailed'), type: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
@@ -347,9 +348,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold">Settings</h2>
|
<h2 className="text-lg font-semibold">{t('settings.title')}</h2>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Configure Claude Code preferences
|
{t('settings.configurePreferences')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -363,12 +364,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{saving ? (
|
{saving ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Saving...
|
{t('settings.saveButton.saving')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Save className="h-4 w-4" />
|
<Save className="h-4 w-4" />
|
||||||
Save Settings
|
{t('settings.saveButton.saveSettings')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -398,55 +399,55 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
<TabsList className="grid grid-cols-9 w-full">
|
<TabsList className="grid grid-cols-9 w-full">
|
||||||
<TabsTrigger value="general">General</TabsTrigger>
|
<TabsTrigger value="general">{t('settings.general')}</TabsTrigger>
|
||||||
<TabsTrigger value="permissions">Permissions</TabsTrigger>
|
<TabsTrigger value="permissions">{t('settings.permissionsTab')}</TabsTrigger>
|
||||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
<TabsTrigger value="environment">{t('settings.environmentTab')}</TabsTrigger>
|
||||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
<TabsTrigger value="advanced">{t('settings.advancedTab')}</TabsTrigger>
|
||||||
<TabsTrigger value="hooks">Hooks</TabsTrigger>
|
<TabsTrigger value="hooks">{t('settings.hooksTab')}</TabsTrigger>
|
||||||
<TabsTrigger value="commands">Commands</TabsTrigger>
|
<TabsTrigger value="commands">{t('settings.commands')}</TabsTrigger>
|
||||||
<TabsTrigger value="storage">Storage</TabsTrigger>
|
<TabsTrigger value="storage">{t('settings.storage')}</TabsTrigger>
|
||||||
<TabsTrigger value="proxy">Proxy</TabsTrigger>
|
<TabsTrigger value="proxy">{t('settings.proxy')}</TabsTrigger>
|
||||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
<TabsTrigger value="analytics">{t('settings.analyticsTab')}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* General Settings */}
|
{/* General Settings */}
|
||||||
<TabsContent value="general" className="space-y-6">
|
<TabsContent value="general" className="space-y-6">
|
||||||
<Card className="p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold mb-4">General Settings</h3>
|
<h3 className="text-base font-semibold mb-4">{t('settings.generalSettings')}</h3>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Theme Selector */}
|
{/* Theme Selector */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="theme">Theme</Label>
|
<Label htmlFor="theme">{t('settings.theme')}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={theme}
|
value={theme}
|
||||||
onValueChange={(value) => setTheme(value as any)}
|
onValueChange={(value) => setTheme(value as any)}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="theme" className="w-full">
|
<SelectTrigger id="theme" className="w-full">
|
||||||
<SelectValue placeholder="Select a theme" />
|
<SelectValue placeholder={t('settings.themeSelector.selectATheme')} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="dark">Dark</SelectItem>
|
<SelectItem value="dark">{t('settings.themeSelector.dark')}</SelectItem>
|
||||||
<SelectItem value="gray">Gray</SelectItem>
|
<SelectItem value="gray">{t('settings.themeSelector.gray')}</SelectItem>
|
||||||
<SelectItem value="light">Light</SelectItem>
|
<SelectItem value="light">{t('settings.themeSelector.light')}</SelectItem>
|
||||||
<SelectItem value="custom">Custom</SelectItem>
|
<SelectItem value="custom">{t('settings.themeSelector.custom')}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Choose your preferred color theme for the interface
|
{t('settings.themeSelector.choosePreferredTheme')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Color Editor */}
|
{/* Custom Color Editor */}
|
||||||
{theme === 'custom' && (
|
{theme === 'custom' && (
|
||||||
<div className="space-y-4 p-4 border rounded-lg bg-muted/20">
|
<div className="space-y-4 p-4 border rounded-lg bg-muted/20">
|
||||||
<h4 className="text-sm font-medium">Custom Theme Colors</h4>
|
<h4 className="text-sm font-medium">{t('settings.customTheme.title')}</h4>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{/* Background Color */}
|
{/* Background Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-background" className="text-xs">Background</Label>
|
<Label htmlFor="color-background" className="text-xs">{t('settings.customTheme.background')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-background"
|
id="color-background"
|
||||||
@@ -465,7 +466,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Foreground Color */}
|
{/* Foreground Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-foreground" className="text-xs">Foreground</Label>
|
<Label htmlFor="color-foreground" className="text-xs">{t('settings.customTheme.foreground')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-foreground"
|
id="color-foreground"
|
||||||
@@ -484,7 +485,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Primary Color */}
|
{/* Primary Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-primary" className="text-xs">Primary</Label>
|
<Label htmlFor="color-primary" className="text-xs">{t('settings.customTheme.primary')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-primary"
|
id="color-primary"
|
||||||
@@ -503,7 +504,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Card Color */}
|
{/* Card Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-card" className="text-xs">Card</Label>
|
<Label htmlFor="color-card" className="text-xs">{t('settings.customTheme.card')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-card"
|
id="color-card"
|
||||||
@@ -522,7 +523,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Accent Color */}
|
{/* Accent Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-accent" className="text-xs">Accent</Label>
|
<Label htmlFor="color-accent" className="text-xs">{t('settings.customTheme.accent')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-accent"
|
id="color-accent"
|
||||||
@@ -541,7 +542,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Destructive Color */}
|
{/* Destructive Color */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="color-destructive" className="text-xs">Destructive</Label>
|
<Label htmlFor="color-destructive" className="text-xs">{t('settings.customTheme.destructive')}</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="color-destructive"
|
id="color-destructive"
|
||||||
@@ -560,7 +561,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately.
|
{t('settings.customTheme.colorValuesDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -568,9 +569,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{/* Include Co-authored By */}
|
{/* Include Co-authored By */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5 flex-1">
|
<div className="space-y-0.5 flex-1">
|
||||||
<Label htmlFor="coauthored">Include "Co-authored by Claude"</Label>
|
<Label htmlFor="coauthored">{t('settings.generalOptions.includeCoAuthor')}</Label>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Add Claude attribution to git commits and pull requests
|
{t('settings.generalOptions.includeCoAuthorDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -583,9 +584,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{/* Verbose Output */}
|
{/* Verbose Output */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5 flex-1">
|
<div className="space-y-0.5 flex-1">
|
||||||
<Label htmlFor="verbose">Verbose Output</Label>
|
<Label htmlFor="verbose">{t('settings.generalOptions.verboseOutput')}</Label>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Show full bash and command outputs
|
{t('settings.generalOptions.verboseOutputDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -597,7 +598,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
{/* Cleanup Period */}
|
{/* Cleanup Period */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="cleanup">Chat Transcript Retention (days)</Label>
|
<Label htmlFor="cleanup">{t('settings.generalOptions.chatRetention')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="cleanup"
|
id="cleanup"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -610,16 +611,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
How long to retain chat transcripts locally (default: 30 days)
|
{t('settings.generalOptions.chatRetentionDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Claude Binary Path Selector */}
|
{/* Claude Binary Path Selector */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm font-medium mb-2 block">Claude Code Installation</Label>
|
<Label className="text-sm font-medium mb-2 block">{t('settings.generalOptions.claudeCodeInstallation')}</Label>
|
||||||
<p className="text-xs text-muted-foreground mb-4">
|
<p className="text-xs text-muted-foreground mb-4">
|
||||||
Select which Claude Code installation to use.
|
{t('settings.generalOptions.claudeCodeInstallationDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ClaudeVersionSelector
|
<ClaudeVersionSelector
|
||||||
@@ -628,7 +629,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
/>
|
/>
|
||||||
{binaryPathChanged && (
|
{binaryPathChanged && (
|
||||||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||||
⚠️ Claude binary path has been changed. Remember to save your settings.
|
{t('settings.generalOptions.binaryPathChanged')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -642,16 +643,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold mb-2">Permission Rules</h3>
|
<h3 className="text-base font-semibold mb-2">{t('settings.permissions.permissionRules')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Control which tools Claude Code can use without manual approval
|
{t('settings.permissions.permissionRulesDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Allow Rules */}
|
{/* Allow Rules */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium text-green-500">Allow Rules</Label>
|
<Label className="text-sm font-medium text-green-500">{t('settings.permissions.allowRules')}</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -659,13 +660,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="gap-2 hover:border-green-500/50 hover:text-green-500"
|
className="gap-2 hover:border-green-500/50 hover:text-green-500"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
Add Rule
|
{t('settings.permissions.addRule')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{allowRules.length === 0 ? (
|
{allowRules.length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground py-2">
|
<p className="text-xs text-muted-foreground py-2">
|
||||||
No allow rules configured. Claude will ask for approval for all tools.
|
{t('settings.permissions.noAllowRules')}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
allowRules.map((rule) => (
|
allowRules.map((rule) => (
|
||||||
@@ -676,7 +677,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g., Bash(npm run test:*)"
|
placeholder={t('settings.placeholders.allowRuleExample')}
|
||||||
value={rule.value}
|
value={rule.value}
|
||||||
onChange={(e) => updatePermissionRule("allow", rule.id, e.target.value)}
|
onChange={(e) => updatePermissionRule("allow", rule.id, e.target.value)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@@ -698,7 +699,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{/* Deny Rules */}
|
{/* Deny Rules */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium text-red-500">Deny Rules</Label>
|
<Label className="text-sm font-medium text-red-500">{t('settings.permissions.denyRules')}</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -706,13 +707,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="gap-2 hover:border-red-500/50 hover:text-red-500"
|
className="gap-2 hover:border-red-500/50 hover:text-red-500"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
Add Rule
|
{t('settings.permissions.addRule')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{denyRules.length === 0 ? (
|
{denyRules.length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground py-2">
|
<p className="text-xs text-muted-foreground py-2">
|
||||||
No deny rules configured.
|
{t('settings.permissions.noDenyRules')}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
denyRules.map((rule) => (
|
denyRules.map((rule) => (
|
||||||
@@ -723,7 +724,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g., Bash(curl:*)"
|
placeholder={t('settings.placeholders.denyRuleExample')}
|
||||||
value={rule.value}
|
value={rule.value}
|
||||||
onChange={(e) => updatePermissionRule("deny", rule.id, e.target.value)}
|
onChange={(e) => updatePermissionRule("deny", rule.id, e.target.value)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@@ -744,14 +745,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
<div className="pt-2 space-y-2">
|
<div className="pt-2 space-y-2">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
<strong>Examples:</strong>
|
<strong>{t('settings.permissions.examples')}</strong>
|
||||||
</p>
|
</p>
|
||||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
<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> - Allow all bash commands</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.permissions.exampleBash')}</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> - Allow exact command</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.permissions.exampleExactCommand')}</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> - Allow commands with prefix</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.permissions.examplePrefix')}</li>
|
||||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Read(~/.zshrc)</code> - Allow reading specific file</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.permissions.exampleReadFile')}</li>
|
||||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Edit(docs/**)</code> - Allow editing files in docs directory</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.permissions.exampleEditDir')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -764,9 +765,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold">Environment Variables</h3>
|
<h3 className="text-base font-semibold">{t('settings.environment.environmentVariables')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Environment variables applied to every Claude Code session
|
{t('settings.environment.environmentVariablesDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -776,14 +777,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
Add Variable
|
{t('settings.environment.addVariable')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{envVars.length === 0 ? (
|
{envVars.length === 0 ? (
|
||||||
<p className="text-xs text-muted-foreground py-2">
|
<p className="text-xs text-muted-foreground py-2">
|
||||||
No environment variables configured.
|
{t('settings.environment.noEnvironmentVariables')}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
envVars.map((envVar) => (
|
envVars.map((envVar) => (
|
||||||
@@ -794,14 +795,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="KEY"
|
placeholder={t('settings.placeholders.envVarKey')}
|
||||||
value={envVar.key}
|
value={envVar.key}
|
||||||
onChange={(e) => updateEnvVar(envVar.id, "key", e.target.value)}
|
onChange={(e) => updateEnvVar(envVar.id, "key", e.target.value)}
|
||||||
className="flex-1 font-mono text-sm"
|
className="flex-1 font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground">=</span>
|
<span className="text-muted-foreground">=</span>
|
||||||
<Input
|
<Input
|
||||||
placeholder="value"
|
placeholder={t('settings.placeholders.envVarValue')}
|
||||||
value={envVar.value}
|
value={envVar.value}
|
||||||
onChange={(e) => updateEnvVar(envVar.id, "value", e.target.value)}
|
onChange={(e) => updateEnvVar(envVar.id, "value", e.target.value)}
|
||||||
className="flex-1 font-mono text-sm"
|
className="flex-1 font-mono text-sm"
|
||||||
@@ -821,12 +822,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
|
|
||||||
<div className="pt-2 space-y-2">
|
<div className="pt-2 space-y-2">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
<strong>Common variables:</strong>
|
<strong>{t('settings.environment.commonVariables')}</strong>
|
||||||
</p>
|
</p>
|
||||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
<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> - Enable/disable telemetry (0 or 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.environment.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> - Custom model name</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.environment.modelDesc')}</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> - Disable cost warnings (1)</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.environment.costWarningsDesc')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -837,34 +838,34 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold mb-4">Advanced Settings</h3>
|
<h3 className="text-base font-semibold mb-4">{t('settings.advanced.advancedSettings')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-6">
|
<p className="text-sm text-muted-foreground mb-6">
|
||||||
Additional configuration options for advanced users
|
{t('settings.advanced.advancedSettingsDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* API Key Helper */}
|
{/* API Key Helper */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="apiKeyHelper">API Key Helper Script</Label>
|
<Label htmlFor="apiKeyHelper">{t('settings.advanced.apiKeyHelper')}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="apiKeyHelper"
|
id="apiKeyHelper"
|
||||||
placeholder="/path/to/generate_api_key.sh"
|
placeholder={t('settings.placeholders.apiKeyHelperPath')}
|
||||||
value={settings?.apiKeyHelper || ""}
|
value={settings?.apiKeyHelper || ""}
|
||||||
onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)}
|
onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Custom script to generate auth values for API requests
|
{t('settings.advanced.apiKeyHelperDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Raw JSON Editor */}
|
{/* Raw JSON Editor */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Raw Settings (JSON)</Label>
|
<Label>{t('settings.advanced.rawSettings')}</Label>
|
||||||
<div className="p-3 rounded-md bg-muted font-mono text-xs overflow-x-auto whitespace-pre-wrap">
|
<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>
|
<pre>{JSON.stringify(settings, null, 2)}</pre>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
This shows the raw JSON that will be saved to ~/.claude/settings.json
|
{t('settings.advanced.rawSettingsDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -876,10 +877,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold mb-2">User Hooks</h3>
|
<h3 className="text-base font-semibold mb-2">{t('settings.hooks.userHooks')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Configure hooks that apply to all Claude Code sessions for your user account.
|
{t('settings.hooks.userHooksDesc')}
|
||||||
These are stored in <code className="mx-1 px-2 py-1 bg-muted rounded text-xs">~/.claude/settings.json</code>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -928,16 +928,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<BarChart3 className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
<BarChart3 className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
||||||
<h3 className="text-base font-semibold">Analytics Settings</h3>
|
<h3 className="text-base font-semibold">{t('settings.analytics.analyticsSettings')}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Analytics Toggle */}
|
{/* Analytics Toggle */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="analytics-enabled" className="text-base">Enable Analytics</Label>
|
<Label htmlFor="analytics-enabled" className="text-base">{t('settings.analytics.enableAnalytics')}</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Help improve Claudia by sharing anonymous usage data
|
{t('settings.analytics.enableAnalyticsDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -950,12 +950,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
await analytics.enable();
|
await analytics.enable();
|
||||||
setAnalyticsEnabled(true);
|
setAnalyticsEnabled(true);
|
||||||
trackEvent.settingsChanged('analytics_enabled', true);
|
trackEvent.settingsChanged('analytics_enabled', true);
|
||||||
setToast({ message: "Analytics enabled", type: "success" });
|
setToast({ message: t('settings.analytics.analyticsEnabled'), type: "success" });
|
||||||
} else {
|
} else {
|
||||||
await analytics.disable();
|
await analytics.disable();
|
||||||
setAnalyticsEnabled(false);
|
setAnalyticsEnabled(false);
|
||||||
trackEvent.settingsChanged('analytics_enabled', false);
|
trackEvent.settingsChanged('analytics_enabled', false);
|
||||||
setToast({ message: "Analytics disabled", type: "success" });
|
setToast({ message: t('settings.analytics.analyticsDisabled'), type: "success" });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -966,12 +966,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-medium text-blue-900 dark:text-blue-100">Your privacy is protected</p>
|
<p className="font-medium text-blue-900 dark:text-blue-100">{t('settings.analytics.privacyProtected')}</p>
|
||||||
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||||
<li>• No personal information is collected</li>
|
<li>• {t('settings.analytics.noPersonalInfo')}</li>
|
||||||
<li>• No file contents, paths, or project names</li>
|
<li>• {t('settings.analytics.noFileContents')}</li>
|
||||||
<li>• All data is anonymous with random IDs</li>
|
<li>• {t('settings.analytics.anonymousData')}</li>
|
||||||
<li>• You can disable analytics at any time</li>
|
<li>• {t('settings.analytics.canDisable')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -981,12 +981,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{analyticsEnabled && (
|
{analyticsEnabled && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium mb-2">What we collect:</h4>
|
<h4 className="text-sm font-medium mb-2">{t('settings.analytics.whatWeCollect')}</h4>
|
||||||
<ul className="text-sm text-muted-foreground space-y-1">
|
<ul className="text-sm text-muted-foreground space-y-1">
|
||||||
<li>• Feature usage patterns</li>
|
<li>• {t('settings.analytics.featureUsage')}</li>
|
||||||
<li>• Performance metrics</li>
|
<li>• {t('settings.analytics.performanceMetrics')}</li>
|
||||||
<li>• Error reports (without sensitive data)</li>
|
<li>• {t('settings.analytics.errorReports')}</li>
|
||||||
<li>• Session frequency and duration</li>
|
<li>• {t('settings.analytics.sessionFrequency')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -999,11 +999,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
await analytics.deleteAllData();
|
await analytics.deleteAllData();
|
||||||
setAnalyticsEnabled(false);
|
setAnalyticsEnabled(false);
|
||||||
setAnalyticsConsented(false);
|
setAnalyticsConsented(false);
|
||||||
setToast({ message: "All analytics data deleted", type: "success" });
|
setToast({ message: t('settings.analytics.allDataDeleted'), type: "success" });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash className="mr-2 h-4 w-4" />
|
<Trash className="mr-2 h-4 w-4" />
|
||||||
Delete All Analytics Data
|
{t('settings.analytics.deleteAllData')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -30,6 +30,7 @@ import { api, type SlashCommand } from "@/lib/api";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
||||||
import { useTrackEvent } from "@/hooks";
|
import { useTrackEvent } from "@/hooks";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface SlashCommandsManagerProps {
|
interface SlashCommandsManagerProps {
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
@@ -92,6 +93,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
className,
|
className,
|
||||||
scopeFilter = 'all',
|
scopeFilter = 'all',
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -305,17 +307,17 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
{scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'}
|
{scopeFilter === 'project' ? t('slashCommands.projectSlashCommands') : t('slashCommands.slashCommands')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
{scopeFilter === 'project'
|
{scopeFilter === 'project'
|
||||||
? 'Create custom commands for this project'
|
? t('slashCommands.createCustomCommandsProject')
|
||||||
: 'Create custom commands to streamline your workflow'}
|
: t('slashCommands.createCustomCommandsWorkflow')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
New Command
|
{t('slashCommands.newCommand')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -325,7 +327,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search commands..."
|
placeholder={t('placeholders.searchCommands')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-9"
|
className="pl-9"
|
||||||
@@ -338,9 +340,9 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Commands</SelectItem>
|
<SelectItem value="all">{t('slashCommands.allCommands')}</SelectItem>
|
||||||
<SelectItem value="project">Project</SelectItem>
|
<SelectItem value="project">{t('slashCommands.project')}</SelectItem>
|
||||||
<SelectItem value="user">User</SelectItem>
|
<SelectItem value="user">{t('slashCommands.user')}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
@@ -365,16 +367,16 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{searchQuery
|
{searchQuery
|
||||||
? "No commands found"
|
? t('slashCommands.noCommandsFound')
|
||||||
: scopeFilter === 'project'
|
: scopeFilter === 'project'
|
||||||
? "No project commands created yet"
|
? t('slashCommands.noProjectCommandsYet')
|
||||||
: "No commands created yet"}
|
: t('slashCommands.noCommandsYet')}
|
||||||
</p>
|
</p>
|
||||||
{!searchQuery && (
|
{!searchQuery && (
|
||||||
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
||||||
{scopeFilter === 'project'
|
{scopeFilter === 'project'
|
||||||
? "Create your first project command"
|
? t('slashCommands.createFirstProjectCommand')
|
||||||
: "Create your first command"}
|
: t('slashCommands.createFirstCommand')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -9,6 +9,7 @@ import { ProjectList } from '@/components/ProjectList';
|
|||||||
import { SessionList } from '@/components/SessionList';
|
import { SessionList } from '@/components/SessionList';
|
||||||
import { RunningClaudeSessions } from '@/components/RunningClaudeSessions';
|
import { RunningClaudeSessions } from '@/components/RunningClaudeSessions';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
// Lazy load heavy components
|
// Lazy load heavy components
|
||||||
const ClaudeCodeSession = lazy(() => import('@/components/ClaudeCodeSession').then(m => ({ default: m.ClaudeCodeSession })));
|
const ClaudeCodeSession = lazy(() => import('@/components/ClaudeCodeSession').then(m => ({ default: m.ClaudeCodeSession })));
|
||||||
@@ -29,6 +30,7 @@ interface TabPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { updateTab, createChatTab } = useTabState();
|
const { updateTab, createChatTab } = useTabState();
|
||||||
const [projects, setProjects] = React.useState<Project[]>([]);
|
const [projects, setProjects] = React.useState<Project[]>([]);
|
||||||
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
||||||
@@ -54,7 +56,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
setProjects(projectList);
|
setProjects(projectList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load projects:", err);
|
console.error("Failed to load projects:", err);
|
||||||
setError("Failed to load projects. Please ensure ~/.claude directory exists.");
|
setError(t('failedToLoadProjects'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
setSelectedProject(project);
|
setSelectedProject(project);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load sessions:", err);
|
console.error("Failed to load sessions:", err);
|
||||||
setError("Failed to load sessions for this project.");
|
setError(t('failedToLoadSessions'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -96,9 +98,9 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
<div className="container mx-auto p-6">
|
<div className="container mx-auto p-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-3xl font-bold tracking-tight">CC Projects</h1>
|
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
Browse your Claude Code sessions
|
{t('browseClaudeCodeSessions')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
className="w-full max-w-md"
|
className="w-full max-w-md"
|
||||||
>
|
>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
New Claude Code session
|
{t('newClaudeCodeSession')}
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -196,7 +198,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="py-8 text-center">
|
<div className="py-8 text-center">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
No projects found in ~/.claude/projects
|
{t('noProjectsFound')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -217,7 +219,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
// Go back to projects view in the same tab
|
// Go back to projects view in the same tab
|
||||||
updateTab(tab.id, {
|
updateTab(tab.id, {
|
||||||
type: 'projects',
|
type: 'projects',
|
||||||
title: 'CC Projects',
|
title: t('ccProjects'),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -225,7 +227,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
|
|
||||||
case 'agent':
|
case 'agent':
|
||||||
if (!tab.agentRunId) {
|
if (!tab.agentRunId) {
|
||||||
return <div className="p-4">No agent run ID specified</div>;
|
return <div className="p-4">{t('messages.noAgentRunIdSpecified')}</div>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AgentRunOutputViewer
|
<AgentRunOutputViewer
|
||||||
@@ -249,15 +251,15 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
|
|
||||||
case 'claude-file':
|
case 'claude-file':
|
||||||
if (!tab.claudeFileId) {
|
if (!tab.claudeFileId) {
|
||||||
return <div className="p-4">No Claude file ID specified</div>;
|
return <div className="p-4">{t('messages.noClaudeFileIdSpecified')}</div>;
|
||||||
}
|
}
|
||||||
// Note: We need to get the actual file object for ClaudeFileEditor
|
// Note: We need to get the actual file object for ClaudeFileEditor
|
||||||
// For now, returning a placeholder
|
// For now, returning a placeholder
|
||||||
return <div className="p-4">Claude file editor not yet implemented in tabs</div>;
|
return <div className="p-4">{t('messages.claudeFileEditorNotImplemented')}</div>;
|
||||||
|
|
||||||
case 'agent-execution':
|
case 'agent-execution':
|
||||||
if (!tab.agentData) {
|
if (!tab.agentData) {
|
||||||
return <div className="p-4">No agent data specified</div>;
|
return <div className="p-4">{t('messages.noAgentDataSpecified')}</div>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AgentExecution
|
<AgentExecution
|
||||||
@@ -282,10 +284,10 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
|
|
||||||
case 'import-agent':
|
case 'import-agent':
|
||||||
// TODO: Implement import agent component
|
// TODO: Implement import agent component
|
||||||
return <div className="p-4">Import agent functionality coming soon...</div>;
|
return <div className="p-4">{t('messages.importAgentComingSoon')}</div>;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <div className="p-4">Unknown tab type: {tab.type}</div>;
|
return <div className="p-4">{t('messages.unknownTabType')}: {tab.type}</div>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -311,6 +313,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TabContent: React.FC = () => {
|
export const TabContent: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState();
|
const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState();
|
||||||
|
|
||||||
// Listen for events to open sessions in tabs
|
// Listen for events to open sessions in tabs
|
||||||
@@ -415,8 +418,8 @@ export const TabContent: React.FC = () => {
|
|||||||
{tabs.length === 0 && (
|
{tabs.length === 0 && (
|
||||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-lg mb-2">No tabs open</p>
|
<p className="text-lg mb-2">{t('messages.noTabsOpen')}</p>
|
||||||
<p className="text-sm">Click the + button to start a new chat</p>
|
<p className="text-sm">{t('messages.clickPlusToStartChat')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
Briefcase
|
Briefcase
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface UsageDashboardProps {
|
interface UsageDashboardProps {
|
||||||
/**
|
/**
|
||||||
@@ -32,6 +33,7 @@ interface UsageDashboardProps {
|
|||||||
* <UsageDashboard onBack={() => setView('welcome')} />
|
* <UsageDashboard onBack={() => setView('welcome')} />
|
||||||
*/
|
*/
|
||||||
export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [stats, setStats] = useState<UsageStats | null>(null);
|
const [stats, setStats] = useState<UsageStats | null>(null);
|
||||||
@@ -82,7 +84,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
setSessionStats(sessionData);
|
setSessionStats(sessionData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load usage stats:", err);
|
console.error("Failed to load usage stats:", err);
|
||||||
setError("Failed to load usage statistics. Please try again.");
|
setError(t('usage.failedToLoadUsageStats'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -146,9 +148,9 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-lg font-semibold">Usage Dashboard</h1>
|
<h1 className="text-lg font-semibold">{t('usage.usageDashboardTitle')}</h1>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Track your Claude Code usage and costs
|
{t('usage.trackUsageAndCosts')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
onClick={() => setSelectedDateRange(range)}
|
onClick={() => setSelectedDateRange(range)}
|
||||||
className="text-xs"
|
className="text-xs"
|
||||||
>
|
>
|
||||||
{range === "all" ? "All Time" : range === "7d" ? "Last 7 Days" : "Last 30 Days"}
|
{range === "all" ? t('usage.allTime') : range === "7d" ? t('usage.last7Days') : t('usage.last30Days')}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground mx-auto mb-4" />
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground mx-auto mb-4" />
|
||||||
<p className="text-sm text-muted-foreground">Loading usage statistics...</p>
|
<p className="text-sm text-muted-foreground">{t('usage.loadingUsageStats')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
@@ -187,7 +189,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<div className="text-center max-w-md">
|
<div className="text-center max-w-md">
|
||||||
<p className="text-sm text-destructive mb-4">{error}</p>
|
<p className="text-sm text-destructive mb-4">{error}</p>
|
||||||
<Button onClick={loadUsageStats} size="sm">
|
<Button onClick={loadUsageStats} size="sm">
|
||||||
Try Again
|
{t('usage.tryAgain')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +206,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<Card className="p-4 shimmer-hover">
|
<Card className="p-4 shimmer-hover">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Cost</p>
|
<p className="text-xs text-muted-foreground">{t('usage.totalCost')}</p>
|
||||||
<p className="text-2xl font-bold mt-1">
|
<p className="text-2xl font-bold mt-1">
|
||||||
{formatCurrency(stats.total_cost)}
|
{formatCurrency(stats.total_cost)}
|
||||||
</p>
|
</p>
|
||||||
@@ -217,7 +219,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<Card className="p-4 shimmer-hover">
|
<Card className="p-4 shimmer-hover">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Sessions</p>
|
<p className="text-xs text-muted-foreground">{t('usage.totalSessions')}</p>
|
||||||
<p className="text-2xl font-bold mt-1">
|
<p className="text-2xl font-bold mt-1">
|
||||||
{formatNumber(stats.total_sessions)}
|
{formatNumber(stats.total_sessions)}
|
||||||
</p>
|
</p>
|
||||||
@@ -230,7 +232,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<Card className="p-4 shimmer-hover">
|
<Card className="p-4 shimmer-hover">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Total Tokens</p>
|
<p className="text-xs text-muted-foreground">{t('usage.totalTokens')}</p>
|
||||||
<p className="text-2xl font-bold mt-1">
|
<p className="text-2xl font-bold mt-1">
|
||||||
{formatTokens(stats.total_tokens)}
|
{formatTokens(stats.total_tokens)}
|
||||||
</p>
|
</p>
|
||||||
@@ -243,7 +245,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
<Card className="p-4 shimmer-hover">
|
<Card className="p-4 shimmer-hover">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Avg Cost/Session</p>
|
<p className="text-xs text-muted-foreground">{t('usage.avgCostPerSession')}</p>
|
||||||
<p className="text-2xl font-bold mt-1">
|
<p className="text-2xl font-bold mt-1">
|
||||||
{formatCurrency(
|
{formatCurrency(
|
||||||
stats.total_sessions > 0
|
stats.total_sessions > 0
|
||||||
@@ -260,32 +262,32 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{/* Tabs for different views */}
|
{/* Tabs for different views */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList className="grid w-full grid-cols-5">
|
<TabsList className="grid w-full grid-cols-5">
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
<TabsTrigger value="overview">{t('usage.overview')}</TabsTrigger>
|
||||||
<TabsTrigger value="models">By Model</TabsTrigger>
|
<TabsTrigger value="models">{t('usage.byModel')}</TabsTrigger>
|
||||||
<TabsTrigger value="projects">By Project</TabsTrigger>
|
<TabsTrigger value="projects">{t('usage.byProject')}</TabsTrigger>
|
||||||
<TabsTrigger value="sessions">By Session</TabsTrigger>
|
<TabsTrigger value="sessions">{t('usage.byDate')}</TabsTrigger>
|
||||||
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
<TabsTrigger value="timeline">{t('usage.timeline')}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* Overview Tab */}
|
{/* Overview Tab */}
|
||||||
<TabsContent value="overview" className="space-y-4">
|
<TabsContent value="overview" className="space-y-4">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Token Breakdown</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.tokenBreakdown')}</h3>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Input Tokens</p>
|
<p className="text-xs text-muted-foreground">{t('usage.inputTokens')}</p>
|
||||||
<p className="text-lg font-semibold">{formatTokens(stats.total_input_tokens)}</p>
|
<p className="text-lg font-semibold">{formatTokens(stats.total_input_tokens)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Output Tokens</p>
|
<p className="text-xs text-muted-foreground">{t('usage.outputTokens')}</p>
|
||||||
<p className="text-lg font-semibold">{formatTokens(stats.total_output_tokens)}</p>
|
<p className="text-lg font-semibold">{formatTokens(stats.total_output_tokens)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Cache Write</p>
|
<p className="text-xs text-muted-foreground">{t('usage.cacheWrite')}</p>
|
||||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_creation_tokens)}</p>
|
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_creation_tokens)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Cache Read</p>
|
<p className="text-xs text-muted-foreground">{t('usage.cacheRead')}</p>
|
||||||
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_read_tokens)}</p>
|
<p className="text-lg font-semibold">{formatTokens(stats.total_cache_read_tokens)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,7 +296,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{/* Quick Stats */}
|
{/* Quick Stats */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Most Used Models</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.mostUsedModels')}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{stats.by_model.slice(0, 3).map((model) => (
|
{stats.by_model.slice(0, 3).map((model) => (
|
||||||
<div key={model.model} className="flex items-center justify-between">
|
<div key={model.model} className="flex items-center justify-between">
|
||||||
@@ -303,7 +305,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{getModelDisplayName(model.model)}
|
{getModelDisplayName(model.model)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{model.session_count} sessions
|
{model.session_count} {t('usage.sessions')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Top Projects</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.topProjects')}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{stats.by_project.slice(0, 3).map((project) => (
|
{stats.by_project.slice(0, 3).map((project) => (
|
||||||
<div key={project.project_path} className="flex items-center justify-between">
|
<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}
|
{project.project_path}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{project.session_count} sessions
|
{project.session_count} {t('usage.sessions')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{/* Models Tab */}
|
{/* Models Tab */}
|
||||||
<TabsContent value="models">
|
<TabsContent value="models">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Usage by Model</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByModel')}</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{stats.by_model.map((model) => (
|
{stats.by_model.map((model) => (
|
||||||
<div key={model.model} className="space-y-2">
|
<div key={model.model} className="space-y-2">
|
||||||
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{getModelDisplayName(model.model)}
|
{getModelDisplayName(model.model)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{model.session_count} sessions
|
{model.session_count} {t('usage.sessions')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-semibold">
|
<span className="text-sm font-semibold">
|
||||||
@@ -362,11 +364,11 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Input: </span>
|
<span className="text-muted-foreground">{t('usage.input')}: </span>
|
||||||
<span className="font-medium">{formatTokens(model.input_tokens)}</span>
|
<span className="font-medium">{formatTokens(model.input_tokens)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Output: </span>
|
<span className="text-muted-foreground">{t('usage.output')}: </span>
|
||||||
<span className="font-medium">{formatTokens(model.output_tokens)}</span>
|
<span className="font-medium">{formatTokens(model.output_tokens)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{/* Projects Tab */}
|
{/* Projects Tab */}
|
||||||
<TabsContent value="projects">
|
<TabsContent value="projects">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Usage by Project</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByProject')}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{stats.by_project.map((project) => (
|
{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">
|
<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>
|
</span>
|
||||||
<div className="flex items-center space-x-3 mt-1">
|
<div className="flex items-center space-x-3 mt-1">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{project.session_count} sessions
|
{project.session_count} {t('usage.sessions')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{formatTokens(project.total_tokens)} tokens
|
{formatTokens(project.total_tokens)} {t('usage.tokens')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-sm font-semibold">{formatCurrency(project.total_cost)}</p>
|
<p className="text-sm font-semibold">{formatCurrency(project.total_cost)}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{formatCurrency(project.total_cost / project.session_count)}/session
|
{formatCurrency(project.total_cost / project.session_count)}/{t('usage.session')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -419,7 +421,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
|||||||
{/* Sessions Tab */}
|
{/* Sessions Tab */}
|
||||||
<TabsContent value="sessions">
|
<TabsContent value="sessions">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-4">Usage by Session</h3>
|
<h3 className="text-sm font-semibold mb-4">{t('usage.usageBySession')}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{sessionStats?.map((session) => (
|
{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">
|
<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">
|
<Card className="p-6">
|
||||||
<h3 className="text-sm font-semibold mb-6 flex items-center space-x-2">
|
<h3 className="text-sm font-semibold mb-6 flex items-center space-x-2">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
<span>Daily Usage</span>
|
<span>{t('usage.dailyUsage')}</span>
|
||||||
</h3>
|
</h3>
|
||||||
{stats.by_date.length > 0 ? (() => {
|
{stats.by_date.length > 0 ? (() => {
|
||||||
const maxCost = Math.max(...stats.by_date.map(d => d.total_cost), 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">
|
<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 font-semibold">{formattedDate}</p>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Cost: {formatCurrency(day.total_cost)}
|
{t('usage.cost')}: {formatCurrency(day.total_cost)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{formatTokens(day.total_tokens)} tokens
|
{formatTokens(day.total_tokens)} {t('usage.tokens')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{day.models_used.length} model{day.models_used.length !== 1 ? 's' : ''}
|
{day.models_used.length} {t('usage.models')}{day.models_used.length !== 1 ? 's' : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1">
|
<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 */}
|
{/* X-axis label */}
|
||||||
<div className="mt-8 text-center text-xs text-muted-foreground">
|
<div className="mt-8 text-center text-xs text-muted-foreground">
|
||||||
Daily Usage Over Time
|
{t('usage.dailyUsageOverTime')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})() : (
|
})() : (
|
||||||
<div className="text-center py-8 text-sm text-muted-foreground">
|
<div className="text-center py-8 text-sm text-muted-foreground">
|
||||||
No usage data available for the selected period
|
{t('usage.noUsageData')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import React, { createContext, useState, useContext, useCallback, useEffect } from 'react';
|
import React, { createContext, useState, useContext, useCallback, useEffect } from 'react';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -37,6 +38,7 @@ const TabContext = createContext<TabContextType | undefined>(undefined);
|
|||||||
const MAX_TABS = 20;
|
const MAX_TABS = 20;
|
||||||
|
|
||||||
export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [tabs, setTabs] = useState<Tab[]>([]);
|
const [tabs, setTabs] = useState<Tab[]>([]);
|
||||||
const [activeTabId, setActiveTabId] = useState<string | null>(null);
|
const [activeTabId, setActiveTabId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const defaultTab: Tab = {
|
const defaultTab: Tab = {
|
||||||
id: generateTabId(),
|
id: generateTabId(),
|
||||||
type: 'projects',
|
type: 'projects',
|
||||||
title: 'CC Projects',
|
title: t('ccProjects'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
order: 0,
|
order: 0,
|
||||||
@@ -55,7 +57,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
};
|
};
|
||||||
setTabs([defaultTab]);
|
setTabs([defaultTab]);
|
||||||
setActiveTabId(defaultTab.id);
|
setActiveTabId(defaultTab.id);
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
// Tab persistence disabled - no longer saving to localStorage
|
// Tab persistence disabled - no longer saving to localStorage
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@@ -75,7 +77,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
|
|
||||||
const addTab = useCallback((tabData: Omit<Tab, 'id' | 'order' | 'createdAt' | 'updatedAt'>): string => {
|
const addTab = useCallback((tabData: Omit<Tab, 'id' | 'order' | 'createdAt' | 'updatedAt'>): string => {
|
||||||
if (tabs.length >= MAX_TABS) {
|
if (tabs.length >= MAX_TABS) {
|
||||||
throw new Error(`Maximum number of tabs (${MAX_TABS}) reached`);
|
throw new Error(t('maximumTabsReached', { max: MAX_TABS }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTab: Tab = {
|
const newTab: Tab = {
|
||||||
@@ -89,7 +91,7 @@ export const TabProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
setTabs(prevTabs => [...prevTabs, newTab]);
|
setTabs(prevTabs => [...prevTabs, newTab]);
|
||||||
setActiveTabId(newTab.id);
|
setActiveTabId(newTab.id);
|
||||||
return newTab.id;
|
return newTab.id;
|
||||||
}, [tabs.length]);
|
}, [tabs.length, t]);
|
||||||
|
|
||||||
const removeTab = useCallback((id: string) => {
|
const removeTab = useCallback((id: string) => {
|
||||||
setTabs(prevTabs => {
|
setTabs(prevTabs => {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTabContext } from '@/contexts/TabContext';
|
import { useTabContext } from '@/contexts/TabContext';
|
||||||
import { Tab } from '@/contexts/TabContext';
|
import { Tab } from '@/contexts/TabContext';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface UseTabStateReturn {
|
interface UseTabStateReturn {
|
||||||
// State
|
// State
|
||||||
@@ -40,6 +41,7 @@ interface UseTabStateReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useTabState = (): UseTabStateReturn => {
|
export const useTabState = (): UseTabStateReturn => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
tabs,
|
tabs,
|
||||||
activeTabId,
|
activeTabId,
|
||||||
@@ -100,12 +102,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'projects',
|
type: 'projects',
|
||||||
title: 'CC Projects',
|
title: t('ccProjects'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'folder'
|
icon: 'folder'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createUsageTab = useCallback((): string | null => {
|
const createUsageTab = useCallback((): string | null => {
|
||||||
// Check if usage tab already exists (singleton)
|
// Check if usage tab already exists (singleton)
|
||||||
@@ -117,12 +119,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'usage',
|
type: 'usage',
|
||||||
title: 'Usage',
|
title: t('messages.usage'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'bar-chart'
|
icon: 'bar-chart'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createMCPTab = useCallback((): string | null => {
|
const createMCPTab = useCallback((): string | null => {
|
||||||
// Check if MCP tab already exists (singleton)
|
// Check if MCP tab already exists (singleton)
|
||||||
@@ -134,12 +136,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'mcp',
|
type: 'mcp',
|
||||||
title: 'MCP Servers',
|
title: t('messages.mcpServersTitle'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'server'
|
icon: 'server'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createSettingsTab = useCallback((): string | null => {
|
const createSettingsTab = useCallback((): string | null => {
|
||||||
// Check if settings tab already exists (singleton)
|
// Check if settings tab already exists (singleton)
|
||||||
@@ -151,12 +153,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'settings',
|
type: 'settings',
|
||||||
title: 'Settings',
|
title: t('settings.title'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'settings'
|
icon: 'settings'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createClaudeMdTab = useCallback((): string | null => {
|
const createClaudeMdTab = useCallback((): string | null => {
|
||||||
// Check if claude-md tab already exists (singleton)
|
// Check if claude-md tab already exists (singleton)
|
||||||
@@ -168,12 +170,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'claude-md',
|
type: 'claude-md',
|
||||||
title: 'CLAUDE.md',
|
title: t('messages.claudemdTitle'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'file-text'
|
icon: 'file-text'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createClaudeFileTab = useCallback((fileId: string, fileName: string): string => {
|
const createClaudeFileTab = useCallback((fileId: string, fileName: string): string => {
|
||||||
// Check if tab already exists for this file
|
// Check if tab already exists for this file
|
||||||
@@ -196,13 +198,13 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
const createAgentExecutionTab = useCallback((agent: any, _tabId: string): string => {
|
const createAgentExecutionTab = useCallback((agent: any, _tabId: string): string => {
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'agent-execution',
|
type: 'agent-execution',
|
||||||
title: `Run: ${agent.name}`,
|
title: t('messages.runAgent', { name: agent.name }),
|
||||||
agentData: agent,
|
agentData: agent,
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'bot'
|
icon: 'bot'
|
||||||
});
|
});
|
||||||
}, [addTab]);
|
}, [addTab, t]);
|
||||||
|
|
||||||
const createCreateAgentTab = useCallback((): string => {
|
const createCreateAgentTab = useCallback((): string => {
|
||||||
// Check if create agent tab already exists (singleton)
|
// Check if create agent tab already exists (singleton)
|
||||||
@@ -214,12 +216,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'create-agent',
|
type: 'create-agent',
|
||||||
title: 'Create Agent',
|
title: t('messages.createAgent'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'plus'
|
icon: 'plus'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const createImportAgentTab = useCallback((): string => {
|
const createImportAgentTab = useCallback((): string => {
|
||||||
// Check if import agent tab already exists (singleton)
|
// Check if import agent tab already exists (singleton)
|
||||||
@@ -231,12 +233,12 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
|
|
||||||
return addTab({
|
return addTab({
|
||||||
type: 'import-agent',
|
type: 'import-agent',
|
||||||
title: 'Import Agent',
|
title: t('messages.importAgent'),
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
icon: 'import'
|
icon: 'import'
|
||||||
});
|
});
|
||||||
}, [addTab, tabs, setActiveTab]);
|
}, [addTab, tabs, setActiveTab, t]);
|
||||||
|
|
||||||
const closeTab = useCallback(async (id: string, force: boolean = false): Promise<boolean> => {
|
const closeTab = useCallback(async (id: string, force: boolean = false): Promise<boolean> => {
|
||||||
const tab = getTabById(id);
|
const tab = getTabById(id);
|
||||||
@@ -245,13 +247,13 @@ export const useTabState = (): UseTabStateReturn => {
|
|||||||
// Check for unsaved changes
|
// Check for unsaved changes
|
||||||
if (!force && tab.hasUnsavedChanges) {
|
if (!force && tab.hasUnsavedChanges) {
|
||||||
// In a real implementation, you'd show a confirmation dialog here
|
// In a real implementation, you'd show a confirmation dialog here
|
||||||
const confirmed = window.confirm(`Tab "${tab.title}" has unsaved changes. Close anyway?`);
|
const confirmed = window.confirm(t('messages.unsavedChangesCloseConfirm', { title: tab.title }));
|
||||||
if (!confirmed) return false;
|
if (!confirmed) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTab(id);
|
removeTab(id);
|
||||||
return true;
|
return true;
|
||||||
}, [getTabById, removeTab]);
|
}, [getTabById, removeTab, t]);
|
||||||
|
|
||||||
const closeCurrentTab = useCallback(async (): Promise<boolean> => {
|
const closeCurrentTab = useCallback(async (): Promise<boolean> => {
|
||||||
if (!activeTabId) return true;
|
if (!activeTabId) return true;
|
||||||
|
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"ccProjects": "CC Projects",
|
||||||
|
"browseClaudeCodeSessions": "Browse your Claude Code sessions",
|
||||||
|
"newClaudeCodeSession": "New Claude Code session",
|
||||||
|
"ccAgents": "CC Agents",
|
||||||
|
"mcpServers": "MCP Servers",
|
||||||
|
"manageMcpServers": "Manage Model Context Protocol servers",
|
||||||
"app": {
|
"app": {
|
||||||
"name": "Claudia",
|
"name": "Claudia",
|
||||||
"welcome": "Welcome to Claudia",
|
"welcome": "Welcome to Claudia",
|
||||||
@@ -19,7 +25,12 @@
|
|||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"open": "Open"
|
"open": "Open",
|
||||||
|
"page": "Page",
|
||||||
|
"of": "of",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"from": "from",
|
||||||
|
"retry": "Retry"
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "CC Projects",
|
"projects": "CC Projects",
|
||||||
@@ -47,9 +58,10 @@
|
|||||||
"editAgent": "Edit Agent",
|
"editAgent": "Edit Agent",
|
||||||
"deleteAgent": "Delete Agent",
|
"deleteAgent": "Delete Agent",
|
||||||
"executeAgent": "Execute Agent",
|
"executeAgent": "Execute Agent",
|
||||||
"agentName": "Agent Name",
|
"agentNameRequired": "Agent name is required",
|
||||||
"agentIcon": "Agent Icon",
|
"agentIcon": "Agent Icon",
|
||||||
"systemPrompt": "System Prompt",
|
"systemPrompt": "System Prompt",
|
||||||
|
"systemPromptRequired": "System prompt is required",
|
||||||
"defaultTask": "Default Task",
|
"defaultTask": "Default Task",
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
@@ -66,7 +78,60 @@
|
|||||||
"createAgentDescription": "Create a new Claude Code agent",
|
"createAgentDescription": "Create a new Claude Code agent",
|
||||||
"updateAgentDescription": "Update your Claude Code agent",
|
"updateAgentDescription": "Update your Claude Code agent",
|
||||||
"createFailed": "Failed to create agent",
|
"createFailed": "Failed to create agent",
|
||||||
"updateFailed": "Failed to update agent"
|
"updateFailed": "Failed to update agent",
|
||||||
|
"basicInformation": "Basic Information",
|
||||||
|
"optional": "Optional",
|
||||||
|
"sonnetDescription": "Faster, efficient for most tasks",
|
||||||
|
"opusDescription": "More capable, better for complex tasks",
|
||||||
|
"defaultTaskDescription": "This will be used as the default task placeholder when executing the agent",
|
||||||
|
"systemPromptDescription": "Define the behavior and capabilities of your CC Agent",
|
||||||
|
"manageAgents": "Manage your Claude Code agents",
|
||||||
|
"noAgentsYet": "No agents yet",
|
||||||
|
"createFirstAgent": "Create your first CC Agent to get started",
|
||||||
|
"created": "Created",
|
||||||
|
"execute": "Execute",
|
||||||
|
"export": "Export",
|
||||||
|
"import": "Import",
|
||||||
|
"importFromFile": "From File",
|
||||||
|
"importFromGitHub": "From GitHub",
|
||||||
|
"recentExecutions": "Recent Executions",
|
||||||
|
"exportedSuccessfully": "Agent \"{{name}}\" exported successfully",
|
||||||
|
"exportFailed": "Failed to export agent",
|
||||||
|
"importedSuccessfully": "Agent imported successfully",
|
||||||
|
"importFailed": "Failed to import agent",
|
||||||
|
"importFromGitHubSuccess": "Agent imported successfully from GitHub",
|
||||||
|
"executeAgentTitle": "Execute agent",
|
||||||
|
"deleteAgentTitle": "Delete Agent",
|
||||||
|
"deleteConfirmation": "Are you sure you want to delete the agent \"{{name}}\"? This action cannot be undone and will permanently remove the agent and all its associated data.",
|
||||||
|
"deleting": "Deleting...",
|
||||||
|
"deleteAgentButton": "Delete Agent",
|
||||||
|
"agentManagement": "Agent Management",
|
||||||
|
"createNewOrManageAgents": "Create new agents or manage running agent executions",
|
||||||
|
"noAgentsAvailable": "No agents available",
|
||||||
|
"availableAgents": "Available Agents",
|
||||||
|
"runningAgents": "Running Agents",
|
||||||
|
"createFirstAgentToGetStarted": "Create your first agent to get started"
|
||||||
|
},
|
||||||
|
"slashCommands": {
|
||||||
|
"slashCommands": "Slash Commands",
|
||||||
|
"projectSlashCommands": "Project Slash Commands",
|
||||||
|
"createCustomCommandsProject": "Create custom commands for this project",
|
||||||
|
"createCustomCommandsWorkflow": "Create custom commands to streamline your workflow",
|
||||||
|
"newCommand": "New Command",
|
||||||
|
"allCommands": "All Commands",
|
||||||
|
"project": "Project",
|
||||||
|
"user": "User",
|
||||||
|
"noCommandsFound": "No commands found",
|
||||||
|
"noProjectCommandsYet": "No project commands created yet",
|
||||||
|
"noCommandsYet": "No commands created yet",
|
||||||
|
"createFirstProjectCommand": "Create your first project command",
|
||||||
|
"createFirstCommand": "Create your first command"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"hooksConfiguration": "Hooks Configuration",
|
||||||
|
"configureShellCommands": "Configure shell commands to execute at various points in Claude Code's lifecycle.",
|
||||||
|
"localSettingsNote": " These settings are not committed to version control.",
|
||||||
|
"unsavedChanges": "You have unsaved changes. Click Save to persist them."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
@@ -85,7 +150,142 @@
|
|||||||
"noProxy": "No Proxy",
|
"noProxy": "No Proxy",
|
||||||
"analyticsConsent": "Analytics Consent",
|
"analyticsConsent": "Analytics Consent",
|
||||||
"enableAnalytics": "Enable Analytics",
|
"enableAnalytics": "Enable Analytics",
|
||||||
"disableAnalytics": "Disable Analytics"
|
"disableAnalytics": "Disable Analytics",
|
||||||
|
"configurePreferences": "Configure Claude Code preferences",
|
||||||
|
"generalSettings": "General Settings",
|
||||||
|
"permissionsTab": "Permissions",
|
||||||
|
"environmentTab": "Environment",
|
||||||
|
"advancedTab": "Advanced",
|
||||||
|
"hooksTab": "Hooks",
|
||||||
|
"commands": "Commands",
|
||||||
|
"storage": "Storage",
|
||||||
|
"proxy": "Proxy",
|
||||||
|
"analyticsTab": "Analytics",
|
||||||
|
"proxySettingsTitle": "Proxy Settings",
|
||||||
|
"proxySettingsDesc": "Configure proxy settings for Claude API requests",
|
||||||
|
"enableProxyDesc": "Use proxy for all Claude API requests",
|
||||||
|
"noProxyDesc": "Comma-separated list of hosts that should bypass the proxy",
|
||||||
|
"allProxyDesc": "Proxy URL to use for all protocols if protocol-specific proxies are not set",
|
||||||
|
"proxySettingsSaved": "Proxy settings saved and applied successfully.",
|
||||||
|
"loadProxySettingsFailed": "Failed to load proxy settings",
|
||||||
|
"saveProxySettingsFailed": "Failed to save proxy settings",
|
||||||
|
"themeSelector": {
|
||||||
|
"selectATheme": "Select a theme",
|
||||||
|
"dark": "Dark",
|
||||||
|
"gray": "Gray",
|
||||||
|
"light": "Light",
|
||||||
|
"custom": "Custom",
|
||||||
|
"choosePreferredTheme": "Choose your preferred color theme for the interface"
|
||||||
|
},
|
||||||
|
"customTheme": {
|
||||||
|
"title": "Custom Theme Colors",
|
||||||
|
"background": "Background",
|
||||||
|
"foreground": "Foreground",
|
||||||
|
"primary": "Primary",
|
||||||
|
"card": "Card",
|
||||||
|
"accent": "Accent",
|
||||||
|
"destructive": "Destructive",
|
||||||
|
"colorValuesDesc": "Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately."
|
||||||
|
},
|
||||||
|
"generalOptions": {
|
||||||
|
"includeCoAuthor": "Include \"Co-authored by Claude\"",
|
||||||
|
"includeCoAuthorDesc": "Add Claude attribution to git commits and pull requests",
|
||||||
|
"verboseOutput": "Verbose Output",
|
||||||
|
"verboseOutputDesc": "Show full bash and command outputs",
|
||||||
|
"chatRetention": "Chat Transcript Retention (days)",
|
||||||
|
"chatRetentionDesc": "How long to retain chat transcripts locally (default: 30 days)",
|
||||||
|
"claudeCodeInstallation": "Claude Code Installation",
|
||||||
|
"choosePreferredInstallation": "Choose your preferred Claude Code installation.",
|
||||||
|
"loadingAvailableInstallations": "Loading available installations...",
|
||||||
|
"errorLoadingInstallations": "Error loading installations",
|
||||||
|
"availableInstallations": "Available Installations",
|
||||||
|
"claudeCodeInstallationDesc": "Select which Claude Code installation to use.",
|
||||||
|
"binaryPathChanged": "⚠️ Claude binary path has been changed. Remember to save your settings."
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"permissionRules": "Permission Rules",
|
||||||
|
"permissionRulesDesc": "Control which tools Claude Code can use without manual approval",
|
||||||
|
"allowRules": "Allow Rules",
|
||||||
|
"denyRules": "Deny Rules",
|
||||||
|
"addRule": "Add Rule",
|
||||||
|
"noAllowRules": "No allow rules configured. Claude will ask for approval for all tools.",
|
||||||
|
"noDenyRules": "No deny rules configured.",
|
||||||
|
"examples": "Examples:",
|
||||||
|
"exampleBash": "Allow all bash commands",
|
||||||
|
"exampleExactCommand": "Allow exact command",
|
||||||
|
"examplePrefix": "Allow commands with prefix",
|
||||||
|
"exampleReadFile": "Allow reading specific file",
|
||||||
|
"exampleEditDir": "Allow editing files in docs directory"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"environmentVariables": "Environment Variables",
|
||||||
|
"environmentVariablesDesc": "Environment variables applied to every Claude Code session",
|
||||||
|
"addVariable": "Add Variable",
|
||||||
|
"noEnvironmentVariables": "No environment variables configured.",
|
||||||
|
"commonVariables": "Common variables:",
|
||||||
|
"telemetryDesc": "Enable/disable telemetry (0 or 1)",
|
||||||
|
"modelDesc": "Custom model name",
|
||||||
|
"costWarningsDesc": "Disable cost warnings (1)"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"advancedSettings": "Advanced Settings",
|
||||||
|
"advancedSettingsDesc": "Additional configuration options for advanced users",
|
||||||
|
"apiKeyHelper": "API Key Helper Script",
|
||||||
|
"apiKeyHelperDesc": "Custom script to generate auth values for API requests",
|
||||||
|
"rawSettings": "Raw Settings (JSON)",
|
||||||
|
"rawSettingsDesc": "This shows the raw JSON that will be saved to ~/.claude/settings.json"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"userHooks": "User Hooks",
|
||||||
|
"userHooksDesc": "Configure hooks that apply to all Claude Code sessions for your user account. These are stored in ~/.claude/settings.json"
|
||||||
|
},
|
||||||
|
"analytics": {
|
||||||
|
"analyticsSettings": "Analytics Settings",
|
||||||
|
"enableAnalytics": "Enable Analytics",
|
||||||
|
"enableAnalyticsDesc": "Help improve Claudia by sharing anonymous usage data",
|
||||||
|
"privacyProtected": "Your privacy is protected",
|
||||||
|
"noPersonalInfo": "No personal information is collected",
|
||||||
|
"noFileContents": "No file contents, paths, or project names",
|
||||||
|
"anonymousData": "All data is anonymous with random IDs",
|
||||||
|
"canDisable": "You can disable analytics at any time",
|
||||||
|
"whatWeCollect": "What we collect:",
|
||||||
|
"featureUsage": "Feature usage patterns",
|
||||||
|
"performanceMetrics": "Performance metrics",
|
||||||
|
"errorReports": "Error reports (without sensitive data)",
|
||||||
|
"sessionFrequency": "Session frequency and duration",
|
||||||
|
"deleteAllData": "Delete All Analytics Data",
|
||||||
|
"analyticsEnabled": "Analytics enabled",
|
||||||
|
"analyticsDisabled": "Analytics disabled",
|
||||||
|
"allDataDeleted": "All analytics data deleted",
|
||||||
|
"helpImproveClaudia": "Help Improve Claudia",
|
||||||
|
"collectAnonymousData": "We'd like to collect anonymous usage data to improve your experience.",
|
||||||
|
"featureUsageDesc": "Feature usage (which tools and commands you use)",
|
||||||
|
"performanceMetricsDesc": "Performance metrics (app speed and reliability)",
|
||||||
|
"errorReportsDesc": "Error reports (to fix bugs and improve stability)",
|
||||||
|
"usagePatternsDesc": "General usage patterns (session frequency and duration)",
|
||||||
|
"noApiKeys": "No API keys or sensitive data",
|
||||||
|
"canOptOut": "You can opt-out anytime in Settings",
|
||||||
|
"dataHelpsUs": "This data helps us understand which features are most valuable, identify performance issues, and prioritize improvements. Your choice won't affect any functionality.",
|
||||||
|
"noThanks": "No Thanks",
|
||||||
|
"allowAnalytics": "Allow Analytics"
|
||||||
|
},
|
||||||
|
"saveButton": {
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
|
"settingsSavedSuccess": "Settings saved successfully!",
|
||||||
|
"settingsSaveFailed": "Failed to save settings"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadFailed": "Failed to load settings. Please ensure ~/.claude directory exists.",
|
||||||
|
"saveFailed": "Failed to save settings."
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"envVarKey": "KEY",
|
||||||
|
"envVarValue": "value",
|
||||||
|
"allowRuleExample": "e.g., Bash(npm run test:*)",
|
||||||
|
"denyRuleExample": "e.g., Bash(curl:*)",
|
||||||
|
"apiKeyHelperPath": "/path/to/generate_api_key.sh"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"title": "MCP Server Management",
|
"title": "MCP Server Management",
|
||||||
@@ -110,7 +310,118 @@
|
|||||||
"last7Days": "Last 7 Days",
|
"last7Days": "Last 7 Days",
|
||||||
"last30Days": "Last 30 Days",
|
"last30Days": "Last 30 Days",
|
||||||
"allTime": "All Time",
|
"allTime": "All Time",
|
||||||
"exportData": "Export Data"
|
"exportData": "Export Data",
|
||||||
|
"usageDashboardTitle": "Usage Dashboard",
|
||||||
|
"trackUsageAndCosts": "Track your Claude Code usage and costs",
|
||||||
|
"allTime": "All Time",
|
||||||
|
"totalCost": "Total Cost",
|
||||||
|
"totalSessions": "Total Sessions",
|
||||||
|
"totalTokens": "Total Tokens",
|
||||||
|
"avgCostPerSession": "Avg Cost/Session",
|
||||||
|
"overview": "Overview",
|
||||||
|
"tokenBreakdown": "Token Breakdown",
|
||||||
|
"inputTokens": "Input Tokens",
|
||||||
|
"outputTokens": "Output Tokens",
|
||||||
|
"cacheWrite": "Cache Write",
|
||||||
|
"cacheRead": "Cache Read",
|
||||||
|
"mostUsedModels": "Most Used Models",
|
||||||
|
"topProjects": "Top Projects",
|
||||||
|
"sessions": "sessions",
|
||||||
|
"usageByModel": "Usage by Model",
|
||||||
|
"usageByProject": "Usage by Project",
|
||||||
|
"usageBySession": "Usage by Session",
|
||||||
|
"timeline": "Timeline",
|
||||||
|
"dailyUsage": "Daily Usage",
|
||||||
|
"tokens": "tokens",
|
||||||
|
"models": "models",
|
||||||
|
"input": "Input",
|
||||||
|
"output": "Output",
|
||||||
|
"session": "session",
|
||||||
|
"loadingUsageStats": "Loading usage statistics...",
|
||||||
|
"failedToLoadUsageStats": "Failed to load usage statistics. Please try again.",
|
||||||
|
"tryAgain": "Try Again",
|
||||||
|
"dailyUsageOverTime": "Daily Usage Over Time",
|
||||||
|
"noUsageData": "No usage data available for the selected period",
|
||||||
|
"cost": "Cost",
|
||||||
|
"lastUsed": "Last Used",
|
||||||
|
"markdownEditorTitle": "Markdown Editor",
|
||||||
|
"editSystemPrompt": "Edit your Claude Code system prompt",
|
||||||
|
"claudemdFile": "CLAUDE.md file",
|
||||||
|
"loadClaudemdFailed": "Failed to load CLAUDE.md file",
|
||||||
|
"saveClaudemdFailed": "Failed to save CLAUDE.md file",
|
||||||
|
"claudemdSavedSuccess": "CLAUDE.md saved successfully",
|
||||||
|
"saveClaudemd": "Save CLAUDE.md",
|
||||||
|
"unsavedChangesConfirm": "You have unsaved changes. Are you sure you want to leave?",
|
||||||
|
"servers": "Servers",
|
||||||
|
"addServer": "Add Server",
|
||||||
|
"importExport": "Import/Export",
|
||||||
|
"mcpServerAdded": "MCP server added successfully!",
|
||||||
|
"serverRemovedSuccess": "Server \"{{name}}\" removed successfully!",
|
||||||
|
"importedServersSuccess": "Successfully imported {{count}} server{{plural}}!",
|
||||||
|
"importedServersFailed": "Imported {{imported}} server{{importedPlural}}, {{failed}} failed",
|
||||||
|
"loadMcpServersFailed": "Failed to load MCP servers. Make sure Claude Code is installed.",
|
||||||
|
"addMcpServer": "Add MCP Server",
|
||||||
|
"configureNewMcpServer": "Configure a new Model Context Protocol server",
|
||||||
|
"serverNameRequired": "Server name is required",
|
||||||
|
"commandRequired": "Command is required",
|
||||||
|
"urlRequired": "URL is required",
|
||||||
|
"failedToAddServer": "Failed to add server",
|
||||||
|
"environmentVariables": "Environment Variables",
|
||||||
|
"addVariable": "Add Variable",
|
||||||
|
"serverName": "Server Name",
|
||||||
|
"command": "Command",
|
||||||
|
"argumentsOptional": "Arguments (optional)",
|
||||||
|
"scope": "Scope",
|
||||||
|
"localProjectOnly": "Local (this project only)",
|
||||||
|
"projectSharedViaMcp": "Project (shared via .mcp.json)",
|
||||||
|
"userAllProjects": "User (all projects)",
|
||||||
|
"addingServer": "Adding Server...",
|
||||||
|
"addStdioServer": "Add Stdio Server",
|
||||||
|
"url": "URL",
|
||||||
|
"addSseServer": "Add SSE Server",
|
||||||
|
"exampleCommands": "Example Commands",
|
||||||
|
"uniqueNameToIdentify": "A unique name to identify this server",
|
||||||
|
"commandToExecuteServer": "The command to execute the server",
|
||||||
|
"spaceSeparatedArgs": "Space-separated command arguments",
|
||||||
|
"sseEndpointUrl": "The SSE endpoint URL",
|
||||||
|
"running": "Running",
|
||||||
|
"showFull": "Show full",
|
||||||
|
"hide": "Hide",
|
||||||
|
"copied": "Copied!",
|
||||||
|
"copy": "Copy",
|
||||||
|
"arguments": "Arguments",
|
||||||
|
"environmentVariables": "Environment Variables",
|
||||||
|
"configuredServers": "Configured Servers",
|
||||||
|
"serversConfigured": "configured",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"noMcpServersConfigured": "No MCP servers configured",
|
||||||
|
"addServerToGetStarted": "Add a server to get started with Model Context Protocol",
|
||||||
|
"localProjectSpecific": "Local (Project-specific)",
|
||||||
|
"projectSharedMcp": "Project (Shared via .mcp.json)",
|
||||||
|
"userAllProjects": "User (All projects)",
|
||||||
|
"welcomeToClaudia": "Welcome to Claudia",
|
||||||
|
"backToHome": "Back to Home",
|
||||||
|
"noProjectsFound": "No projects found in ~/.claude/projects",
|
||||||
|
"failedToLoadProjects": "Failed to load projects. Please ensure ~/.claude directory exists.",
|
||||||
|
"failedToLoadSessions": "Failed to load sessions for this project.",
|
||||||
|
"claudeBinaryPathSaved": "Claude binary path saved successfully",
|
||||||
|
"selectClaudeCodeInstallation": "Select Claude Code Installation",
|
||||||
|
"multipleInstallationsFound": "Multiple Claude Code installations were found on your system. Please select which one you'd like to use.",
|
||||||
|
"claudeCodeNotFoundDialog": "Claude Code was not found in any of the common installation locations. Please install Claude Code to continue.",
|
||||||
|
"searchedLocations": "Searched locations",
|
||||||
|
"installationTip": "You can install Claude Code using",
|
||||||
|
"searchingInstallations": "Searching for Claude installations...",
|
||||||
|
"installationGuide": "Installation Guide",
|
||||||
|
"validating": "Validating...",
|
||||||
|
"saveSelection": "Save Selection",
|
||||||
|
"noInstallationsFound": "No Installations Found",
|
||||||
|
"pleaseSelectInstallation": "Please select a Claude installation",
|
||||||
|
"fileSavedSuccess": "File saved successfully",
|
||||||
|
"saveFileFailed": "Failed to save file",
|
||||||
|
"loadFileFailed": "Failed to load CLAUDE.md file",
|
||||||
|
"editProjectSpecificPrompt": "Edit project-specific Claude Code system prompt",
|
||||||
|
"maximumTabsReached": "Maximum number of tabs ({{max}}) reached",
|
||||||
|
"saving": "Saving..."
|
||||||
},
|
},
|
||||||
"checkpoint": {
|
"checkpoint": {
|
||||||
"title": "Checkpoints",
|
"title": "Checkpoints",
|
||||||
@@ -121,14 +432,39 @@
|
|||||||
"checkpointMessage": "Checkpoint Message",
|
"checkpointMessage": "Checkpoint Message",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"diff": "Diff",
|
"diff": "Diff",
|
||||||
"noCheckpoints": "No checkpoints found"
|
"noCheckpoints": "No checkpoints found",
|
||||||
|
"checkpointSettingsTitle": "Checkpoint Settings",
|
||||||
|
"experimentalFeature": "Experimental Feature",
|
||||||
|
"checkpointWarning": "Checkpointing may affect directory structure or cause data loss. Use with caution.",
|
||||||
|
"automaticCheckpoints": "Automatic Checkpoints",
|
||||||
|
"automaticCheckpointsDesc": "Automatically create checkpoints based on the selected strategy",
|
||||||
|
"checkpointStrategy": "Checkpoint Strategy",
|
||||||
|
"manualOnly": "Manual Only",
|
||||||
|
"afterEachPrompt": "After Each Prompt",
|
||||||
|
"afterToolUse": "After Tool Use",
|
||||||
|
"smart": "Smart (Recommended)",
|
||||||
|
"manualOnlyDesc": "Checkpoints will only be created manually",
|
||||||
|
"afterEachPromptDesc": "A checkpoint will be created after each user prompt",
|
||||||
|
"afterToolUseDesc": "A checkpoint will be created after each tool use",
|
||||||
|
"smartDesc": "Checkpoints will be created after destructive operations",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
|
"storageManagement": "Storage Management",
|
||||||
|
"totalCheckpoints": "Total checkpoints",
|
||||||
|
"keepRecentCheckpoints": "Keep Recent Checkpoints",
|
||||||
|
"cleanUp": "Clean Up",
|
||||||
|
"removeOldCheckpoints": "Remove old checkpoints, keeping only the most recent {{count}}",
|
||||||
|
"checkpointSettingsFailed": "Failed to load checkpoint settings",
|
||||||
|
"saveCheckpointSettingsFailed": "Failed to save checkpoint settings",
|
||||||
|
"cleanupCheckpointsFailed": "Failed to cleanup checkpoints",
|
||||||
|
"removedOldCheckpoints": "Removed {{count}} old checkpoints"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"searchProjects": "Search projects...",
|
"searchProjects": "Search projects...",
|
||||||
"searchAgents": "Search agents...",
|
"searchAgents": "Search agents...",
|
||||||
"enterAgentName": "Enter agent name...",
|
"enterAgentName": "e.g., Code Assistant",
|
||||||
"enterSystemPrompt": "Enter system prompt...",
|
"enterSystemPrompt": "Enter system prompt...",
|
||||||
"enterDefaultTask": "Enter default task...",
|
"enterDefaultTask": "e.g., Review this code for security issues",
|
||||||
"enterURL": "Enter URL...",
|
"enterURL": "Enter URL...",
|
||||||
"searchCommands": "Search commands...",
|
"searchCommands": "Search commands...",
|
||||||
"enterCommand": "Enter command...",
|
"enterCommand": "Enter command...",
|
||||||
@@ -151,6 +487,34 @@
|
|||||||
"unknownError": "Unknown error occurred",
|
"unknownError": "Unknown error occurred",
|
||||||
"claudeCodeNotFound": "Claude Code not found",
|
"claudeCodeNotFound": "Claude Code not found",
|
||||||
"selectClaudeInstallation": "Select Claude Installation",
|
"selectClaudeInstallation": "Select Claude Installation",
|
||||||
"installClaudeCode": "Install Claude Code"
|
"installClaudeCode": "Install Claude Code",
|
||||||
|
"noTabsOpen": "No tabs open",
|
||||||
|
"clickPlusToStartChat": "Click the + button to start a new chat",
|
||||||
|
"noAgentRunIdSpecified": "No agent run ID specified",
|
||||||
|
"noClaudeFileIdSpecified": "No Claude file ID specified",
|
||||||
|
"claudeFileEditorNotImplemented": "Claude file editor not yet implemented in tabs",
|
||||||
|
"noAgentDataSpecified": "No agent data specified",
|
||||||
|
"importAgentComingSoon": "Import agent functionality coming soon...",
|
||||||
|
"unknownTabType": "Unknown tab type",
|
||||||
|
"letClaudeDecide": "Let Claude decide",
|
||||||
|
"basicReasoning": "Basic reasoning",
|
||||||
|
"deeperAnalysis": "Deeper analysis",
|
||||||
|
"extensiveReasoning": "Extensive reasoning",
|
||||||
|
"maximumAnalysis": "Maximum analysis",
|
||||||
|
"typeYourPromptHere": "Type your prompt here...",
|
||||||
|
"dropImagesHere": "Drop images here...",
|
||||||
|
"askClaudeAnything": "Ask Claude anything...",
|
||||||
|
"usage": "Usage",
|
||||||
|
"mcpServersTitle": "MCP Servers",
|
||||||
|
"claudemdTitle": "CLAUDE.md",
|
||||||
|
"runAgent": "Run: {{name}}",
|
||||||
|
"createAgent": "Create Agent",
|
||||||
|
"importAgent": "Import Agent",
|
||||||
|
"unsavedChangesCloseConfirm": "Tab \"{{title}}\" has unsaved changes. Close anyway?",
|
||||||
|
"session": "Session"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"pressEnterToSend": "Press Enter to send, Shift+Enter for new line",
|
||||||
|
"withFileAndCommandSupport": ", @ to mention files, / for commands, drag & drop or paste images"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"ccProjects": "Claude Code 项目",
|
||||||
|
"browseClaudeCodeSessions": "浏览您的 Claude Code 会话",
|
||||||
|
"newClaudeCodeSession": "新建 Claude Code 会话",
|
||||||
|
"ccAgents": "CC 智能体",
|
||||||
|
"mcpServers": "MCP 服务器",
|
||||||
|
"manageMcpServers": "管理模型上下文协议服务器",
|
||||||
"app": {
|
"app": {
|
||||||
"name": "Claudia",
|
"name": "Claudia",
|
||||||
"welcome": "欢迎使用 Claudia",
|
"welcome": "欢迎使用 Claudia",
|
||||||
@@ -18,11 +24,13 @@
|
|||||||
"next": "下一步",
|
"next": "下一步",
|
||||||
"previous": "上一步",
|
"previous": "上一步",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"close": "关闭",
|
"page": "页面",
|
||||||
"open": "打开"
|
"of": "共",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"from": "从"
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "CC 项目",
|
"projects": "Claude Code 项目",
|
||||||
"agents": "CC 智能体",
|
"agents": "CC 智能体",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"usage": "用量仪表板",
|
"usage": "用量仪表板",
|
||||||
@@ -47,9 +55,10 @@
|
|||||||
"editAgent": "编辑智能体",
|
"editAgent": "编辑智能体",
|
||||||
"deleteAgent": "删除智能体",
|
"deleteAgent": "删除智能体",
|
||||||
"executeAgent": "执行智能体",
|
"executeAgent": "执行智能体",
|
||||||
"agentName": "智能体名称",
|
"agentNameRequired": "代理名称为必填项",
|
||||||
"agentIcon": "智能体图标",
|
"agentIcon": "智能体图标",
|
||||||
"systemPrompt": "系统提示",
|
"systemPrompt": "系统提示",
|
||||||
|
"systemPromptRequired": "系统提示为必填项",
|
||||||
"defaultTask": "默认任务",
|
"defaultTask": "默认任务",
|
||||||
"model": "模型",
|
"model": "模型",
|
||||||
"permissions": "权限",
|
"permissions": "权限",
|
||||||
@@ -66,7 +75,60 @@
|
|||||||
"createAgentDescription": "创建新的 Claude Code 智能体",
|
"createAgentDescription": "创建新的 Claude Code 智能体",
|
||||||
"updateAgentDescription": "更新您的 Claude Code 智能体",
|
"updateAgentDescription": "更新您的 Claude Code 智能体",
|
||||||
"createFailed": "创建智能体失败",
|
"createFailed": "创建智能体失败",
|
||||||
"updateFailed": "更新智能体失败"
|
"updateFailed": "更新智能体失败",
|
||||||
|
"basicInformation": "基本信息",
|
||||||
|
"optional": "可选",
|
||||||
|
"sonnetDescription": "更快,适用于大多数任务",
|
||||||
|
"opusDescription": "功能更强,适用于复杂任务",
|
||||||
|
"defaultTaskDescription": "执行智能体时将用作默认任务占位符",
|
||||||
|
"systemPromptDescription": "定义您的 CC 智能体的行为和功能",
|
||||||
|
"manageAgents": "管理您的 Claude Code 智能体",
|
||||||
|
"noAgentsYet": "还没有智能体",
|
||||||
|
"createFirstAgent": "创建您的第一个 CC 智能体来开始",
|
||||||
|
"created": "创建于",
|
||||||
|
"execute": "执行",
|
||||||
|
"export": "导出",
|
||||||
|
"import": "导入",
|
||||||
|
"importFromFile": "从文件导入",
|
||||||
|
"importFromGitHub": "从 GitHub 导入",
|
||||||
|
"recentExecutions": "最近执行记录",
|
||||||
|
"exportedSuccessfully": "智能体 \"{{name}}\" 导出成功",
|
||||||
|
"exportFailed": "导出智能体失败",
|
||||||
|
"importedSuccessfully": "智能体导入成功",
|
||||||
|
"importFailed": "导入智能体失败",
|
||||||
|
"importFromGitHubSuccess": "从 GitHub 导入智能体成功",
|
||||||
|
"executeAgentTitle": "执行智能体",
|
||||||
|
"deleteAgentTitle": "删除智能体",
|
||||||
|
"deleteConfirmation": "确定要删除智能体 \"{{name}}\" 吗?此操作无法撤销,将永久删除智能体及其所有相关数据。",
|
||||||
|
"deleting": "删除中...",
|
||||||
|
"deleteAgentButton": "删除智能体",
|
||||||
|
"agentManagement": "代理管理",
|
||||||
|
"createNewOrManageAgents": "创建新代理或管理运行中的代理执行",
|
||||||
|
"noAgentsAvailable": "无可用代理",
|
||||||
|
"availableAgents": "可用代理",
|
||||||
|
"runningAgents": "运行中的代理",
|
||||||
|
"createFirstAgentToGetStarted": "创建您的第一个代理开始使用"
|
||||||
|
},
|
||||||
|
"slashCommands": {
|
||||||
|
"slashCommands": "斜杠命令",
|
||||||
|
"projectSlashCommands": "项目斜杠命令",
|
||||||
|
"createCustomCommandsProject": "为此项目创建自定义命令",
|
||||||
|
"createCustomCommandsWorkflow": "创建自定义命令来简化您的工作流程",
|
||||||
|
"newCommand": "新建命令",
|
||||||
|
"allCommands": "所有命令",
|
||||||
|
"project": "项目",
|
||||||
|
"user": "用户",
|
||||||
|
"noCommandsFound": "未找到命令",
|
||||||
|
"noProjectCommandsYet": "尚未创建项目命令",
|
||||||
|
"noCommandsYet": "尚未创建命令",
|
||||||
|
"createFirstProjectCommand": "创建您的第一个项目命令",
|
||||||
|
"createFirstCommand": "创建您的第一个命令"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"hooksConfiguration": "钩子配置",
|
||||||
|
"configureShellCommands": "配置在 Claude Code 生命周期的各个阶段执行的 shell 命令。",
|
||||||
|
"localSettingsNote": " 这些设置不会提交到版本控制中。",
|
||||||
|
"unsavedChanges": "您有未保存的更改。点击保存以持久化它们。"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "设置",
|
"title": "设置",
|
||||||
@@ -85,7 +147,142 @@
|
|||||||
"noProxy": "无代理",
|
"noProxy": "无代理",
|
||||||
"analyticsConsent": "分析同意",
|
"analyticsConsent": "分析同意",
|
||||||
"enableAnalytics": "启用分析",
|
"enableAnalytics": "启用分析",
|
||||||
"disableAnalytics": "禁用分析"
|
"disableAnalytics": "禁用分析",
|
||||||
|
"configurePreferences": "配置 Claude Code 偏好设置",
|
||||||
|
"generalSettings": "常规设置",
|
||||||
|
"permissionsTab": "权限",
|
||||||
|
"environmentTab": "环境",
|
||||||
|
"advancedTab": "高级",
|
||||||
|
"hooksTab": "钩子",
|
||||||
|
"commands": "命令",
|
||||||
|
"storage": "存储",
|
||||||
|
"proxy": "代理",
|
||||||
|
"analyticsTab": "分析",
|
||||||
|
"proxySettingsTitle": "代理设置",
|
||||||
|
"proxySettingsDesc": "为 Claude API 请求配置代理设置",
|
||||||
|
"enableProxyDesc": "为所有 Claude API 请求使用代理",
|
||||||
|
"noProxyDesc": "逗号分隔的主机列表,这些主机应绕过代理",
|
||||||
|
"allProxyDesc": "如果未设置协议特定代理,则用于所有协议的代理 URL",
|
||||||
|
"proxySettingsSaved": "代理设置已保存并应用成功。",
|
||||||
|
"loadProxySettingsFailed": "加载代理设置失败",
|
||||||
|
"saveProxySettingsFailed": "保存代理设置失败",
|
||||||
|
"themeSelector": {
|
||||||
|
"selectATheme": "选择主题",
|
||||||
|
"dark": "暗色",
|
||||||
|
"gray": "灰色",
|
||||||
|
"light": "浅色",
|
||||||
|
"custom": "自定义",
|
||||||
|
"choosePreferredTheme": "选择您偏好的界面配色主题"
|
||||||
|
},
|
||||||
|
"customTheme": {
|
||||||
|
"title": "自定义主题配色",
|
||||||
|
"background": "背景色",
|
||||||
|
"foreground": "前景色",
|
||||||
|
"primary": "主色",
|
||||||
|
"card": "卡片色",
|
||||||
|
"accent": "强调色",
|
||||||
|
"destructive": "危险色",
|
||||||
|
"colorValuesDesc": "使用 CSS 颜色值 (hex、rgb、oklch 等)。更改将立即生效。"
|
||||||
|
},
|
||||||
|
"generalOptions": {
|
||||||
|
"includeCoAuthor": "包含 \"由 Claude 协作\" 标识",
|
||||||
|
"includeCoAuthorDesc": "在 git 提交和拉取请求中添加 Claude 归属标识",
|
||||||
|
"verboseOutput": "详细输出",
|
||||||
|
"verboseOutputDesc": "显示完整的 bash 和命令输出",
|
||||||
|
"chatRetention": "聊天记录保留期 (天)",
|
||||||
|
"chatRetentionDesc": "本地保留聊天记录的时长(默认:30 天)",
|
||||||
|
"claudeCodeInstallation": "Claude Code 安装",
|
||||||
|
"choosePreferredInstallation": "选择您偏好的 Claude Code 安装。",
|
||||||
|
"loadingAvailableInstallations": "正在加载可用安装...",
|
||||||
|
"errorLoadingInstallations": "加载安装时出错",
|
||||||
|
"availableInstallations": "可用安装",
|
||||||
|
"claudeCodeInstallationDesc": "选择要使用的 Claude Code 安装。",
|
||||||
|
"binaryPathChanged": "⚠️ Claude 二进制路径已更改。请记住保存您的设置。"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"permissionRules": "权限规则",
|
||||||
|
"permissionRulesDesc": "控制 Claude Code 可以使用哪些工具而无需手动批准",
|
||||||
|
"allowRules": "允许规则",
|
||||||
|
"denyRules": "拒绝规则",
|
||||||
|
"addRule": "添加规则",
|
||||||
|
"noAllowRules": "未配置允许规则。Claude 将请求批准所有工具。",
|
||||||
|
"noDenyRules": "未配置拒绝规则。",
|
||||||
|
"examples": "示例:",
|
||||||
|
"exampleBash": "允许所有 bash 命令",
|
||||||
|
"exampleExactCommand": "允许精确命令",
|
||||||
|
"examplePrefix": "允许带前缀的命令",
|
||||||
|
"exampleReadFile": "允许读取指定文件",
|
||||||
|
"exampleEditDir": "允许编辑 docs 目录中的文件"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"environmentVariables": "环境变量",
|
||||||
|
"environmentVariablesDesc": "应用于每个 Claude Code 会话的环境变量",
|
||||||
|
"addVariable": "添加变量",
|
||||||
|
"noEnvironmentVariables": "未配置环境变量。",
|
||||||
|
"commonVariables": "常用变量:",
|
||||||
|
"telemetryDesc": "启用/禁用遥测 (0 或 1)",
|
||||||
|
"modelDesc": "自定义模型名称",
|
||||||
|
"costWarningsDesc": "禁用成本警告 (1)"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"advancedSettings": "高级设置",
|
||||||
|
"advancedSettingsDesc": "面向高级用户的额外配置选项",
|
||||||
|
"apiKeyHelper": "API 密钥辅助脚本",
|
||||||
|
"apiKeyHelperDesc": "为 API 请求生成认证值的自定义脚本",
|
||||||
|
"rawSettings": "原始设置 (JSON)",
|
||||||
|
"rawSettingsDesc": "这显示了将保存到 ~/.claude/settings.json 的原始 JSON 数据"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"userHooks": "用户钩子",
|
||||||
|
"userHooksDesc": "配置适用于您用户账户所有 Claude Code 会话的钩子。这些存储在 ~/.claude/settings.json 中"
|
||||||
|
},
|
||||||
|
"analytics": {
|
||||||
|
"analyticsSettings": "分析设置",
|
||||||
|
"enableAnalytics": "启用分析",
|
||||||
|
"enableAnalyticsDesc": "通过分享匿名使用数据帮助改善 Claudia",
|
||||||
|
"privacyProtected": "您的隐私受到保护",
|
||||||
|
"noPersonalInfo": "不收集个人信息",
|
||||||
|
"noFileContents": "不收集文件内容、路径或项目名称",
|
||||||
|
"anonymousData": "所有数据都是匿名的,使用随机 ID",
|
||||||
|
"canDisable": "您可以随时禁用分析",
|
||||||
|
"whatWeCollect": "我们收集什么:",
|
||||||
|
"featureUsage": "功能使用模式",
|
||||||
|
"performanceMetrics": "性能指标",
|
||||||
|
"errorReports": "错误报告(不包含敏感数据)",
|
||||||
|
"sessionFrequency": "会话频率和时长",
|
||||||
|
"deleteAllData": "删除所有分析数据",
|
||||||
|
"analyticsEnabled": "分析已启用",
|
||||||
|
"analyticsDisabled": "分析已禁用",
|
||||||
|
"allDataDeleted": "所有分析数据已删除",
|
||||||
|
"helpImproveClaudia": "帮助改善 Claudia",
|
||||||
|
"collectAnonymousData": "我们希望收集匿名使用数据以改善您的使用体验。",
|
||||||
|
"featureUsageDesc": "功能使用(您使用的工具和命令)",
|
||||||
|
"performanceMetricsDesc": "性能指标(应用速度和可靠性)",
|
||||||
|
"errorReportsDesc": "错误报告(用于修复错误和提高稳定性)",
|
||||||
|
"usagePatternsDesc": "一般使用模式(会话频率和持续时间)",
|
||||||
|
"noApiKeys": "不收集 API 密钥或敏感数据",
|
||||||
|
"canOptOut": "您可以随时在设置中选择退出",
|
||||||
|
"dataHelpsUs": "这些数据有助于我们了解哪些功能最有价值,识别性能问题,并优先进行改进。您的选择不会影响任何功能。",
|
||||||
|
"noThanks": "不用了",
|
||||||
|
"allowAnalytics": "允许分析"
|
||||||
|
},
|
||||||
|
"saveButton": {
|
||||||
|
"saving": "保存中...",
|
||||||
|
"saveSettings": "保存设置",
|
||||||
|
"settingsSavedSuccess": "设置保存成功!",
|
||||||
|
"settingsSaveFailed": "保存设置失败"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadFailed": "加载设置失败。请确保 ~/.claude 目录存在。",
|
||||||
|
"saveFailed": "保存设置失败。"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"envVarKey": "键名",
|
||||||
|
"envVarValue": "值",
|
||||||
|
"allowRuleExample": "例如:Bash(npm run test:*)",
|
||||||
|
"denyRuleExample": "例如:Bash(curl:*)",
|
||||||
|
"apiKeyHelperPath": "/path/to/generate_api_key.sh"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"title": "MCP 服务器管理",
|
"title": "MCP 服务器管理",
|
||||||
@@ -110,7 +307,140 @@
|
|||||||
"last7Days": "最近 7 天",
|
"last7Days": "最近 7 天",
|
||||||
"last30Days": "最近 30 天",
|
"last30Days": "最近 30 天",
|
||||||
"allTime": "全部时间",
|
"allTime": "全部时间",
|
||||||
"exportData": "导出数据"
|
"exportData": "导出数据",
|
||||||
|
"usageDashboardTitle": "用量仪表板",
|
||||||
|
"trackUsageAndCosts": "跟踪您的 Claude Code 用量和成本",
|
||||||
|
"allTime": "全部时间",
|
||||||
|
"totalCost": "总成本",
|
||||||
|
"totalSessions": "总会话数",
|
||||||
|
"totalTokens": "总令牌数",
|
||||||
|
"avgCostPerSession": "平均每会话成本",
|
||||||
|
"overview": "概览",
|
||||||
|
"tokenBreakdown": "令牌明细",
|
||||||
|
"inputTokens": "输入令牌",
|
||||||
|
"outputTokens": "输出令牌",
|
||||||
|
"cacheWrite": "缓存写入",
|
||||||
|
"cacheRead": "缓存读取",
|
||||||
|
"mostUsedModels": "最常用模型",
|
||||||
|
"topProjects": "热门项目",
|
||||||
|
"sessions": "会话",
|
||||||
|
"usageByModel": "按模型统计用量",
|
||||||
|
"usageByProject": "按项目统计用量",
|
||||||
|
"usageBySession": "按会话统计用量",
|
||||||
|
"timeline": "时间线",
|
||||||
|
"dailyUsage": "日常用量",
|
||||||
|
"tokens": "令牌",
|
||||||
|
"models": "模型",
|
||||||
|
"input": "输入",
|
||||||
|
"output": "输出",
|
||||||
|
"session": "会话",
|
||||||
|
"loadingUsageStats": "加载用量统计中...",
|
||||||
|
"failedToLoadUsageStats": "加载用量统计失败。请重试。",
|
||||||
|
"tryAgain": "重试",
|
||||||
|
"dailyUsageOverTime": "随时间变化的日常用量",
|
||||||
|
"noUsageData": "选定时期内无用量数据",
|
||||||
|
"cost": "成本",
|
||||||
|
"lastUsed": "上次使用",
|
||||||
|
"markdownEditorTitle": "Markdown 编辑器",
|
||||||
|
"editSystemPrompt": "编辑您的 Claude Code 系统提示",
|
||||||
|
"claudemdFile": "CLAUDE.md 文件",
|
||||||
|
"loadClaudemدFailed": "加载 CLAUDE.md 文件失败",
|
||||||
|
"saveClaudemdFailed": "保存 CLAUDE.md 文件失败",
|
||||||
|
"claudemdSavedSuccess": "CLAUDE.md 保存成功",
|
||||||
|
"saveClaudemd": "保存 CLAUDE.md",
|
||||||
|
"unsavedChangesConfirm": "您有未保存的更改。确定要离开吗?",
|
||||||
|
"servers": "服务器",
|
||||||
|
"addServer": "添加服务器",
|
||||||
|
"importExport": "导入/导出",
|
||||||
|
"mcpServerAdded": "MCP 服务器添加成功!",
|
||||||
|
"serverRemovedSuccess": "服务器 \"{{name}}\" 删除成功!",
|
||||||
|
"importedServersSuccess": "成功导入 {{count}} 个服务器!",
|
||||||
|
"importedServersFailed": "导入 {{imported}} 个服务器,{{failed}} 个失败",
|
||||||
|
"loadMcpServersFailed": "加载 MCP 服务器失败。请确保 Claude Code 已安装。",
|
||||||
|
"addMcpServer": "添加 MCP 服务器",
|
||||||
|
"configureNewMcpServer": "配置新的模型上下文协议服务器",
|
||||||
|
"serverNameRequired": "服务器名称为必填项",
|
||||||
|
"commandRequired": "命令为必填项",
|
||||||
|
"urlRequired": "URL为必填项",
|
||||||
|
"failedToAddServer": "添加服务器失败",
|
||||||
|
"environmentVariables": "环境变量",
|
||||||
|
"addVariable": "添加变量",
|
||||||
|
"serverName": "服务器名称",
|
||||||
|
"command": "命令",
|
||||||
|
"argumentsOptional": "参数(可选)",
|
||||||
|
"scope": "作用域",
|
||||||
|
"localProjectOnly": "本地(仅此项目)",
|
||||||
|
"projectSharedViaMcp": "项目(通过 .mcp.json 共享)",
|
||||||
|
"addingServer": "添加服务器中...",
|
||||||
|
"addStdioServer": "添加 Stdio 服务器",
|
||||||
|
"url": "URL",
|
||||||
|
"addSseServer": "添加 SSE 服务器",
|
||||||
|
"exampleCommands": "示例命令",
|
||||||
|
"uniqueNameToIdentify": "用于标识此服务器的唯一名称",
|
||||||
|
"commandToExecuteServer": "执行服务器的命令",
|
||||||
|
"spaceSeparatedArgs": "空格分隔的命令参数",
|
||||||
|
"sseEndpointUrl": "SSE 端点 URL",
|
||||||
|
"running": "运行中",
|
||||||
|
"showFull": "显示全部",
|
||||||
|
"hide": "隐藏",
|
||||||
|
"copied": "已复制",
|
||||||
|
"copy": "复制",
|
||||||
|
"arguments": "参数",
|
||||||
|
"environmentVariables": "环境变量",
|
||||||
|
"configuredServers": "已配置的服务器",
|
||||||
|
"serversConfigured": "个服务器已配置",
|
||||||
|
"refresh": "刷新",
|
||||||
|
"noMcpServersConfigured": "未配置 MCP 服务器",
|
||||||
|
"addServerToGetStarted": "添加一个服务器开始使用模型上下文协议",
|
||||||
|
"localProjectSpecific": "本地(项目特定)",
|
||||||
|
"projectSharedMcp": "项目(通过 .mcp.json 共享)",
|
||||||
|
"userAllProjects": "用户(所有项目)",
|
||||||
|
"letClaudeDecide": "让 Claude 决定",
|
||||||
|
"basicReasoning": "基础推理",
|
||||||
|
"deeperAnalysis": "更深入的分析",
|
||||||
|
"extensiveReasoning": "广泛推理",
|
||||||
|
"maximumAnalysis": "最大化分析",
|
||||||
|
"typeYourPromptHere": "在此输入您的提示...",
|
||||||
|
"dropImagesHere": "在此拖放图像...",
|
||||||
|
"askClaudeAnything": "向 Claude 提问任何问题...",
|
||||||
|
"usage": "用量",
|
||||||
|
"mcpServersTitle": "MCP 服务器",
|
||||||
|
"claudemdTitle": "CLAUDE.md",
|
||||||
|
"runAgent": "运行: {{name}}",
|
||||||
|
"createAgent": "创建智能体",
|
||||||
|
"importAgent": "导入智能体",
|
||||||
|
"unsavedChangesCloseConfirm": "标签页 \"{{title}}\" 有未保存的更改。仍要关闭吗?",
|
||||||
|
"welcomeToClaudia": "欢迎使用 Claudia",
|
||||||
|
"backToHome": "返回首页",
|
||||||
|
"noProjectsFound": "在 ~/.claude/projects 中未找到项目",
|
||||||
|
"failedToLoadProjects": "加载项目失败。请确保 ~/.claude 目录存在。",
|
||||||
|
"failedToLoadSessions": "加载此项目的会话失败。",
|
||||||
|
"claudeBinaryPathSaved": "Claude 二进制路径保存成功",
|
||||||
|
"noTabsOpen": "未打开标签页",
|
||||||
|
"clickPlusToStartChat": "点击 + 按钮开始新聊天",
|
||||||
|
"noAgentRunIdSpecified": "未指定智能体运行ID",
|
||||||
|
"noClaudeFileIdSpecified": "未指定Claude文件ID",
|
||||||
|
"claudeFileEditorNotImplemented": "标签页中的Claude文件编辑器尚未实现",
|
||||||
|
"noAgentDataSpecified": "未指定智能体数据",
|
||||||
|
"importAgentComingSoon": "导入智能体功能即将推出...",
|
||||||
|
"unknownTabType": "未知的标签页类型",
|
||||||
|
"selectClaudeCodeInstallation": "选择 Claude Code 安装",
|
||||||
|
"multipleInstallationsFound": "在您的系统上找到了多个 Claude Code 安装。请选择您要使用的安装。",
|
||||||
|
"claudeCodeNotFoundDialog": "在常见安装位置中未找到 Claude Code。请安装 Claude Code 以继续。",
|
||||||
|
"searchedLocations": "搜索位置",
|
||||||
|
"installationTip": "提示:您可以使用以下命令安装 Claude Code",
|
||||||
|
"searchingInstallations": "正在搜索 Claude 安装...",
|
||||||
|
"installationGuide": "安装指南",
|
||||||
|
"validating": "验证中...",
|
||||||
|
"saveSelection": "保存选择",
|
||||||
|
"noInstallationsFound": "未找到安装",
|
||||||
|
"pleaseSelectInstallation": "请选择一个 Claude 安装",
|
||||||
|
"fileSavedSuccess": "文件保存成功",
|
||||||
|
"saveFileFailed": "保存文件失败",
|
||||||
|
"loadFileFailed": "加载 CLAUDE.md 文件失败",
|
||||||
|
"editProjectSpecificPrompt": "编辑项目特定的 Claude Code 系统提示",
|
||||||
|
"maximumTabsReached": "已达到最大标签页数量 ({{max}})",
|
||||||
|
"saving": "保存中..."
|
||||||
},
|
},
|
||||||
"checkpoint": {
|
"checkpoint": {
|
||||||
"title": "检查点",
|
"title": "检查点",
|
||||||
@@ -121,14 +451,44 @@
|
|||||||
"checkpointMessage": "检查点消息",
|
"checkpointMessage": "检查点消息",
|
||||||
"timeline": "时间线",
|
"timeline": "时间线",
|
||||||
"diff": "差异",
|
"diff": "差异",
|
||||||
"noCheckpoints": "未找到检查点"
|
"deleteCheckpoint": "删除检查点",
|
||||||
|
"checkpointName": "检查点名称",
|
||||||
|
"checkpointMessage": "检查点消息",
|
||||||
|
"timeline": "时间线",
|
||||||
|
"diff": "差异",
|
||||||
|
"noCheckpoints": "未找到检查点",
|
||||||
|
"checkpointSettingsTitle": "检查点设置",
|
||||||
|
"experimentalFeature": "实验性功能",
|
||||||
|
"checkpointWarning": "检查点可能会影响目录结构或导致数据丢失。请谨慎使用。",
|
||||||
|
"automaticCheckpoints": "自动检查点",
|
||||||
|
"automaticCheckpointsDesc": "根据所选策略自动创建检查点",
|
||||||
|
"checkpointStrategy": "检查点策略",
|
||||||
|
"manualOnly": "仅手动",
|
||||||
|
"afterEachPrompt": "每个提示后",
|
||||||
|
"afterToolUse": "工具使用后",
|
||||||
|
"smart": "智能(推荐)",
|
||||||
|
"manualOnlyDesc": "检查点将仅手动创建",
|
||||||
|
"afterEachPromptDesc": "在每个用户提示后创建检查点",
|
||||||
|
"afterToolUseDesc": "在每次工具使用后创建检查点",
|
||||||
|
"smartDesc": "在破坏性操作后创建检查点",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"saveSettings": "保存设置",
|
||||||
|
"storageManagement": "存储管理",
|
||||||
|
"totalCheckpoints": "总检查点数",
|
||||||
|
"keepRecentCheckpoints": "保留最近检查点",
|
||||||
|
"cleanUp": "清理",
|
||||||
|
"removeOldCheckpoints": "删除旧检查点,仅保留最近的 {{count}} 个",
|
||||||
|
"checkpointSettingsFailed": "加载检查点设置失败",
|
||||||
|
"saveCheckpointSettingsFailed": "保存检查点设置失败",
|
||||||
|
"cleanupCheckpointsFailed": "清理检查点失败",
|
||||||
|
"removedOldCheckpoints": "已删除 {{count}} 个旧检查点"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"searchProjects": "搜索项目...",
|
"searchProjects": "搜索项目...",
|
||||||
"searchAgents": "搜索智能体...",
|
"searchAgents": "搜索智能体...",
|
||||||
"enterAgentName": "输入智能体名称...",
|
"enterAgentName": "例如:代码助手",
|
||||||
"enterSystemPrompt": "输入系统提示...",
|
"enterSystemPrompt": "输入系统提示...",
|
||||||
"enterDefaultTask": "输入默认任务...",
|
"enterDefaultTask": "例如:检查这段代码的安全问题",
|
||||||
"enterURL": "输入 URL...",
|
"enterURL": "输入 URL...",
|
||||||
"searchCommands": "搜索命令...",
|
"searchCommands": "搜索命令...",
|
||||||
"enterCommand": "输入命令...",
|
"enterCommand": "输入命令...",
|
||||||
@@ -151,6 +511,12 @@
|
|||||||
"unknownError": "未知错误",
|
"unknownError": "未知错误",
|
||||||
"claudeCodeNotFound": "未找到 Claude Code",
|
"claudeCodeNotFound": "未找到 Claude Code",
|
||||||
"selectClaudeInstallation": "选择 Claude 安装",
|
"selectClaudeInstallation": "选择 Claude 安装",
|
||||||
"installClaudeCode": "安装 Claude Code"
|
"installClaudeCode": "安装 Claude Code",
|
||||||
|
"session": "会话"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"pressEnterToSend": "按 Enter 发送,Shift+Enter 换行",
|
||||||
|
"withFileAndCommandSupport": ",@ 提及文件,/ 调用命令,拖拽或粘贴图片"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user