汉化
This commit is contained in:
@@ -21,6 +21,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Toast } from '@/components/ui/toast';
|
||||
import { api, type Agent, type AgentRunWithMetrics } from '@/lib/api';
|
||||
import { useTabState } from '@/hooks/useTabState';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { formatISOTimestamp } from '@/lib/date-utils';
|
||||
import { open as openDialog, save } from '@tauri-apps/plugin-dialog';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
@@ -32,6 +33,7 @@ interface AgentsModalProps {
|
||||
}
|
||||
|
||||
export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState('agents');
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [runningAgents, setRunningAgents] = useState<AgentRunWithMetrics[]>([]);
|
||||
@@ -201,18 +203,18 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
<DialogHeader className="px-6 pt-6">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bot className="w-5 h-5" />
|
||||
Agent Management
|
||||
{t('agents.agentManagement')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create new agents or manage running agent executions
|
||||
{t('agents.createNewOrManageAgents')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
||||
<TabsList className="mx-6">
|
||||
<TabsTrigger value="agents">Available Agents</TabsTrigger>
|
||||
<TabsTrigger value="agents">{t('agents.availableAgents')}</TabsTrigger>
|
||||
<TabsTrigger value="running" className="relative">
|
||||
Running Agents
|
||||
{t('agents.runningAgents')}
|
||||
{runningAgents.length > 0 && (
|
||||
<Badge variant="secondary" className="ml-2 h-5 px-1.5">
|
||||
{runningAgents.length}
|
||||
@@ -257,9 +259,9 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
) : agents.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<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">
|
||||
Create your first agent to get started
|
||||
{t('agents.createFirstAgentToGetStarted')}
|
||||
</p>
|
||||
<Button onClick={() => {
|
||||
onOpenChange(false);
|
||||
|
@@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { analytics } from '@/lib/analytics';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface AnalyticsConsentProps {
|
||||
open?: boolean;
|
||||
@@ -18,6 +19,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
onOpenChange,
|
||||
onComplete,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [internalOpen, setInternalOpen] = 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">
|
||||
<BarChart3 className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<DialogTitle className="text-2xl">Help Improve Claudia</DialogTitle>
|
||||
<DialogTitle className="text-2xl">{t('analytics.helpImproveClaudia')}</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="text-base mt-2">
|
||||
We'd like to collect anonymous usage data to improve your experience.
|
||||
{t('analytics.collectAnonymousData')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</div>
|
||||
@@ -84,12 +86,12 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-3">
|
||||
<Check className="h-5 w-5 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<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">
|
||||
<li>• Feature usage (which tools and commands you use)</li>
|
||||
<li>• Performance metrics (app speed and reliability)</li>
|
||||
<li>• Error reports (to fix bugs and improve stability)</li>
|
||||
<li>• General usage patterns (session frequency and duration)</li>
|
||||
<li>• {t('analytics.featureUsageDesc')}</li>
|
||||
<li>• {t('analytics.performanceMetricsDesc')}</li>
|
||||
<li>• {t('analytics.errorReportsDesc')}</li>
|
||||
<li>• {t('analytics.usagePatternsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,13 +101,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-3">
|
||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<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">
|
||||
<li>• No personal information is collected</li>
|
||||
<li>• No file contents, paths, or project names</li>
|
||||
<li>• No API keys or sensitive data</li>
|
||||
<li>• Completely anonymous with random IDs</li>
|
||||
<li>• You can opt-out anytime in Settings</li>
|
||||
<li>• {t('analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('analytics.noFileContents')}</li>
|
||||
<li>• {t('analytics.noApiKeys')}</li>
|
||||
<li>• {t('analytics.anonymousData')}</li>
|
||||
<li>• {t('analytics.canOptOut')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,8 +118,7 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
<div className="flex gap-2 items-start">
|
||||
<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">
|
||||
This data helps us understand which features are most valuable, identify performance
|
||||
issues, and prioritize improvements. Your choice won't affect any functionality.
|
||||
{t('analytics.dataHelpsUs')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,13 +130,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
No Thanks
|
||||
{t('analytics.noThanks')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAccept}
|
||||
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
Allow Analytics
|
||||
{t('analytics.allowAnalytics')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@@ -40,6 +40,7 @@ import { AgentExecution } from "./AgentExecution";
|
||||
import { AgentRunsList } from "./AgentRunsList";
|
||||
import { GitHubAgentBrowser } from "./GitHubAgentBrowser";
|
||||
import { ICON_MAP } from "./IconPicker";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface CCAgentsProps {
|
||||
/**
|
||||
@@ -64,6 +65,7 @@ export type AgentIconName = keyof typeof AGENT_ICONS;
|
||||
* <CCAgents onBack={() => setView('home')} />
|
||||
*/
|
||||
export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
const { t } = useTranslation();
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [runs, setRuns] = useState<AgentRunWithMetrics[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -94,8 +96,8 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
setAgents(agentsList);
|
||||
} catch (err) {
|
||||
console.error("Failed to load agents:", err);
|
||||
setError("Failed to load agents");
|
||||
setToast({ message: "Failed to load agents", type: "error" });
|
||||
setError(t('agents.loadAgentsFailed'));
|
||||
setToast({ message: t('agents.loadAgentsFailed'), type: "error" });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -132,12 +134,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await api.deleteAgent(agentToDelete.id);
|
||||
setToast({ message: "Agent deleted successfully", type: "success" });
|
||||
setToast({ message: t('agents.agentDeleted'), type: "success" });
|
||||
await loadAgents();
|
||||
await loadRuns(); // Reload runs as they might be affected
|
||||
} catch (err) {
|
||||
console.error("Failed to delete agent:", err);
|
||||
setToast({ message: "Failed to delete agent", type: "error" });
|
||||
setToast({ message: t('agents.deleteFailed'), type: "error" });
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setShowDeleteDialog(false);
|
||||
@@ -166,13 +168,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
const handleAgentCreated = async () => {
|
||||
setView("list");
|
||||
await loadAgents();
|
||||
setToast({ message: "Agent created successfully", type: "success" });
|
||||
setToast({ message: t('agents.agentCreated'), type: "success" });
|
||||
};
|
||||
|
||||
const handleAgentUpdated = async () => {
|
||||
setView("list");
|
||||
await loadAgents();
|
||||
setToast({ message: "Agent updated successfully", type: "success" });
|
||||
setToast({ message: t('agents.agentUpdated'), type: "success" });
|
||||
};
|
||||
|
||||
// const handleRunClick = (run: AgentRunWithMetrics) => {
|
||||
@@ -209,10 +211,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
filePath
|
||||
});
|
||||
|
||||
setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" });
|
||||
setToast({ message: t('agents.exportedSuccessfully', { name: agent.name }), type: "success" });
|
||||
} catch (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
|
||||
await api.importAgentFromFile(filePath as string);
|
||||
|
||||
setToast({ message: "Agent imported successfully", type: "success" });
|
||||
setToast({ message: t('agents.importedSuccessfully'), type: "success" });
|
||||
await loadAgents();
|
||||
} catch (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" });
|
||||
}
|
||||
};
|
||||
@@ -308,9 +310,9 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<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">
|
||||
Manage your Claude Code agents
|
||||
{t('agents.manageAgents')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -323,18 +325,18 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Import
|
||||
{t('agents.import')}
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={handleImportAgent}>
|
||||
<FileJson className="h-4 w-4 mr-2" />
|
||||
From File
|
||||
{t('agents.importFromFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setShowGitHubBrowser(true)}>
|
||||
<Globe className="h-4 w-4 mr-2" />
|
||||
From GitHub
|
||||
{t('agents.importFromGitHub')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -344,7 +346,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Create CC Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,13 +383,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
) : agents.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||
<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">
|
||||
Create your first CC Agent to get started
|
||||
{t('agents.createFirstAgent')}
|
||||
</p>
|
||||
<Button onClick={() => setView("create")} size="default">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create CC Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -411,7 +413,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
{agent.name}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Created: {new Date(agent.created_at).toLocaleDateString()}
|
||||
{t('agents.created')}: {new Date(agent.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
</CardContent>
|
||||
<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"
|
||||
onClick={() => handleExecuteAgent(agent)}
|
||||
className="flex items-center gap-1"
|
||||
title="Execute agent"
|
||||
title={t('agents.executeAgentTitle')}
|
||||
>
|
||||
<Play className="h-3 w-3" />
|
||||
Execute
|
||||
{t('agents.execute')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleEditAgent(agent)}
|
||||
className="flex items-center gap-1"
|
||||
title="Edit agent"
|
||||
title={t('agents.editAgent')}
|
||||
>
|
||||
<Edit className="h-3 w-3" />
|
||||
Edit
|
||||
{t('app.edit')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -443,17 +445,17 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
title="Export agent to .claudia.json"
|
||||
>
|
||||
<Upload className="h-3 w-3" />
|
||||
Export
|
||||
{t('agents.export')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDeleteAgent(agent)}
|
||||
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
||||
title="Delete agent"
|
||||
title={t('agents.deleteAgent')}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
Delete
|
||||
{t('app.delete')}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
@@ -471,10 +473,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
{t('app.previous')}
|
||||
</Button>
|
||||
<span className="flex items-center px-3 text-sm">
|
||||
Page {currentPage} of {totalPages}
|
||||
{t('app.page')} {currentPage} {t('app.of')} {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -482,7 +484,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
{t('app.next')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -495,7 +497,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
<div className="overflow-hidden">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<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>
|
||||
{runsLoading ? (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
@@ -531,7 +533,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
onImportSuccess={async () => {
|
||||
setShowGitHubBrowser(false);
|
||||
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>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
Delete Agent
|
||||
{t('agents.deleteAgentTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete the agent "{agentToDelete?.name}"?
|
||||
This action cannot be undone and will permanently remove the agent and all its associated data.
|
||||
{t('agents.deleteConfirmation', { name: agentToDelete?.name })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<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}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Cancel
|
||||
{t('app.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -566,12 +567,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<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" />
|
||||
Delete Agent
|
||||
{t('agents.deleteAgentButton')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
@@ -14,6 +14,7 @@ import { SelectComponent, type SelectOption } from "@/components/ui/select";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api, type CheckpointStrategy } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface CheckpointSettingsProps {
|
||||
sessionId: string;
|
||||
@@ -40,6 +41,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
onClose,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [autoCheckpointEnabled, setAutoCheckpointEnabled] = useState(true);
|
||||
const [checkpointStrategy, setCheckpointStrategy] = useState<CheckpointStrategy>("smart");
|
||||
const [totalCheckpoints, setTotalCheckpoints] = useState(0);
|
||||
@@ -50,10 +52,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
|
||||
const strategyOptions: SelectOption[] = [
|
||||
{ value: "manual", label: "Manual Only" },
|
||||
{ value: "per_prompt", label: "After Each Prompt" },
|
||||
{ value: "per_tool_use", label: "After Tool Use" },
|
||||
{ value: "smart", label: "Smart (Recommended)" },
|
||||
{ value: "manual", label: t('checkpoint.manualOnly') },
|
||||
{ value: "per_prompt", label: t('checkpoint.afterEachPrompt') },
|
||||
{ value: "per_tool_use", label: t('checkpoint.afterToolUse') },
|
||||
{ value: "smart", label: t('checkpoint.smart') },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,7 +73,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
setTotalCheckpoints(settings.total_checkpoints);
|
||||
} catch (err) {
|
||||
console.error("Failed to load checkpoint settings:", err);
|
||||
setError("Failed to load checkpoint settings");
|
||||
setError(t('checkpoint.checkpointSettingsFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -91,11 +93,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
checkpointStrategy
|
||||
);
|
||||
|
||||
setSuccessMessage("Settings saved successfully");
|
||||
setSuccessMessage(t('messages.saveSuccess'));
|
||||
setTimeout(() => setSuccessMessage(null), 3000);
|
||||
} catch (err) {
|
||||
console.error("Failed to save checkpoint settings:", err);
|
||||
setError("Failed to save checkpoint settings");
|
||||
setError(t('checkpoint.saveCheckpointSettingsFailed'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -114,14 +116,14 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
keepCount
|
||||
);
|
||||
|
||||
setSuccessMessage(`Removed ${removed} old checkpoints`);
|
||||
setSuccessMessage(t('checkpoint.removedOldCheckpoints', { count: removed }));
|
||||
setTimeout(() => setSuccessMessage(null), 3000);
|
||||
|
||||
// Reload settings to get updated count
|
||||
await loadSettings();
|
||||
} catch (err) {
|
||||
console.error("Failed to cleanup checkpoints:", err);
|
||||
setError("Failed to cleanup checkpoints");
|
||||
setError(t('checkpoint.cleanupCheckpointsFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -137,11 +139,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
{onClose && (
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||
Close
|
||||
{t('app.close')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -151,9 +153,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-yellow-600 mt-0.5" />
|
||||
<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">
|
||||
Checkpointing may affect directory structure or cause data loss. Use with caution.
|
||||
{t('checkpoint.checkpointWarning')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,9 +188,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
{/* Auto-checkpoint toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<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">
|
||||
Automatically create checkpoints based on the selected strategy
|
||||
{t('checkpoint.automaticCheckpointsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -201,7 +203,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
|
||||
{/* Checkpoint strategy */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="strategy">Checkpoint Strategy</Label>
|
||||
<Label htmlFor="strategy">{t('checkpoint.checkpointStrategy')}</Label>
|
||||
<SelectComponent
|
||||
value={checkpointStrategy}
|
||||
onValueChange={(value: string) => setCheckpointStrategy(value as CheckpointStrategy)}
|
||||
@@ -209,10 +211,10 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
disabled={isLoading || !autoCheckpointEnabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{checkpointStrategy === "manual" && "Checkpoints will only be created manually"}
|
||||
{checkpointStrategy === "per_prompt" && "A checkpoint will be created after each user prompt"}
|
||||
{checkpointStrategy === "per_tool_use" && "A checkpoint will be created after each tool use"}
|
||||
{checkpointStrategy === "smart" && "Checkpoints will be created after destructive operations"}
|
||||
{checkpointStrategy === "manual" && t('checkpoint.manualOnlyDesc')}
|
||||
{checkpointStrategy === "per_prompt" && t('checkpoint.afterEachPromptDesc')}
|
||||
{checkpointStrategy === "per_tool_use" && t('checkpoint.afterToolUseDesc')}
|
||||
{checkpointStrategy === "smart" && t('checkpoint.smartDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -225,12 +227,12 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2 animate-spin" />
|
||||
Saving...
|
||||
{t('checkpoint.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
Save Settings
|
||||
{t('checkpoint.saveSettings')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -239,9 +241,9 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
<div className="border-t pt-6 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>Storage Management</Label>
|
||||
<Label>{t('checkpoint.storageManagement')}</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Total checkpoints: {totalCheckpoints}
|
||||
{t('checkpoint.totalCheckpoints')}: {totalCheckpoints}
|
||||
</p>
|
||||
</div>
|
||||
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
||||
@@ -249,7 +251,7 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
|
||||
{/* Cleanup settings */}
|
||||
<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">
|
||||
<Input
|
||||
id="keep-count"
|
||||
@@ -267,11 +269,11 @@ export const CheckpointSettings: React.FC<CheckpointSettingsProps> = ({
|
||||
disabled={isLoading || totalCheckpoints <= keepCount}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Clean Up
|
||||
{t('checkpoint.cleanUp')}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Remove old checkpoints, keeping only the most recent {keepCount}
|
||||
{t('checkpoint.removeOldCheckpoints', { count: keepCount })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ExternalLink, FileQuestion, Terminal, AlertCircle, Loader2 } from "lucide-react";
|
||||
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface ClaudeBinaryDialogProps {
|
||||
open: boolean;
|
||||
@@ -13,6 +14,7 @@ interface ClaudeBinaryDialogProps {
|
||||
}
|
||||
|
||||
export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: ClaudeBinaryDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [hasInstallations, setHasInstallations] = useState(true);
|
||||
@@ -39,7 +41,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedInstallation) {
|
||||
onError("Please select a Claude installation");
|
||||
onError(t('pleaseSelectInstallation'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,29 +64,27 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileQuestion className="w-5 h-5" />
|
||||
Select Claude Code Installation
|
||||
{t('selectClaudeCodeInstallation')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-3 mt-4">
|
||||
{checkingInstallations ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">Searching for Claude installations...</span>
|
||||
<span className="ml-2 text-sm text-muted-foreground">{t('searchingInstallations')}</span>
|
||||
</div>
|
||||
) : hasInstallations ? (
|
||||
<p>
|
||||
Multiple Claude Code installations were found on your system.
|
||||
Please select which one you'd like to use.
|
||||
{t('multipleInstallationsFound')}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
Claude Code was not found in any of the common installation locations.
|
||||
Please install Claude Code to continue.
|
||||
{t('claudeCodeNotFoundDialog')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<AlertCircle className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">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
|
||||
</p>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md">
|
||||
<Terminal className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium">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>
|
||||
</p>
|
||||
</div>
|
||||
@@ -118,20 +118,20 @@ export function ClaudeBinaryDialog({ open, onOpenChange, onSuccess, onError }: C
|
||||
className="mr-auto"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Installation Guide
|
||||
{t('installationGuide')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isValidating}
|
||||
>
|
||||
Cancel
|
||||
{t('app.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isValidating || !selectedInstallation || !hasInstallations}
|
||||
>
|
||||
{isValidating ? "Validating..." : hasInstallations ? "Save Selection" : "No Installations Found"}
|
||||
{isValidating ? t('validating') : hasInstallations ? t('saveSelection') : t('noInstallationsFound')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||
import { api, type ClaudeMdFile } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface ClaudeFileEditorProps {
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [originalContent, setOriginalContent] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -59,7 +61,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
setOriginalContent(fileContent);
|
||||
} catch (err) {
|
||||
console.error("Failed to load file:", err);
|
||||
setError("Failed to load CLAUDE.md file");
|
||||
setError(t('loadFileFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -72,11 +74,11 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
setToast(null);
|
||||
await api.saveClaudeMdFile(file.absolute_path, content);
|
||||
setOriginalContent(content);
|
||||
setToast({ message: "File saved successfully", type: "success" });
|
||||
setToast({ message: t('fileSavedSuccess'), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save file:", err);
|
||||
setError("Failed to save CLAUDE.md file");
|
||||
setToast({ message: "Failed to save file", type: "error" });
|
||||
setError(t('saveFileFailed'));
|
||||
setToast({ message: t('saveFileFailed'), type: "error" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -85,7 +87,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
const handleBack = () => {
|
||||
if (hasChanges) {
|
||||
const confirmLeave = window.confirm(
|
||||
"You have unsaved changes. Are you sure you want to leave?"
|
||||
t('unsavedChangesConfirm')
|
||||
);
|
||||
if (!confirmLeave) return;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-lg font-semibold truncate">{file.relative_path}</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Edit project-specific Claude Code system prompt
|
||||
{t('editProjectSpecificPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +131,7 @@ export const ClaudeFileEditor: React.FC<ClaudeFileEditorProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "Saving..." : "Save"}
|
||||
{saving ? t('saving') : t('app.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { api, type ClaudeInstallation } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CheckCircle, HardDrive, Settings } from "lucide-react";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface ClaudeVersionSelectorProps {
|
||||
/**
|
||||
@@ -53,6 +54,7 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
onSave,
|
||||
isSaving = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [installations, setInstallations] = useState<ClaudeInstallation[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -132,8 +134,8 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Claude Code Installation</CardTitle>
|
||||
<CardDescription>Loading available installations...</CardDescription>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.loadingAvailableInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-center py-4">
|
||||
@@ -148,13 +150,13 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Claude Code Installation</CardTitle>
|
||||
<CardDescription>Error loading installations</CardDescription>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.errorLoadingInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-sm text-destructive mb-4">{error}</div>
|
||||
<Button onClick={loadInstallations} variant="outline" size="sm">
|
||||
Retry
|
||||
{t('app.retry')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -169,16 +171,16 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="h-5 w-5" />
|
||||
Claude Code Installation
|
||||
{t('settings.claudeCodeInstallation')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Choose your preferred Claude Code installation.
|
||||
{t('settings.choosePreferredInstallation')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Available Installations */}
|
||||
<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}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Claude installation">
|
||||
|
@@ -59,12 +59,12 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name.trim()) {
|
||||
setError("Agent name is required");
|
||||
setError(t('agents.agentNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!systemPrompt.trim()) {
|
||||
setError("System prompt is required");
|
||||
setError(t('agents.systemPromptRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,24 +181,24 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
{/* Basic Information */}
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
|
||||
{/* Name and Icon */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Agent Name</Label>
|
||||
<Label htmlFor="name">{t('agents.agentName')}</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g., Code Assistant"
|
||||
placeholder={t('placeholders.enterAgentName')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Agent Icon</Label>
|
||||
<Label>{t('agents.agentIcon')}</Label>
|
||||
<div
|
||||
onClick={() => setShowIconPicker(true)}
|
||||
className="h-10 px-3 py-2 bg-background border border-input rounded-md cursor-pointer hover:bg-accent hover:text-accent-foreground transition-colors flex items-center justify-between"
|
||||
@@ -221,7 +221,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label>Model</Label>
|
||||
<Label>{t('agents.model')}</Label>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -245,7 +245,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Sonnet</div>
|
||||
<div className="text-xs opacity-80">Faster, efficient for most tasks</div>
|
||||
<div className="text-xs opacity-80">{t('agents.sonnetDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -272,7 +272,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-semibold">Claude 4 Opus</div>
|
||||
<div className="text-xs opacity-80">More capable, better for complex tasks</div>
|
||||
<div className="text-xs opacity-80">{t('agents.opusDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -281,25 +281,25 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
|
||||
{/* Default Task */}
|
||||
<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
|
||||
id="default-task"
|
||||
type="text"
|
||||
placeholder="e.g., Review this code for security issues"
|
||||
placeholder={t('placeholders.enterDefaultTask')}
|
||||
value={defaultTask}
|
||||
onChange={(e) => setDefaultTask(e.target.value)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This will be used as the default task placeholder when executing the agent
|
||||
{t('agents.defaultTaskDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* System Prompt Editor */}
|
||||
<div className="space-y-2">
|
||||
<Label>System Prompt</Label>
|
||||
<Label>{t('agents.systemPrompt')}</Label>
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
Define the behavior and capabilities of your CC Agent
|
||||
{t('agents.systemPromptDescription')}
|
||||
</p>
|
||||
<div className="rounded-lg border border-border overflow-hidden shadow-sm" data-color-mode="dark">
|
||||
<MDEditor
|
||||
|
@@ -20,6 +20,7 @@ import { SlashCommandPicker } from "./SlashCommandPicker";
|
||||
import { ImagePreview } from "./ImagePreview";
|
||||
import { type FileEntry, type SlashCommand } from "@/lib/api";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface FloatingPromptInputProps {
|
||||
/**
|
||||
@@ -72,42 +73,7 @@ type ThinkingModeConfig = {
|
||||
phrase?: string; // The phrase to append
|
||||
};
|
||||
|
||||
const THINKING_MODES: ThinkingModeConfig[] = [
|
||||
{
|
||||
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"
|
||||
}
|
||||
];
|
||||
// Thinking modes configuration will be defined inside the component to use translations
|
||||
|
||||
/**
|
||||
* ThinkingModeIndicator component - Shows visual indicator bars for thinking level
|
||||
@@ -173,6 +139,45 @@ const FloatingPromptInputInner = (
|
||||
}: FloatingPromptInputProps,
|
||||
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 [selectedModel, setSelectedModel] = useState<"sonnet" | "opus">(defaultModel);
|
||||
const [selectedThinkingMode, setSelectedThinkingMode] = useState<ThinkingMode>("auto");
|
||||
@@ -758,7 +763,7 @@ const FloatingPromptInputInner = (
|
||||
value={prompt}
|
||||
onChange={handleTextChange}
|
||||
onPaste={handlePaste}
|
||||
placeholder="Type your prompt here..."
|
||||
placeholder={t('messages.typeYourPromptHere')}
|
||||
className="min-h-[200px] resize-none"
|
||||
disabled={disabled}
|
||||
onDragEnter={handleDrag}
|
||||
@@ -1001,7 +1006,7 @@ const FloatingPromptInputInner = (
|
||||
onChange={handleTextChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
placeholder={dragActive ? "Drop images here..." : "Ask Claude anything..."}
|
||||
placeholder={dragActive ? t('messages.dropImagesHere') : t('messages.askClaudeAnything')}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"min-h-[44px] max-h-[120px] resize-none pr-10",
|
||||
@@ -1065,7 +1070,7 @@ const FloatingPromptInputInner = (
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
@@ -51,6 +51,7 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { HooksManager } from '@/lib/hooksManager';
|
||||
import { api } from '@/lib/api';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import {
|
||||
HooksConfiguration,
|
||||
HookEvent,
|
||||
@@ -116,6 +117,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
onChange,
|
||||
hideActions = false
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedEvent, setSelectedEvent] = useState<HookEvent>('PreToolUse');
|
||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||
@@ -754,7 +756,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
{/* Header */}
|
||||
<div className="space-y-2">
|
||||
<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">
|
||||
<Badge variant={scope === 'project' ? 'secondary' : scope === 'local' ? 'outline' : 'default'}>
|
||||
{scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope
|
||||
@@ -789,12 +791,12 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure shell commands to execute at various points in Claude Code's lifecycle.
|
||||
{scope === 'local' && ' These settings are not committed to version control.'}
|
||||
{t('hooks.configureShellCommands')}
|
||||
{scope === 'local' && t('hooks.localSettingsNote')}
|
||||
</p>
|
||||
{hasUnsavedChanges && !readOnly && (
|
||||
<p className="text-sm text-amber-600">
|
||||
You have unsaved changes. Click Save to persist them.
|
||||
{t('hooks.unsavedChanges')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -8,6 +8,7 @@ import { SelectComponent } from "@/components/ui/select";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { api } from "@/lib/api";
|
||||
import { useTrackEvent } from "@/hooks";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface MCPAddServerProps {
|
||||
/**
|
||||
@@ -34,6 +35,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onServerAdded,
|
||||
onError,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [transport, setTransport] = useState<"stdio" | "sse">("stdio");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -101,12 +103,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
*/
|
||||
const handleAddStdioServer = async () => {
|
||||
if (!stdioName.trim()) {
|
||||
onError("Server name is required");
|
||||
onError(t('serverNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stdioCommand.trim()) {
|
||||
onError("Command is required");
|
||||
onError(t('commandRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,7 +154,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError("Failed to add server");
|
||||
onError(t('failedToAddServer'));
|
||||
console.error("Failed to add stdio server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -164,12 +166,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
*/
|
||||
const handleAddSseServer = async () => {
|
||||
if (!sseName.trim()) {
|
||||
onError("Server name is required");
|
||||
onError(t('serverNameRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sseUrl.trim()) {
|
||||
onError("URL is required");
|
||||
onError(t('urlRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -211,7 +213,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError("Failed to add server");
|
||||
onError(t('failedToAddServer'));
|
||||
console.error("Failed to add SSE server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -225,7 +227,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium">Environment Variables</Label>
|
||||
<Label className="text-sm font-medium">{t('environmentVariables')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -233,7 +235,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Variable
|
||||
{t('addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -273,9 +275,9 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<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">
|
||||
Configure a new Model Context Protocol server
|
||||
{t('configureNewMcpServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -296,7 +298,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-name">Server Name</Label>
|
||||
<Label htmlFor="stdio-name">{t('serverName')}</Label>
|
||||
<Input
|
||||
id="stdio-name"
|
||||
placeholder="my-server"
|
||||
@@ -304,12 +306,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setStdioName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
A unique name to identify this server
|
||||
{t('uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-command">Command</Label>
|
||||
<Label htmlFor="stdio-command">{t('command')}</Label>
|
||||
<Input
|
||||
id="stdio-command"
|
||||
placeholder="/path/to/server"
|
||||
@@ -318,12 +320,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The command to execute the server
|
||||
{t('commandToExecuteServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-args">Arguments (optional)</Label>
|
||||
<Label htmlFor="stdio-args">{t('argumentsOptional')}</Label>
|
||||
<Input
|
||||
id="stdio-args"
|
||||
placeholder="arg1 arg2 arg3"
|
||||
@@ -332,19 +334,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Space-separated command arguments
|
||||
{t('spaceSeparatedArgs')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-scope">Scope</Label>
|
||||
<Label htmlFor="stdio-scope">{t('scope')}</Label>
|
||||
<SelectComponent
|
||||
value={stdioScope}
|
||||
onValueChange={(value: string) => setStdioScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: "Local (this project only)" },
|
||||
{ value: "project", label: "Project (shared via .mcp.json)" },
|
||||
{ value: "user", label: "User (all projects)" },
|
||||
{ value: "local", label: t('localProjectOnly') },
|
||||
{ value: "project", label: t('projectSharedViaMcp') },
|
||||
{ value: "user", label: t('userAllProjects') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -361,12 +363,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Adding Server...
|
||||
{t('addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Stdio Server
|
||||
{t('addStdioServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -379,7 +381,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<Card className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-name">Server Name</Label>
|
||||
<Label htmlFor="sse-name">{t('serverName')}</Label>
|
||||
<Input
|
||||
id="sse-name"
|
||||
placeholder="sse-server"
|
||||
@@ -387,12 +389,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setSseName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
A unique name to identify this server
|
||||
{t('uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-url">URL</Label>
|
||||
<Label htmlFor="sse-url">{t('url')}</Label>
|
||||
<Input
|
||||
id="sse-url"
|
||||
placeholder="https://example.com/sse-endpoint"
|
||||
@@ -401,19 +403,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The SSE endpoint URL
|
||||
{t('sseEndpointUrl')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-scope">Scope</Label>
|
||||
<Label htmlFor="sse-scope">{t('scope')}</Label>
|
||||
<SelectComponent
|
||||
value={sseScope}
|
||||
onValueChange={(value: string) => setSseScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: "Local (this project only)" },
|
||||
{ value: "project", label: "Project (shared via .mcp.json)" },
|
||||
{ value: "user", label: "User (all projects)" },
|
||||
{ value: "local", label: t('localProjectOnly') },
|
||||
{ value: "project", label: t('projectSharedViaMcp') },
|
||||
{ value: "user", label: t('userAllProjects') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -430,12 +432,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Adding Server...
|
||||
{t('addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add SSE Server
|
||||
{t('addSseServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -449,7 +451,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Info className="h-4 w-4 text-primary" />
|
||||
<span>Example Commands</span>
|
||||
<span>{t('exampleCommands')}</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs text-muted-foreground">
|
||||
<div className="font-mono bg-background p-2 rounded">
|
||||
|
@@ -9,6 +9,7 @@ import { api, type MCPServer } from "@/lib/api";
|
||||
import { MCPServerList } from "./MCPServerList";
|
||||
import { MCPAddServer } from "./MCPAddServer";
|
||||
import { MCPImportExport } from "./MCPImportExport";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface MCPManagerProps {
|
||||
/**
|
||||
@@ -29,6 +30,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState("servers");
|
||||
const [servers, setServers] = useState<MCPServer[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -55,7 +57,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
setServers(serverList);
|
||||
} catch (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 {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -66,7 +68,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
*/
|
||||
const handleServerAdded = () => {
|
||||
loadServers();
|
||||
setToast({ message: "MCP server added successfully!", type: "success" });
|
||||
setToast({ message: t('mcpServerAdded'), type: "success" });
|
||||
setActiveTab("servers");
|
||||
};
|
||||
|
||||
@@ -75,7 +77,7 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
*/
|
||||
const handleServerRemoved = (name: string) => {
|
||||
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();
|
||||
if (failed === 0) {
|
||||
setToast({
|
||||
message: `Successfully imported ${imported} server${imported > 1 ? 's' : ''}!`,
|
||||
message: t('importedServersSuccess', { count: imported, plural: imported > 1 ? 's' : '' }),
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
message: `Imported ${imported} server${imported > 1 ? 's' : ''}, ${failed} failed`,
|
||||
message: t('importedServersFailed', { imported, importedPlural: imported > 1 ? 's' : '', failed }),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
@@ -118,10 +120,10 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Network className="h-5 w-5 text-blue-500" />
|
||||
MCP Servers
|
||||
{t('mcpServers')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Manage Model Context Protocol servers
|
||||
{t('manageMcpServers')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,15 +155,15 @@ export const MCPManager: React.FC<MCPManagerProps> = ({
|
||||
<TabsList className="grid w-full max-w-md grid-cols-3">
|
||||
<TabsTrigger value="servers" className="gap-2">
|
||||
<Network className="h-4 w-4 text-blue-500" />
|
||||
Servers
|
||||
{t('servers')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="add" className="gap-2">
|
||||
<Plus className="h-4 w-4 text-green-500" />
|
||||
Add Server
|
||||
{t('addServer')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="import" className="gap-2">
|
||||
<Download className="h-4 w-4 text-purple-500" />
|
||||
Import/Export
|
||||
{t('importExport')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { api, type MCPServer } from "@/lib/api";
|
||||
import { useTrackEvent } from "@/hooks";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface MCPServerListProps {
|
||||
/**
|
||||
@@ -50,6 +51,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
onServerRemoved,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [removingServer, setRemovingServer] = useState<string | null>(null);
|
||||
const [testingServer, setTestingServer] = useState<string | null>(null);
|
||||
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
||||
@@ -184,11 +186,11 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
const getScopeDisplayName = (scope: string) => {
|
||||
switch (scope) {
|
||||
case "local":
|
||||
return "Local (Project-specific)";
|
||||
return t('localProjectSpecific');
|
||||
case "project":
|
||||
return "Project (Shared via .mcp.json)";
|
||||
return t('projectSharedMcp');
|
||||
case "user":
|
||||
return "User (All projects)";
|
||||
return t('userAllProjects');
|
||||
default:
|
||||
return scope;
|
||||
}
|
||||
@@ -220,7 +222,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{server.status?.running && (
|
||||
<Badge variant="outline" className="gap-1 flex-shrink-0 border-green-500/50 text-green-600 bg-green-500/10">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
Running
|
||||
{t('running')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -237,7 +239,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronDown className="h-3 w-3 mr-1" />
|
||||
Show full
|
||||
{t('showFull')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -299,7 +301,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{server.command && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-medium text-muted-foreground">Command</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('command')}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -308,7 +310,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<Copy className="h-3 w-3 mr-1" />
|
||||
{isCopied ? "Copied!" : "Copy"}
|
||||
{isCopied ? t('copied') : t('copy')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -317,7 +319,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
className="h-6 px-2 text-xs hover:bg-primary/10"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3 mr-1" />
|
||||
Hide
|
||||
{t('hide')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +331,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">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">
|
||||
{server.args.map((arg, idx) => (
|
||||
<div key={idx} className="break-all">
|
||||
@@ -343,7 +345,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{server.transport === "sse" && server.url && (
|
||||
<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">
|
||||
{server.url}
|
||||
</p>
|
||||
@@ -352,7 +354,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{Object.keys(server.env).length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">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">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="break-all">
|
||||
@@ -384,9 +386,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<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">
|
||||
{servers.length} server{servers.length !== 1 ? "s" : ""} configured
|
||||
{servers.length} {servers.length !== 1 ? t('servers') : 'server'} {t('serversConfigured')}
|
||||
</p>
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
Refresh
|
||||
{t('app.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -406,9 +408,9 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
<div className="p-4 bg-primary/10 rounded-full mb-4">
|
||||
<Network className="h-12 w-12 text-primary" />
|
||||
</div>
|
||||
<p className="text-muted-foreground mb-2 font-medium">No MCP servers configured</p>
|
||||
<p className="text-muted-foreground mb-2 font-medium">{t('noMcpServersConfigured')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Add a server to get started with Model Context Protocol
|
||||
{t('addServerToGetStarted')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||
import { api } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
/**
|
||||
@@ -28,6 +29,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [originalContent, setOriginalContent] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -51,7 +53,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
setOriginalContent(prompt);
|
||||
} catch (err) {
|
||||
console.error("Failed to load system prompt:", err);
|
||||
setError("Failed to load CLAUDE.md file");
|
||||
setError(t('loadClaudemdFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -64,11 +66,11 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
setToast(null);
|
||||
await api.saveSystemPrompt(content);
|
||||
setOriginalContent(content);
|
||||
setToast({ message: "CLAUDE.md saved successfully", type: "success" });
|
||||
setToast({ message: t('claudemdSavedSuccess'), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save system prompt:", err);
|
||||
setError("Failed to save CLAUDE.md file");
|
||||
setToast({ message: "Failed to save CLAUDE.md", type: "error" });
|
||||
setError(t('saveClaudemdFailed'));
|
||||
setToast({ message: t('saveClaudemdFailed'), type: "error" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -77,7 +79,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
const handleBack = () => {
|
||||
if (hasChanges) {
|
||||
const confirmLeave = window.confirm(
|
||||
"You have unsaved changes. Are you sure you want to leave?"
|
||||
t('unsavedChangesConfirm')
|
||||
);
|
||||
if (!confirmLeave) return;
|
||||
}
|
||||
@@ -106,7 +108,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">CLAUDE.md</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Edit your Claude Code system prompt
|
||||
{t('editSystemPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,7 +123,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "Saving..." : "Save"}
|
||||
{saving ? t('app.saving') : t('app.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
import type { Project } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { formatTimeAgo } from "@/lib/date-utils";
|
||||
@@ -70,6 +71,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
||||
onProjectSettings,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
// Calculate pagination
|
||||
@@ -150,7 +152,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
||||
}}
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Hooks
|
||||
{t('settings.hooks')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
export interface ProxySettings {
|
||||
http_proxy: string | null;
|
||||
@@ -18,6 +19,7 @@ interface ProxySettingsProps {
|
||||
}
|
||||
|
||||
export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [settings, setSettings] = useState<ProxySettings>({
|
||||
http_proxy: null,
|
||||
https_proxy: null,
|
||||
@@ -43,13 +45,13 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
await invoke('save_proxy_settings', { settings });
|
||||
setOriginalSettings(settings);
|
||||
setToast({
|
||||
message: 'Proxy settings saved and applied successfully.',
|
||||
message: t('settings.proxySettingsSaved'),
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save proxy settings:', error);
|
||||
setToast({
|
||||
message: 'Failed to save proxy settings',
|
||||
message: t('settings.saveProxySettingsFailed'),
|
||||
type: 'error',
|
||||
});
|
||||
throw error; // Re-throw to let parent handle the error
|
||||
@@ -72,7 +74,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
} catch (error) {
|
||||
console.error('Failed to load proxy settings:', error);
|
||||
setToast({
|
||||
message: 'Failed to load proxy settings',
|
||||
message: t('settings.loadProxySettingsFailed'),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
@@ -89,18 +91,18 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<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">
|
||||
Configure proxy settings for Claude API requests
|
||||
{t('settings.proxySettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<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">
|
||||
Use proxy for all Claude API requests
|
||||
{t('settings.enableProxyDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<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-2">
|
||||
<Label htmlFor="http-proxy">HTTP Proxy</Label>
|
||||
<Label htmlFor="http-proxy">{t('settings.httpProxy')}</Label>
|
||||
<Input
|
||||
id="http-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
@@ -123,7 +125,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="https-proxy">HTTPS Proxy</Label>
|
||||
<Label htmlFor="https-proxy">{t('settings.httpsProxy')}</Label>
|
||||
<Input
|
||||
id="https-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
@@ -134,7 +136,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="no-proxy">No Proxy</Label>
|
||||
<Label htmlFor="no-proxy">{t('settings.noProxy')}</Label>
|
||||
<Input
|
||||
id="no-proxy"
|
||||
placeholder="localhost,127.0.0.1,.example.com"
|
||||
@@ -143,12 +145,12 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Comma-separated list of hosts that should bypass the proxy
|
||||
{t('settings.noProxyDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="all-proxy">All Proxy (Optional)</Label>
|
||||
<Label htmlFor="all-proxy">All Proxy ({t('agents.optional')})</Label>
|
||||
<Input
|
||||
id="all-proxy"
|
||||
placeholder="socks5://proxy.example.com:1080"
|
||||
@@ -157,7 +159,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -31,7 +31,7 @@ import { HooksEditor } from "./HooksEditor";
|
||||
import { SlashCommandsManager } from "./SlashCommandsManager";
|
||||
import { ProxySettings } from "./ProxySettings";
|
||||
import { AnalyticsConsent } from "./AnalyticsConsent";
|
||||
import { useTheme, useTrackEvent } from "@/hooks";
|
||||
import { useTheme, useTrackEvent, useTranslation } from "@/hooks";
|
||||
import { analytics } from "@/lib/analytics";
|
||||
|
||||
interface SettingsProps {
|
||||
@@ -64,6 +64,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
onBack,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [settings, setSettings] = useState<ClaudeSettings | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -178,7 +179,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load settings:", err);
|
||||
setError("Failed to load settings. Please ensure ~/.claude directory exists.");
|
||||
setError(t('settings.messages.loadFailed'));
|
||||
setSettings({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -232,11 +233,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
setProxySettingsChanged(false);
|
||||
}
|
||||
|
||||
setToast({ message: "Settings saved successfully!", type: "success" });
|
||||
setToast({ message: t('settings.saveButton.settingsSavedSuccess'), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save settings:", err);
|
||||
setError("Failed to save settings.");
|
||||
setToast({ message: "Failed to save settings", type: "error" });
|
||||
setError(t('settings.messages.saveFailed'));
|
||||
setToast({ message: t('settings.saveButton.settingsSaveFailed'), type: "error" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -347,9 +348,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<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">
|
||||
Configure Claude Code preferences
|
||||
{t('settings.configurePreferences')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,12 +364,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Saving...
|
||||
{t('settings.saveButton.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Save Settings
|
||||
{t('settings.saveButton.saveSettings')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -398,55 +399,55 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-9 w-full">
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="permissions">Permissions</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
<TabsTrigger value="hooks">Hooks</TabsTrigger>
|
||||
<TabsTrigger value="commands">Commands</TabsTrigger>
|
||||
<TabsTrigger value="storage">Storage</TabsTrigger>
|
||||
<TabsTrigger value="proxy">Proxy</TabsTrigger>
|
||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||
<TabsTrigger value="general">{t('settings.general')}</TabsTrigger>
|
||||
<TabsTrigger value="permissions">{t('settings.permissionsTab')}</TabsTrigger>
|
||||
<TabsTrigger value="environment">{t('settings.environmentTab')}</TabsTrigger>
|
||||
<TabsTrigger value="advanced">{t('settings.advancedTab')}</TabsTrigger>
|
||||
<TabsTrigger value="hooks">{t('settings.hooksTab')}</TabsTrigger>
|
||||
<TabsTrigger value="commands">{t('settings.commands')}</TabsTrigger>
|
||||
<TabsTrigger value="storage">{t('settings.storage')}</TabsTrigger>
|
||||
<TabsTrigger value="proxy">{t('settings.proxy')}</TabsTrigger>
|
||||
<TabsTrigger value="analytics">{t('settings.analyticsTab')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* General Settings */}
|
||||
<TabsContent value="general" className="space-y-6">
|
||||
<Card className="p-6 space-y-6">
|
||||
<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">
|
||||
{/* Theme Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="theme">Theme</Label>
|
||||
<Label htmlFor="theme">{t('settings.theme')}</Label>
|
||||
<Select
|
||||
value={theme}
|
||||
onValueChange={(value) => setTheme(value as any)}
|
||||
>
|
||||
<SelectTrigger id="theme" className="w-full">
|
||||
<SelectValue placeholder="Select a theme" />
|
||||
<SelectValue placeholder={t('settings.themeSelector.selectATheme')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="gray">Gray</SelectItem>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
<SelectItem value="dark">{t('settings.themeSelector.dark')}</SelectItem>
|
||||
<SelectItem value="gray">{t('settings.themeSelector.gray')}</SelectItem>
|
||||
<SelectItem value="light">{t('settings.themeSelector.light')}</SelectItem>
|
||||
<SelectItem value="custom">{t('settings.themeSelector.custom')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose your preferred color theme for the interface
|
||||
{t('settings.themeSelector.choosePreferredTheme')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Custom Color Editor */}
|
||||
{theme === 'custom' && (
|
||||
<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">
|
||||
{/* Background Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-background"
|
||||
@@ -465,7 +466,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Foreground Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-foreground"
|
||||
@@ -484,7 +485,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Primary Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-primary"
|
||||
@@ -503,7 +504,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Card Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-card"
|
||||
@@ -522,7 +523,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Accent Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-accent"
|
||||
@@ -541,7 +542,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Destructive Color */}
|
||||
<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">
|
||||
<Input
|
||||
id="color-destructive"
|
||||
@@ -560,7 +561,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately.
|
||||
{t('settings.customTheme.colorValuesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -568,9 +569,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Include Co-authored By */}
|
||||
<div className="flex items-center justify-between">
|
||||
<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">
|
||||
Add Claude attribution to git commits and pull requests
|
||||
{t('settings.generalOptions.includeCoAuthorDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -583,9 +584,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Verbose Output */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5 flex-1">
|
||||
<Label htmlFor="verbose">Verbose Output</Label>
|
||||
<Label htmlFor="verbose">{t('settings.generalOptions.verboseOutput')}</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Show full bash and command outputs
|
||||
{t('settings.generalOptions.verboseOutputDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -597,7 +598,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
{/* Cleanup Period */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cleanup">Chat Transcript Retention (days)</Label>
|
||||
<Label htmlFor="cleanup">{t('settings.generalOptions.chatRetention')}</Label>
|
||||
<Input
|
||||
id="cleanup"
|
||||
type="number"
|
||||
@@ -610,16 +611,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
How long to retain chat transcripts locally (default: 30 days)
|
||||
{t('settings.generalOptions.chatRetentionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Claude Binary Path Selector */}
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
Select which Claude Code installation to use.
|
||||
{t('settings.generalOptions.claudeCodeInstallationDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<ClaudeVersionSelector
|
||||
@@ -628,7 +629,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
/>
|
||||
{binaryPathChanged && (
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
@@ -642,16 +643,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold mb-2">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">
|
||||
Control which tools Claude Code can use without manual approval
|
||||
{t('settings.permissions.permissionRulesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Allow Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-green-500">Allow Rules</Label>
|
||||
<Label className="text-sm font-medium text-green-500">{t('settings.permissions.allowRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -659,13 +660,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-green-500/50 hover:text-green-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Rule
|
||||
{t('settings.permissions.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{allowRules.length === 0 ? (
|
||||
<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>
|
||||
) : (
|
||||
allowRules.map((rule) => (
|
||||
@@ -676,7 +677,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g., Bash(npm run test:*)"
|
||||
placeholder={t('settings.placeholders.allowRuleExample')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("allow", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -698,7 +699,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{/* Deny Rules */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-red-500">Deny Rules</Label>
|
||||
<Label className="text-sm font-medium text-red-500">{t('settings.permissions.denyRules')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -706,13 +707,13 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2 hover:border-red-500/50 hover:text-red-500"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Rule
|
||||
{t('settings.permissions.addRule')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{denyRules.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
No deny rules configured.
|
||||
{t('settings.permissions.noDenyRules')}
|
||||
</p>
|
||||
) : (
|
||||
denyRules.map((rule) => (
|
||||
@@ -723,7 +724,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g., Bash(curl:*)"
|
||||
placeholder={t('settings.placeholders.denyRuleExample')}
|
||||
value={rule.value}
|
||||
onChange={(e) => updatePermissionRule("deny", rule.id, e.target.value)}
|
||||
className="flex-1"
|
||||
@@ -744,14 +745,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>Examples:</strong>
|
||||
<strong>{t('settings.permissions.examples')}</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-green-500/10 text-green-600 dark:text-green-400">Bash</code> - 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(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 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">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">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">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> - {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> - {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> - {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> - {t('settings.permissions.exampleEditDir')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -764,9 +765,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">Environment Variables</h3>
|
||||
<h3 className="text-base font-semibold">{t('settings.environment.environmentVariables')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Environment variables applied to every Claude Code session
|
||||
{t('settings.environment.environmentVariablesDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -776,14 +777,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add Variable
|
||||
{t('settings.environment.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{envVars.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">
|
||||
No environment variables configured.
|
||||
{t('settings.environment.noEnvironmentVariables')}
|
||||
</p>
|
||||
) : (
|
||||
envVars.map((envVar) => (
|
||||
@@ -794,14 +795,14 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Input
|
||||
placeholder="KEY"
|
||||
placeholder={t('settings.placeholders.envVarKey')}
|
||||
value={envVar.key}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "key", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<Input
|
||||
placeholder="value"
|
||||
placeholder={t('settings.placeholders.envVarValue')}
|
||||
value={envVar.value}
|
||||
onChange={(e) => updateEnvVar(envVar.id, "value", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
@@ -821,12 +822,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
|
||||
<div className="pt-2 space-y-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<strong>Common variables:</strong>
|
||||
<strong>{t('settings.environment.commonVariables')}</strong>
|
||||
</p>
|
||||
<ul className="text-xs text-muted-foreground space-y-1 ml-4">
|
||||
<li>• <code className="px-1 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">CLAUDE_CODE_ENABLE_TELEMETRY</code> - 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">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">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">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> - {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> - {t('settings.environment.costWarningsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -837,34 +838,34 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<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">
|
||||
Additional configuration options for advanced users
|
||||
{t('settings.advanced.advancedSettingsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Key Helper */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKeyHelper">API Key Helper Script</Label>
|
||||
<Label htmlFor="apiKeyHelper">{t('settings.advanced.apiKeyHelper')}</Label>
|
||||
<Input
|
||||
id="apiKeyHelper"
|
||||
placeholder="/path/to/generate_api_key.sh"
|
||||
placeholder={t('settings.placeholders.apiKeyHelperPath')}
|
||||
value={settings?.apiKeyHelper || ""}
|
||||
onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Custom script to generate auth values for API requests
|
||||
{t('settings.advanced.apiKeyHelperDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Raw JSON Editor */}
|
||||
<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">
|
||||
<pre>{JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -876,10 +877,9 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
Configure hooks that apply to all Claude Code sessions for your user account.
|
||||
These are stored in <code className="mx-1 px-2 py-1 bg-muted rounded text-xs">~/.claude/settings.json</code>
|
||||
{t('settings.hooks.userHooksDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -928,16 +928,16 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<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 className="space-y-6">
|
||||
{/* Analytics Toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<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">
|
||||
Help improve Claudia by sharing anonymous usage data
|
||||
{t('settings.analytics.enableAnalyticsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -950,12 +950,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
await analytics.enable();
|
||||
setAnalyticsEnabled(true);
|
||||
trackEvent.settingsChanged('analytics_enabled', true);
|
||||
setToast({ message: "Analytics enabled", type: "success" });
|
||||
setToast({ message: t('settings.analytics.analyticsEnabled'), type: "success" });
|
||||
} else {
|
||||
await analytics.disable();
|
||||
setAnalyticsEnabled(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">
|
||||
<Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<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">
|
||||
<li>• No personal information is collected</li>
|
||||
<li>• No file contents, paths, or project names</li>
|
||||
<li>• All data is anonymous with random IDs</li>
|
||||
<li>• You can disable analytics at any time</li>
|
||||
<li>• {t('settings.analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('settings.analytics.noFileContents')}</li>
|
||||
<li>• {t('settings.analytics.anonymousData')}</li>
|
||||
<li>• {t('settings.analytics.canDisable')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -981,12 +981,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
{analyticsEnabled && (
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
<li>• Feature usage patterns</li>
|
||||
<li>• Performance metrics</li>
|
||||
<li>• Error reports (without sensitive data)</li>
|
||||
<li>• Session frequency and duration</li>
|
||||
<li>• {t('settings.analytics.featureUsage')}</li>
|
||||
<li>• {t('settings.analytics.performanceMetrics')}</li>
|
||||
<li>• {t('settings.analytics.errorReports')}</li>
|
||||
<li>• {t('settings.analytics.sessionFrequency')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -999,11 +999,11 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
await analytics.deleteAllData();
|
||||
setAnalyticsEnabled(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" />
|
||||
Delete All Analytics Data
|
||||
{t('settings.analytics.deleteAllData')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -30,6 +30,7 @@ import { api, type SlashCommand } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
||||
import { useTrackEvent } from "@/hooks";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface SlashCommandsManagerProps {
|
||||
projectPath?: string;
|
||||
@@ -92,6 +93,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
className,
|
||||
scopeFilter = 'all',
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -305,17 +307,17 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">
|
||||
{scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'}
|
||||
{scopeFilter === 'project' ? t('slashCommands.projectSlashCommands') : t('slashCommands.slashCommands')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{scopeFilter === 'project'
|
||||
? 'Create custom commands for this project'
|
||||
: 'Create custom commands to streamline your workflow'}
|
||||
? t('slashCommands.createCustomCommandsProject')
|
||||
: t('slashCommands.createCustomCommandsWorkflow')}
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
New Command
|
||||
{t('slashCommands.newCommand')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -325,7 +327,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search commands..."
|
||||
placeholder={t('placeholders.searchCommands')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
@@ -338,9 +340,9 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Commands</SelectItem>
|
||||
<SelectItem value="project">Project</SelectItem>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="all">{t('slashCommands.allCommands')}</SelectItem>
|
||||
<SelectItem value="project">{t('slashCommands.project')}</SelectItem>
|
||||
<SelectItem value="user">{t('slashCommands.user')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
@@ -365,16 +367,16 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{searchQuery
|
||||
? "No commands found"
|
||||
? t('slashCommands.noCommandsFound')
|
||||
: scopeFilter === 'project'
|
||||
? "No project commands created yet"
|
||||
: "No commands created yet"}
|
||||
? t('slashCommands.noProjectCommandsYet')
|
||||
: t('slashCommands.noCommandsYet')}
|
||||
</p>
|
||||
{!searchQuery && (
|
||||
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
||||
{scopeFilter === 'project'
|
||||
? "Create your first project command"
|
||||
: "Create your first command"}
|
||||
? t('slashCommands.createFirstProjectCommand')
|
||||
: t('slashCommands.createFirstCommand')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -9,6 +9,7 @@ import { ProjectList } from '@/components/ProjectList';
|
||||
import { SessionList } from '@/components/SessionList';
|
||||
import { RunningClaudeSessions } from '@/components/RunningClaudeSessions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
// Lazy load heavy components
|
||||
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 { t } = useTranslation();
|
||||
const { updateTab, createChatTab } = useTabState();
|
||||
const [projects, setProjects] = React.useState<Project[]>([]);
|
||||
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
||||
@@ -54,7 +56,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
setProjects(projectList);
|
||||
} catch (err) {
|
||||
console.error("Failed to load projects:", err);
|
||||
setError("Failed to load projects. Please ensure ~/.claude directory exists.");
|
||||
setError(t('failedToLoadProjects'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -69,7 +71,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
setSelectedProject(project);
|
||||
} catch (err) {
|
||||
console.error("Failed to load sessions:", err);
|
||||
setError("Failed to load sessions for this project.");
|
||||
setError(t('failedToLoadSessions'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -96,9 +98,9 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
<div className="container mx-auto p-6">
|
||||
{/* Header */}
|
||||
<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">
|
||||
Browse your Claude Code sessions
|
||||
{t('browseClaudeCodeSessions')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -174,7 +176,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
className="w-full max-w-md"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Claude Code session
|
||||
{t('newClaudeCodeSession')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
@@ -196,7 +198,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
) : (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No projects found in ~/.claude/projects
|
||||
{t('noProjectsFound')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -217,7 +219,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
// Go back to projects view in the same tab
|
||||
updateTab(tab.id, {
|
||||
type: 'projects',
|
||||
title: 'CC Projects',
|
||||
title: t('ccProjects'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -225,7 +227,7 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'agent':
|
||||
if (!tab.agentRunId) {
|
||||
return <div className="p-4">No agent run ID specified</div>;
|
||||
return <div className="p-4">{t('messages.noAgentRunIdSpecified')}</div>;
|
||||
}
|
||||
return (
|
||||
<AgentRunOutputViewer
|
||||
@@ -249,15 +251,15 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'claude-file':
|
||||
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
|
||||
// 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':
|
||||
if (!tab.agentData) {
|
||||
return <div className="p-4">No agent data specified</div>;
|
||||
return <div className="p-4">{t('messages.noAgentDataSpecified')}</div>;
|
||||
}
|
||||
return (
|
||||
<AgentExecution
|
||||
@@ -282,10 +284,10 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
||||
|
||||
case 'import-agent':
|
||||
// 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:
|
||||
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 = () => {
|
||||
const { t } = useTranslation();
|
||||
const { tabs, activeTabId, createChatTab, findTabBySessionId, createClaudeFileTab, createAgentExecutionTab, createCreateAgentTab, createImportAgentTab, closeTab, updateTab } = useTabState();
|
||||
|
||||
// Listen for events to open sessions in tabs
|
||||
@@ -415,8 +418,8 @@ export const TabContent: React.FC = () => {
|
||||
{tabs.length === 0 && (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
<div className="text-center">
|
||||
<p className="text-lg mb-2">No tabs open</p>
|
||||
<p className="text-sm">Click the + button to start a new chat</p>
|
||||
<p className="text-lg mb-2">{t('messages.noTabsOpen')}</p>
|
||||
<p className="text-sm">{t('messages.clickPlusToStartChat')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
Briefcase
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface UsageDashboardProps {
|
||||
/**
|
||||
@@ -32,6 +33,7 @@ interface UsageDashboardProps {
|
||||
* <UsageDashboard onBack={() => setView('welcome')} />
|
||||
*/
|
||||
export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [stats, setStats] = useState<UsageStats | null>(null);
|
||||
@@ -82,7 +84,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
setSessionStats(sessionData);
|
||||
} catch (err) {
|
||||
console.error("Failed to load usage stats:", err);
|
||||
setError("Failed to load usage statistics. Please try again.");
|
||||
setError(t('usage.failedToLoadUsageStats'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -146,9 +148,9 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold">Usage Dashboard</h1>
|
||||
<h1 className="text-lg font-semibold">{t('usage.usageDashboardTitle')}</h1>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Track your Claude Code usage and costs
|
||||
{t('usage.trackUsageAndCosts')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +167,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
onClick={() => setSelectedDateRange(range)}
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
@@ -179,7 +181,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-sm text-muted-foreground">Loading usage statistics...</p>
|
||||
<p className="text-sm text-muted-foreground">{t('usage.loadingUsageStats')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
@@ -187,7 +189,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="text-center max-w-md">
|
||||
<p className="text-sm text-destructive mb-4">{error}</p>
|
||||
<Button onClick={loadUsageStats} size="sm">
|
||||
Try Again
|
||||
{t('usage.tryAgain')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,7 +206,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Cost</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalCost')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(stats.total_cost)}
|
||||
</p>
|
||||
@@ -217,7 +219,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Sessions</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalSessions')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatNumber(stats.total_sessions)}
|
||||
</p>
|
||||
@@ -230,7 +232,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Total Tokens</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.totalTokens')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatTokens(stats.total_tokens)}
|
||||
</p>
|
||||
@@ -243,7 +245,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-4 shimmer-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Avg Cost/Session</p>
|
||||
<p className="text-xs text-muted-foreground">{t('usage.avgCostPerSession')}</p>
|
||||
<p className="text-2xl font-bold mt-1">
|
||||
{formatCurrency(
|
||||
stats.total_sessions > 0
|
||||
@@ -260,32 +262,32 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Tabs for different views */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="models">By Model</TabsTrigger>
|
||||
<TabsTrigger value="projects">By Project</TabsTrigger>
|
||||
<TabsTrigger value="sessions">By Session</TabsTrigger>
|
||||
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
||||
<TabsTrigger value="overview">{t('usage.overview')}</TabsTrigger>
|
||||
<TabsTrigger value="models">{t('usage.byModel')}</TabsTrigger>
|
||||
<TabsTrigger value="projects">{t('usage.byProject')}</TabsTrigger>
|
||||
<TabsTrigger value="sessions">{t('usage.byDate')}</TabsTrigger>
|
||||
<TabsTrigger value="timeline">{t('usage.timeline')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Overview Tab */}
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +296,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Most Used Models</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.mostUsedModels')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_model.slice(0, 3).map((model) => (
|
||||
<div key={model.model} className="flex items-center justify-between">
|
||||
@@ -303,7 +305,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{model.session_count} sessions
|
||||
{model.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -315,7 +317,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Top Projects</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.topProjects')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.slice(0, 3).map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between">
|
||||
@@ -324,7 +326,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{project.project_path}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} sessions
|
||||
{project.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
@@ -340,7 +342,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Models Tab */}
|
||||
<TabsContent value="models">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Model</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByModel')}</h3>
|
||||
<div className="space-y-4">
|
||||
{stats.by_model.map((model) => (
|
||||
<div key={model.model} className="space-y-2">
|
||||
@@ -353,7 +355,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{getModelDisplayName(model.model)}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{model.session_count} sessions
|
||||
{model.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-semibold">
|
||||
@@ -362,11 +364,11 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div>
|
||||
@@ -387,7 +389,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Projects Tab */}
|
||||
<TabsContent value="projects">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Project</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageByProject')}</h3>
|
||||
<div className="space-y-3">
|
||||
{stats.by_project.map((project) => (
|
||||
<div key={project.project_path} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -397,17 +399,17 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
</span>
|
||||
<div className="flex items-center space-x-3 mt-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{project.session_count} sessions
|
||||
{project.session_count} {t('usage.sessions')}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatTokens(project.total_tokens)} tokens
|
||||
{formatTokens(project.total_tokens)} {t('usage.tokens')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-semibold">{formatCurrency(project.total_cost)}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatCurrency(project.total_cost / project.session_count)}/session
|
||||
{formatCurrency(project.total_cost / project.session_count)}/{t('usage.session')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,7 +421,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
{/* Sessions Tab */}
|
||||
<TabsContent value="sessions">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-4">Usage by Session</h3>
|
||||
<h3 className="text-sm font-semibold mb-4">{t('usage.usageBySession')}</h3>
|
||||
<div className="space-y-3">
|
||||
{sessionStats?.map((session) => (
|
||||
<div key={`${session.project_path}-${session.project_name}`} className="flex items-center justify-between py-2 border-b border-border last:border-0">
|
||||
@@ -451,7 +453,7 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<Card className="p-6">
|
||||
<h3 className="text-sm font-semibold mb-6 flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>Daily Usage</span>
|
||||
<span>{t('usage.dailyUsage')}</span>
|
||||
</h3>
|
||||
{stats.by_date.length > 0 ? (() => {
|
||||
const maxCost = Math.max(...stats.by_date.map(d => d.total_cost), 0);
|
||||
@@ -484,13 +486,13 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
<div className="bg-background border border-border rounded-lg shadow-lg p-3 whitespace-nowrap">
|
||||
<p className="text-sm font-semibold">{formattedDate}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Cost: {formatCurrency(day.total_cost)}
|
||||
{t('usage.cost')}: {formatCurrency(day.total_cost)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatTokens(day.total_tokens)} tokens
|
||||
{formatTokens(day.total_tokens)} {t('usage.tokens')}
|
||||
</p>
|
||||
<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>
|
||||
</div>
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 -mt-1">
|
||||
@@ -517,13 +519,13 @@ export const UsageDashboard: React.FC<UsageDashboardProps> = ({ onBack }) => {
|
||||
|
||||
{/* X-axis label */}
|
||||
<div className="mt-8 text-center text-xs text-muted-foreground">
|
||||
Daily Usage Over Time
|
||||
{t('usage.dailyUsageOverTime')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})() : (
|
||||
<div className="text-center py-8 text-sm text-muted-foreground">
|
||||
No usage data available for the selected period
|
||||
{t('usage.noUsageData')}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
Reference in New Issue
Block a user