美化
This commit is contained in:
31
index.html
31
index.html
@@ -6,6 +6,37 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Claudia - Claude Code Session Browser</title>
|
||||
<!-- Maple Mono Font (Local) -->
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Maple Mono';
|
||||
src: url('/fonts/MapleMono-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Maple Mono';
|
||||
src: url('/fonts/MapleMono-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Maple Mono';
|
||||
src: url('/fonts/MapleMono-SemiBold.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Maple Mono';
|
||||
src: url('/fonts/MapleMono-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
BIN
public/fonts/MapleMono-Bold.ttf
Normal file
BIN
public/fonts/MapleMono-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-Light.ttf
Normal file
BIN
public/fonts/MapleMono-Light.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-Regular.ttf
Normal file
BIN
public/fonts/MapleMono-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/MapleMono-SemiBold.ttf
Normal file
BIN
public/fonts/MapleMono-SemiBold.ttf
Normal file
Binary file not shown.
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@@ -13,8 +13,8 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "Claudia",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
"width": 1600,
|
||||
"height": 1200
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
132
src/App.tsx
132
src/App.tsx
@@ -1,12 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Plus, Loader2, Bot, FolderCode } from "lucide-react";
|
||||
import { Plus, Loader2, ArrowLeft } from "lucide-react";
|
||||
import { api, type Project, type Session, type ClaudeMdFile } from "@/lib/api";
|
||||
import { OutputCacheProvider } from "@/lib/outputCache";
|
||||
import { TabProvider } from "@/contexts/TabContext";
|
||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ProjectList } from "@/components/ProjectList";
|
||||
import { SessionList } from "@/components/SessionList";
|
||||
import { RunningClaudeSessions } from "@/components/RunningClaudeSessions";
|
||||
@@ -28,6 +27,7 @@ import { useTabState } from "@/hooks/useTabState";
|
||||
import { AnalyticsConsentBanner } from "@/components/AnalyticsConsent";
|
||||
import { useAppLifecycle, useTrackEvent } from "@/hooks";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
import { WelcomePage } from "@/components/WelcomePage";
|
||||
|
||||
type View =
|
||||
| "welcome"
|
||||
@@ -50,7 +50,7 @@ type View =
|
||||
*/
|
||||
function AppContent() {
|
||||
const { t } = useTranslation();
|
||||
const [view, setView] = useState<View>("tabs");
|
||||
const [view, setView] = useState<View>("welcome");
|
||||
const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState();
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
@@ -149,6 +149,18 @@ function AppContent() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Listen for switch to welcome view event
|
||||
useEffect(() => {
|
||||
const handleSwitchToWelcome = () => {
|
||||
setView("welcome");
|
||||
};
|
||||
|
||||
window.addEventListener('switch-to-welcome', handleSwitchToWelcome);
|
||||
return () => {
|
||||
window.removeEventListener('switch-to-welcome', handleSwitchToWelcome);
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Loads all projects from the ~/.claude/projects directory
|
||||
*/
|
||||
@@ -219,9 +231,9 @@ function AppContent() {
|
||||
/**
|
||||
* Handles view changes with navigation protection
|
||||
*/
|
||||
const handleViewChange = (newView: View) => {
|
||||
const handleViewChange = (newView: string) => {
|
||||
// No need for navigation protection with tabs since sessions stay open
|
||||
setView(newView);
|
||||
setView(newView as View);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -237,60 +249,10 @@ function AppContent() {
|
||||
switch (view) {
|
||||
case "welcome":
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4" style={{ height: "100%" }}>
|
||||
<div className="w-full max-w-4xl">
|
||||
{/* Welcome Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="mb-12 text-center"
|
||||
>
|
||||
<h1 className="text-4xl font-bold tracking-tight">
|
||||
<span className="rotating-symbol"></span>
|
||||
{t('welcomeToClaudia')}
|
||||
</h1>
|
||||
</motion.div>
|
||||
|
||||
{/* Navigation Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-2xl mx-auto">
|
||||
{/* CC Agents Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
>
|
||||
<Card
|
||||
className="h-64 cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-lg border border-border/50 shimmer-hover trailing-border"
|
||||
onClick={() => handleViewChange("cc-agents")}
|
||||
>
|
||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||
<Bot className="h-16 w-16 mb-4 text-primary" />
|
||||
<h2 className="text-xl font-semibold">{t('ccAgents')}</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* CC Projects Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card
|
||||
className="h-64 cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-lg border border-border/50 shimmer-hover trailing-border"
|
||||
onClick={() => handleViewChange("projects")}
|
||||
>
|
||||
<div className="h-full flex flex-col items-center justify-center p-8">
|
||||
<FolderCode className="h-16 w-16 mb-4 text-primary" />
|
||||
<h2 className="text-xl font-semibold">{t('ccProjects')}</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<WelcomePage
|
||||
onNavigate={handleViewChange}
|
||||
onNewSession={handleNewSession}
|
||||
/>
|
||||
);
|
||||
|
||||
case "cc-agents":
|
||||
@@ -325,19 +287,20 @@ function AppContent() {
|
||||
transition={{ duration: 0.5 }}
|
||||
className="mb-6"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleViewChange("welcome")}
|
||||
className="mb-4"
|
||||
>
|
||||
{t('backToHome')}
|
||||
</Button>
|
||||
<div className="mb-4">
|
||||
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t('browseClaudeCodeSessions')}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleViewChange("welcome")}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">{t('ccProjects')}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t('browseClaudeCodeSessions')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -375,6 +338,19 @@ function AppContent() {
|
||||
projectPath={selectedProject.path}
|
||||
onBack={handleBack}
|
||||
onEditClaudeFile={handleEditClaudeFile}
|
||||
onSessionClick={(session) => {
|
||||
// Navigate to session detail view in tabs mode
|
||||
setView("tabs");
|
||||
// Create a new tab for this session
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('open-session-tab', {
|
||||
detail: {
|
||||
session,
|
||||
projectPath: selectedProject.path
|
||||
}
|
||||
}));
|
||||
}, 100);
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
@@ -480,19 +456,19 @@ function AppContent() {
|
||||
<div className="h-screen bg-background flex flex-col">
|
||||
{/* Topbar */}
|
||||
<Topbar
|
||||
onClaudeClick={() => createClaudeMdTab()}
|
||||
onSettingsClick={() => createSettingsTab()}
|
||||
onUsageClick={() => createUsageTab()}
|
||||
onMCPClick={() => createMCPTab()}
|
||||
onClaudeClick={() => view === 'tabs' ? createClaudeMdTab() : handleViewChange('editor')}
|
||||
onSettingsClick={() => view === 'tabs' ? createSettingsTab() : handleViewChange('settings')}
|
||||
onUsageClick={() => view === 'tabs' ? createUsageTab() : handleViewChange('usage-dashboard')}
|
||||
onMCPClick={() => view === 'tabs' ? createMCPTab() : handleViewChange('mcp')}
|
||||
onInfoClick={() => setShowNFO(true)}
|
||||
onAgentsClick={() => setShowAgentsModal(true)}
|
||||
onAgentsClick={() => view === 'tabs' ? setShowAgentsModal(true) : handleViewChange('cc-agents')}
|
||||
/>
|
||||
|
||||
{/* Analytics Consent Banner */}
|
||||
<AnalyticsConsentBanner />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex-1 overflow-hidden relative">
|
||||
{renderContent()}
|
||||
</div>
|
||||
|
||||
|
@@ -230,24 +230,24 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
<div className="flex gap-2 mb-4 pt-4">
|
||||
<Button onClick={handleCreateAgent} className="flex-1">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="flex-1">
|
||||
<Import className="w-4 h-4 mr-2" />
|
||||
Import Agent
|
||||
{t('agents.import')}
|
||||
<ChevronDown className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleImportFromFile}>
|
||||
<FileJson className="w-4 h-4 mr-2" />
|
||||
From File
|
||||
{t('agents.importFromFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleImportFromGitHub}>
|
||||
<Globe className="w-4 h-4 mr-2" />
|
||||
From GitHub
|
||||
{t('agents.importFromGitHub')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -268,7 +268,7 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
window.dispatchEvent(new CustomEvent('open-create-agent-tab'));
|
||||
}}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Agent
|
||||
{t('agents.createAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -299,7 +299,7 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
onClick={() => handleExportAgent(agent)}
|
||||
>
|
||||
<Download className="w-3 h-3 mr-1" />
|
||||
Export
|
||||
{t('agents.export')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -308,14 +308,14 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="w-3 h-3 mr-1" />
|
||||
Delete
|
||||
{t('app.delete')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => handleRunAgent(agent)}
|
||||
>
|
||||
<Play className="w-3 h-3 mr-1" />
|
||||
Run
|
||||
{t('agents.runAgent')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -331,9 +331,9 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
{runningAgents.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<Clock className="w-12 h-12 text-muted-foreground mb-4" />
|
||||
<p className="text-lg font-medium mb-2">No running agents</p>
|
||||
<p className="text-lg font-medium mb-2">{t('agents.noRunningAgents')}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Agent executions will appear here when started
|
||||
{t('agents.agentExecutionsWillAppear')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -373,7 +373,7 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
handleOpenAgentRun(run);
|
||||
}}
|
||||
>
|
||||
View
|
||||
{t('agents.view')}
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -392,9 +392,9 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Agent</DialogTitle>
|
||||
<DialogTitle>{t('agents.deleteAgentTitle')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete "{agentToDelete?.name}"? This action cannot be undone.
|
||||
{t('agents.deleteConfirmation', { name: agentToDelete?.name })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex justify-end gap-3 mt-4">
|
||||
@@ -405,13 +405,13 @@ export const AgentsModal: React.FC<AgentsModalProps> = ({ open, onOpenChange })
|
||||
setAgentToDelete(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t('app.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={confirmDelete}
|
||||
>
|
||||
Delete
|
||||
{t('app.delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@@ -72,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">{t('analytics.helpImproveClaudia')}</DialogTitle>
|
||||
<DialogTitle className="text-2xl">{t('settings.analytics.helpImproveClaudia')}</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="text-base mt-2">
|
||||
{t('analytics.collectAnonymousData')}
|
||||
{t('settings.analytics.collectAnonymousData')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</div>
|
||||
@@ -86,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">{t('analytics.whatWeCollect')}</p>
|
||||
<p className="font-medium text-green-900 dark:text-green-100">{t('settings.analytics.whatWeCollect')}</p>
|
||||
<ul className="text-sm text-green-800 dark:text-green-200 space-y-1">
|
||||
<li>• {t('analytics.featureUsageDesc')}</li>
|
||||
<li>• {t('analytics.performanceMetricsDesc')}</li>
|
||||
<li>• {t('analytics.errorReportsDesc')}</li>
|
||||
<li>• {t('analytics.usagePatternsDesc')}</li>
|
||||
<li>• {t('settings.analytics.featureUsageDesc')}</li>
|
||||
<li>• {t('settings.analytics.performanceMetricsDesc')}</li>
|
||||
<li>• {t('settings.analytics.errorReportsDesc')}</li>
|
||||
<li>• {t('settings.analytics.usagePatternsDesc')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,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">{t('analytics.privacyProtected')}</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>• {t('analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('analytics.noFileContents')}</li>
|
||||
<li>• {t('analytics.noApiKeys')}</li>
|
||||
<li>• {t('analytics.anonymousData')}</li>
|
||||
<li>• {t('analytics.canOptOut')}</li>
|
||||
<li>• {t('settings.analytics.noPersonalInfo')}</li>
|
||||
<li>• {t('settings.analytics.noFileContents')}</li>
|
||||
<li>• {t('settings.analytics.noApiKeys')}</li>
|
||||
<li>• {t('settings.analytics.anonymousData')}</li>
|
||||
<li>• {t('settings.analytics.canOptOut')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +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">
|
||||
{t('analytics.dataHelpsUs')}
|
||||
{t('settings.analytics.dataHelpsUs')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,13 +130,13 @@ export const AnalyticsConsent: React.FC<AnalyticsConsentProps> = ({
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
{t('analytics.noThanks')}
|
||||
{t('settings.analytics.noThanks')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAccept}
|
||||
className="flex-1 bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
{t('analytics.allowAnalytics')}
|
||||
{t('settings.analytics.allowAnalytics')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
@@ -151,6 +151,7 @@ interface AnalyticsConsentBannerProps {
|
||||
export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [hasChecked, setHasChecked] = useState(false);
|
||||
|
||||
@@ -199,9 +200,9 @@ export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
||||
<div className="flex items-start gap-3">
|
||||
<BarChart3 className="h-5 w-5 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="space-y-2 flex-1">
|
||||
<p className="text-sm font-medium">Help improve Claudia</p>
|
||||
<p className="text-sm font-medium">{t('settings.analytics.helpImproveClaudia')}</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
We collect anonymous usage data to improve your experience. No personal data is collected.
|
||||
{t('settings.analytics.collectAnonymousData')}
|
||||
</p>
|
||||
<div className="flex gap-2 pt-1">
|
||||
<Button
|
||||
@@ -210,14 +211,14 @@ export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
||||
onClick={handleDecline}
|
||||
className="text-xs"
|
||||
>
|
||||
No Thanks
|
||||
{t('settings.analytics.noThanks')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleAccept}
|
||||
className="text-xs bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
Allow
|
||||
{t('settings.analytics.allowAnalytics')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -134,8 +134,8 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.loadingAvailableInstallations')}</CardDescription>
|
||||
<CardTitle>{t('settings.generalOptions.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.generalOptions.loadingAvailableInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-center py-4">
|
||||
@@ -150,8 +150,8 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('settings.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.errorLoadingInstallations')}</CardDescription>
|
||||
<CardTitle>{t('settings.generalOptions.claudeCodeInstallation')}</CardTitle>
|
||||
<CardDescription>{t('settings.generalOptions.errorLoadingInstallations')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-sm text-destructive mb-4">{error}</div>
|
||||
@@ -171,19 +171,19 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="h-5 w-5" />
|
||||
{t('settings.claudeCodeInstallation')}
|
||||
{t('settings.generalOptions.claudeCodeInstallation')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('settings.choosePreferredInstallation')}
|
||||
{t('settings.generalOptions.choosePreferredInstallation')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Available Installations */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium">{t('settings.availableInstallations')}</Label>
|
||||
<Label className="text-sm font-medium">{t('settings.generalOptions.availableInstallations')}</Label>
|
||||
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Claude installation">
|
||||
<SelectValue placeholder={t('pleaseSelectInstallation')}>
|
||||
{selectedInstallation && (
|
||||
<div className="flex items-center gap-2">
|
||||
{getInstallationIcon(selectedInstallation)}
|
||||
@@ -198,7 +198,7 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<SelectContent>
|
||||
{systemInstallations.length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">System Installations</div>
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">{t('settings.systemInstallations')}</div>
|
||||
{systemInstallations.map((installation) => (
|
||||
<SelectItem key={installation.path} value={installation.path}>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
@@ -206,11 +206,11 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium truncate">{installation.path}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{installation.version || "Version unknown"} • {installation.source}
|
||||
{installation.version || t('settings.versionUnknown')} • {installation.source}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
System
|
||||
{t('settings.system')}
|
||||
</Badge>
|
||||
</div>
|
||||
</SelectItem>
|
||||
@@ -220,7 +220,7 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
|
||||
{customInstallations.length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Custom Installations</div>
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">{t('settings.customInstallations')}</div>
|
||||
{customInstallations.map((installation) => (
|
||||
<SelectItem key={installation.path} value={installation.path}>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
@@ -228,11 +228,11 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium truncate">{installation.path}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{installation.version || "Version unknown"} • {installation.source}
|
||||
{installation.version || t('settings.versionUnknown')} • {installation.source}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Custom
|
||||
{t('settings.custom')}
|
||||
</Badge>
|
||||
</div>
|
||||
</SelectItem>
|
||||
@@ -247,16 +247,16 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
{selectedInstallation && (
|
||||
<div className="p-3 bg-muted rounded-lg space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Selected Installation</span>
|
||||
<span className="text-sm font-medium">{t('settings.selectedInstallation')}</span>
|
||||
<Badge className={cn("text-xs", getInstallationTypeColor(selectedInstallation))}>
|
||||
{selectedInstallation.installation_type}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div><strong>Path:</strong> {selectedInstallation.path}</div>
|
||||
<div><strong>Source:</strong> {selectedInstallation.source}</div>
|
||||
<div><strong>{t('settings.path')}:</strong> {selectedInstallation.path}</div>
|
||||
<div><strong>{t('settings.source')}:</strong> {selectedInstallation.source}</div>
|
||||
{selectedInstallation.version && (
|
||||
<div><strong>Version:</strong> {selectedInstallation.version}</div>
|
||||
<div><strong>{t('settings.version')}:</strong> {selectedInstallation.version}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,7 +269,7 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
|
||||
disabled={isSaving || !selectedInstallation}
|
||||
className="w-full"
|
||||
>
|
||||
{isSaving ? "Saving..." : "Save Selection"}
|
||||
{isSaving ? t('saving') : t('saveSelection')}
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
|
198
src/components/ClaudiaLogo.tsx
Normal file
198
src/components/ClaudiaLogo.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface ClaudiaLogoProps {
|
||||
size?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ClaudiaLogo({ size = 48, className = "" }: ClaudiaLogoProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`relative inline-flex items-center justify-center ${className}`}
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
{/* Background glow animation */}
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-2xl"
|
||||
style={{
|
||||
background: "radial-gradient(circle, rgba(251, 146, 60, 0.3) 0%, transparent 70%)",
|
||||
}}
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.5, 0.8, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Main logo with image */}
|
||||
<motion.div
|
||||
className="relative w-full h-full rounded-2xl overflow-hidden shadow-xl"
|
||||
animate={{
|
||||
rotateY: [0, 360],
|
||||
}}
|
||||
transition={{
|
||||
duration: 8,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
ease: "easeOut",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Use the actual Claudia icon */}
|
||||
<motion.img
|
||||
src="/icon.png"
|
||||
alt="Claudia"
|
||||
className="w-full h-full object-contain"
|
||||
animate={{
|
||||
scale: [1, 1.05, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Shimmer effect overlay */}
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-tr from-transparent via-white/20 to-transparent"
|
||||
animate={{
|
||||
x: ["-200%", "200%"],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
repeatDelay: 1,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Orbiting particles */}
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute w-1.5 h-1.5 bg-orange-400 rounded-full"
|
||||
style={{
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
}}
|
||||
animate={{
|
||||
x: [
|
||||
0,
|
||||
Math.cos((i * Math.PI) / 2) * size * 0.6,
|
||||
0,
|
||||
-Math.cos((i * Math.PI) / 2) * size * 0.6,
|
||||
0,
|
||||
],
|
||||
y: [
|
||||
0,
|
||||
Math.sin((i * Math.PI) / 2) * size * 0.6,
|
||||
0,
|
||||
-Math.sin((i * Math.PI) / 2) * size * 0.6,
|
||||
0,
|
||||
],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
delay: i * 0.25,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
// Alternative minimalist version
|
||||
export function ClaudiaLogoMinimal({ size = 48, className = "" }: ClaudiaLogoProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className={`relative inline-flex items-center justify-center ${className}`}
|
||||
style={{ width: size, height: size }}
|
||||
animate={{
|
||||
rotate: [0, 5, -5, 5, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
{/* Gradient background */}
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-2xl bg-gradient-to-br from-orange-400 via-orange-500 to-orange-600"
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Inner light effect */}
|
||||
<motion.div
|
||||
className="absolute inset-1 rounded-xl bg-gradient-to-br from-orange-300/50 to-transparent"
|
||||
animate={{
|
||||
opacity: [0.5, 1, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Letter C with animation */}
|
||||
<motion.div
|
||||
className="relative z-10 text-white font-bold flex items-center justify-center"
|
||||
style={{ fontSize: size * 0.5 }}
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
textShadow: [
|
||||
"0 0 10px rgba(255,255,255,0.5)",
|
||||
"0 0 20px rgba(255,255,255,0.8)",
|
||||
"0 0 10px rgba(255,255,255,0.5)",
|
||||
],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
C
|
||||
</motion.div>
|
||||
|
||||
{/* Pulse rings */}
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute inset-0 rounded-2xl border-2 border-orange-400"
|
||||
animate={{
|
||||
scale: [1, 1.5, 2],
|
||||
opacity: [0.5, 0.2, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
delay: i * 1.5,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
@@ -81,33 +81,33 @@ interface EditableHookMatcher extends Omit<HookMatcher, 'hooks'> {
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
const EVENT_INFO: Record<HookEvent, { label: string; description: string; icon: React.ReactNode }> = {
|
||||
const getEventInfo = (t: any): Record<HookEvent, { label: string; description: string; icon: React.ReactNode }> => ({
|
||||
PreToolUse: {
|
||||
label: 'Pre Tool Use',
|
||||
description: 'Runs before tool calls, can block and provide feedback',
|
||||
label: t('hooks.preToolUse'),
|
||||
description: t('hooks.runsBeforeToolCalls'),
|
||||
icon: <Shield className="h-4 w-4" />
|
||||
},
|
||||
PostToolUse: {
|
||||
label: 'Post Tool Use',
|
||||
description: 'Runs after successful tool completion',
|
||||
label: t('hooks.postToolUse'),
|
||||
description: t('hooks.runsAfterToolCompletion'),
|
||||
icon: <PlayCircle className="h-4 w-4" />
|
||||
},
|
||||
Notification: {
|
||||
label: 'Notification',
|
||||
description: 'Customizes notifications when Claude needs attention',
|
||||
label: t('hooks.notification'),
|
||||
description: t('hooks.customizesNotifications'),
|
||||
icon: <Zap className="h-4 w-4" />
|
||||
},
|
||||
Stop: {
|
||||
label: 'Stop',
|
||||
description: 'Runs when Claude finishes responding',
|
||||
label: t('hooks.stop'),
|
||||
description: t('hooks.runsWhenClaudeFinishes'),
|
||||
icon: <Code2 className="h-4 w-4" />
|
||||
},
|
||||
SubagentStop: {
|
||||
label: 'Subagent Stop',
|
||||
description: 'Runs when a Claude subagent (Task) finishes',
|
||||
label: t('hooks.subagentStop'),
|
||||
description: t('hooks.runsWhenSubagentFinishes'),
|
||||
icon: <Terminal className="h-4 w-4" />
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
projectPath,
|
||||
@@ -118,6 +118,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
hideActions = false
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const EVENT_INFO = getEventInfo(t);
|
||||
const [selectedEvent, setSelectedEvent] = useState<HookEvent>('PreToolUse');
|
||||
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||
@@ -525,14 +526,14 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor={`matcher-${matcher.id}`}>Pattern</Label>
|
||||
<Label htmlFor={`matcher-${matcher.id}`}>{t('hooks.pattern')}</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3 w-3 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Tool name pattern (regex supported). Leave empty to match all tools.</p>
|
||||
<p>{t('hooks.toolNamePatternTooltip')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -558,7 +559,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
disabled={readOnly}
|
||||
>
|
||||
<SelectTrigger className="w-40">
|
||||
<SelectValue placeholder="Common patterns" />
|
||||
<SelectValue placeholder={t('hooks.commonPatterns')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
@@ -591,7 +592,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>Commands</Label>
|
||||
<Label>{t('hooks.commands')}</Label>
|
||||
{!readOnly && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -599,7 +600,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
onClick={() => addCommand(event, matcher.id)}
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Add Command
|
||||
{t('hooks.addCommand')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -613,7 +614,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Textarea
|
||||
placeholder="Enter shell command..."
|
||||
placeholder={t('hooks.enterShellCommand')}
|
||||
value={hook.command || ''}
|
||||
onChange={(e) => updateCommand(event, matcher.id, hook.id, { command: e.target.value })}
|
||||
disabled={readOnly}
|
||||
@@ -633,7 +634,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
disabled={readOnly}
|
||||
className="w-20 h-8"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">seconds</span>
|
||||
<span className="text-sm text-muted-foreground">{t('hooks.seconds')}</span>
|
||||
</div>
|
||||
|
||||
{!readOnly && (
|
||||
@@ -679,7 +680,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Textarea
|
||||
placeholder="Enter shell command..."
|
||||
placeholder={t('hooks.enterShellCommand')}
|
||||
value={command.command || ''}
|
||||
onChange={(e) => updateDirectCommand(event, command.id, { command: e.target.value })}
|
||||
disabled={readOnly}
|
||||
@@ -738,7 +739,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
<span className="text-sm text-muted-foreground">Loading hooks configuration...</span>
|
||||
<span className="text-sm text-muted-foreground">{t('hooks.loadingHooksConfiguration')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -759,7 +760,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
<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
|
||||
{scope === 'project' ? t('hooks.projectScope') : scope === 'local' ? t('hooks.localScope') : t('hooks.userScope')}
|
||||
</Badge>
|
||||
{!readOnly && (
|
||||
<>
|
||||
@@ -769,7 +770,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
onClick={() => setShowTemplateDialog(true)}
|
||||
>
|
||||
<FileText className="h-4 w-4 mr-2" />
|
||||
Templates
|
||||
{t('hooks.templates')}
|
||||
</Button>
|
||||
{!hideActions && (
|
||||
<Button
|
||||
@@ -783,7 +784,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
) : (
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
{isSaving ? t('hooks.saving') : t('hooks.save')}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -804,7 +805,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
{/* Validation Messages */}
|
||||
{validationErrors.length > 0 && (
|
||||
<div className="p-3 bg-red-500/10 rounded-md space-y-1">
|
||||
<p className="text-sm font-medium text-red-600">Validation Errors:</p>
|
||||
<p className="text-sm font-medium text-red-600">{t('hooks.validationErrors')}:</p>
|
||||
{validationErrors.map((error, i) => (
|
||||
<p key={i} className="text-xs text-red-600">• {error}</p>
|
||||
))}
|
||||
@@ -813,7 +814,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
|
||||
{validationWarnings.length > 0 && (
|
||||
<div className="p-3 bg-yellow-500/10 rounded-md space-y-1">
|
||||
<p className="text-sm font-medium text-yellow-600">Security Warnings:</p>
|
||||
<p className="text-sm font-medium text-yellow-600">{t('hooks.securityWarnings')}:</p>
|
||||
{validationWarnings.map((warning, i) => (
|
||||
<p key={i} className="text-xs text-yellow-600">• {warning}</p>
|
||||
))}
|
||||
@@ -859,11 +860,11 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
|
||||
{items.length === 0 ? (
|
||||
<Card className="p-8 text-center">
|
||||
<p className="text-muted-foreground mb-4">No hooks configured for this event</p>
|
||||
<p className="text-muted-foreground mb-4">{t('hooks.noHooksConfigured')}</p>
|
||||
{!readOnly && (
|
||||
<Button onClick={() => isMatcherEvent ? addMatcher(event) : addDirectCommand(event)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Hook
|
||||
{t('hooks.addHook')}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
@@ -881,7 +882,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
className="w-full"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Another {isMatcherEvent ? 'Matcher' : 'Command'}
|
||||
{isMatcherEvent ? t('hooks.addAnotherMatcher') : t('hooks.addAnotherCommand')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -895,9 +896,9 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
<Dialog open={showTemplateDialog} onOpenChange={setShowTemplateDialog}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Hook Templates</DialogTitle>
|
||||
<DialogTitle>{t('hooks.hookTemplates')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Choose a pre-configured hook template to get started quickly
|
||||
{t('hooks.quickStartTemplates')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -916,7 +917,7 @@ export const HooksEditor: React.FC<HooksEditorProps> = ({
|
||||
<p className="text-sm text-muted-foreground">{template.description}</p>
|
||||
{matcherEvents.includes(template.event as any) && template.matcher && (
|
||||
<p className="text-xs font-mono bg-muted px-2 py-1 rounded inline-block">
|
||||
Matcher: {template.matcher}
|
||||
{t('hooks.pattern')}: {template.matcher}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -145,6 +145,7 @@ import {
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
/**
|
||||
* Icon categories for better organization
|
||||
@@ -333,6 +334,7 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [hoveredIcon, setHoveredIcon] = useState<string | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Filter icons based on search query
|
||||
const filteredCategories = useMemo(() => {
|
||||
@@ -368,7 +370,7 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] p-0">
|
||||
<DialogHeader className="px-6 py-4 border-b">
|
||||
<DialogTitle>Choose an icon</DialogTitle>
|
||||
<DialogTitle>{t('agents.chooseAnIcon')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Search Bar */}
|
||||
@@ -376,7 +378,7 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
<div className="relative">
|
||||
<SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search icons..."
|
||||
placeholder={t('agents.searchIcons')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10"
|
||||
@@ -390,7 +392,7 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
{Object.keys(filteredCategories).length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-32 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No icons found for "{searchQuery}"
|
||||
{t('agents.noIconsFound')} "{searchQuery}"
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -405,7 +407,15 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<h3 className="text-sm font-medium text-muted-foreground mb-3">
|
||||
{category}
|
||||
{category === "Interface & Navigation" ? t('agents.iconCategories.interfaceNavigation') :
|
||||
category === "Development & Tech" ? t('agents.iconCategories.developmentTech') :
|
||||
category === "Business & Finance" ? t('agents.iconCategories.businessFinance') :
|
||||
category === "Creative & Design" ? t('agents.iconCategories.creativeDesign') :
|
||||
category === "Nature & Science" ? t('agents.iconCategories.natureScience') :
|
||||
category === "Gaming & Entertainment" ? t('agents.iconCategories.gamingEntertainment') :
|
||||
category === "Communication" ? t('agents.iconCategories.communication') :
|
||||
category === "Miscellaneous" ? t('agents.iconCategories.miscellaneous') :
|
||||
category}
|
||||
</h3>
|
||||
<div className="grid grid-cols-10 gap-2">
|
||||
{icons.map((item: IconItem) => {
|
||||
@@ -444,7 +454,7 @@ export const IconPicker: React.FC<IconPickerProps> = ({
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-3 border-t bg-muted/50">
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
Click an icon to select • {allIcons.length} icons available
|
||||
{t('agents.clickToSelect')} • {allIcons.length} {t('agents.iconsAvailable')}
|
||||
</p>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
@@ -108,7 +108,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
}
|
||||
|
||||
if (!stdioCommand.trim()) {
|
||||
onError(t('commandRequired'));
|
||||
onError(t('mcp.commandRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError(t('failedToAddServer'));
|
||||
onError(t('mcp.failedToAddServer'));
|
||||
console.error("Failed to add stdio server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -171,7 +171,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
}
|
||||
|
||||
if (!sseUrl.trim()) {
|
||||
onError(t('urlRequired'));
|
||||
onError(t('mcp.urlRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onError(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
onError(t('failedToAddServer'));
|
||||
onError(t('mcp.failedToAddServer'));
|
||||
console.error("Failed to add SSE server:", error);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
@@ -227,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">{t('environmentVariables')}</Label>
|
||||
<Label className="text-sm font-medium">{t('mcp.environmentVariables')}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -235,7 +235,7 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
{t('addVariable')}
|
||||
{t('mcp.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -244,14 +244,14 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{envVars.map((envVar) => (
|
||||
<div key={envVar.id} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="KEY"
|
||||
placeholder={t('settings.placeholders.envVarKey')}
|
||||
value={envVar.key}
|
||||
onChange={(e) => updateEnvVar(type, 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(type, envVar.id, "value", e.target.value)}
|
||||
className="flex-1 font-mono text-sm"
|
||||
@@ -275,9 +275,9 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">{t('addMcpServer')}</h3>
|
||||
<h3 className="text-base font-semibold">{t('mcp.addMcpServer')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t('configureNewMcpServer')}
|
||||
{t('mcp.configureNewMcpServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -298,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">{t('serverName')}</Label>
|
||||
<Label htmlFor="stdio-name">{t('mcp.serverName')}</Label>
|
||||
<Input
|
||||
id="stdio-name"
|
||||
placeholder="my-server"
|
||||
@@ -306,12 +306,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setStdioName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('uniqueNameToIdentify')}
|
||||
{t('mcp.uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-command">{t('command')}</Label>
|
||||
<Label htmlFor="stdio-command">{t('mcp.command')}</Label>
|
||||
<Input
|
||||
id="stdio-command"
|
||||
placeholder="/path/to/server"
|
||||
@@ -320,12 +320,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('commandToExecuteServer')}
|
||||
{t('mcp.commandToExecuteServer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-args">{t('argumentsOptional')}</Label>
|
||||
<Label htmlFor="stdio-args">{t('mcp.argumentsOptional')}</Label>
|
||||
<Input
|
||||
id="stdio-args"
|
||||
placeholder="arg1 arg2 arg3"
|
||||
@@ -334,19 +334,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('spaceSeparatedArgs')}
|
||||
{t('mcp.spaceSeparatedArgs')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stdio-scope">{t('scope')}</Label>
|
||||
<Label htmlFor="stdio-scope">{t('mcp.scope')}</Label>
|
||||
<SelectComponent
|
||||
value={stdioScope}
|
||||
onValueChange={(value: string) => setStdioScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: t('localProjectOnly') },
|
||||
{ value: "project", label: t('projectSharedViaMcp') },
|
||||
{ value: "user", label: t('userAllProjects') },
|
||||
{ value: "local", label: t('mcp.localProjectOnly') },
|
||||
{ value: "project", label: t('mcp.projectSharedViaMcp') },
|
||||
{ value: "user", label: t('mcp.userAllProjects') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -363,12 +363,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t('addingServer')}
|
||||
{t('mcp.addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
{t('addStdioServer')}
|
||||
{t('mcp.addStdioServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -381,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">{t('serverName')}</Label>
|
||||
<Label htmlFor="sse-name">{t('mcp.serverName')}</Label>
|
||||
<Input
|
||||
id="sse-name"
|
||||
placeholder="sse-server"
|
||||
@@ -389,12 +389,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
onChange={(e) => setSseName(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('uniqueNameToIdentify')}
|
||||
{t('mcp.uniqueNameToIdentify')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-url">{t('url')}</Label>
|
||||
<Label htmlFor="sse-url">{t('mcp.url')}</Label>
|
||||
<Input
|
||||
id="sse-url"
|
||||
placeholder="https://example.com/sse-endpoint"
|
||||
@@ -403,19 +403,19 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('sseEndpointUrl')}
|
||||
{t('mcp.sseEndpointUrl')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sse-scope">{t('scope')}</Label>
|
||||
<Label htmlFor="sse-scope">{t('mcp.scope')}</Label>
|
||||
<SelectComponent
|
||||
value={sseScope}
|
||||
onValueChange={(value: string) => setSseScope(value)}
|
||||
options={[
|
||||
{ value: "local", label: t('localProjectOnly') },
|
||||
{ value: "project", label: t('projectSharedViaMcp') },
|
||||
{ value: "user", label: t('userAllProjects') },
|
||||
{ value: "local", label: t('mcp.localProjectOnly') },
|
||||
{ value: "project", label: t('mcp.projectSharedViaMcp') },
|
||||
{ value: "user", label: t('mcp.userAllProjects') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -432,12 +432,12 @@ export const MCPAddServer: React.FC<MCPAddServerProps> = ({
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t('addingServer')}
|
||||
{t('mcp.addingServer')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="h-4 w-4" />
|
||||
{t('addSseServer')}
|
||||
{t('mcp.addSseServer')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -451,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>{t('exampleCommands')}</span>
|
||||
<span>{t('mcp.exampleCommands')}</span>
|
||||
</div>
|
||||
<div className="space-y-2 text-xs text-muted-foreground">
|
||||
<div className="font-mono bg-background p-2 rounded">
|
||||
|
@@ -5,6 +5,7 @@ import { Card } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { SelectComponent } from "@/components/ui/select";
|
||||
import { api } from "@/lib/api";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface MCPImportExportProps {
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
onImportCompleted,
|
||||
onError,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [importingDesktop, setImportingDesktop] = useState(false);
|
||||
const [importingJson, setImportingJson] = useState(false);
|
||||
const [importScope, setImportScope] = useState("local");
|
||||
@@ -163,9 +165,9 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">Import & Export</h3>
|
||||
<h3 className="text-base font-semibold">{t('mcp.importExport')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Import MCP servers from other sources or export your configuration
|
||||
{t('mcp.importExportDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -175,19 +177,19 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Settings2 className="h-4 w-4 text-slate-500" />
|
||||
<Label className="text-sm font-medium">Import Scope</Label>
|
||||
<Label className="text-sm font-medium">{t('mcp.importScope')}</Label>
|
||||
</div>
|
||||
<SelectComponent
|
||||
value={importScope}
|
||||
onValueChange={(value: string) => setImportScope(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('mcp.localProjectOnly') },
|
||||
{ value: "project", label: t('mcp.projectShared') },
|
||||
{ value: "user", label: t('mcp.userAllProjects') },
|
||||
]}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose where to save imported servers from JSON files
|
||||
{t('mcp.chooseImportLocation')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -200,9 +202,9 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<Download className="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium">Import from Claude Desktop</h4>
|
||||
<h4 className="text-sm font-medium">{t('mcp.importFromClaudeDesktop')}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Automatically imports all MCP servers from Claude Desktop. Installs to user scope (available across all projects).
|
||||
{t('mcp.importFromClaudeDesktopDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,12 +216,12 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
{importingDesktop ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Importing...
|
||||
{t('mcp.importing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4" />
|
||||
Import from Claude Desktop
|
||||
{t('mcp.importFromClaudeDesktop')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -234,9 +236,9 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<FileText className="h-5 w-5 text-purple-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium">Import from JSON</h4>
|
||||
<h4 className="text-sm font-medium">{t('mcp.importFromJSON')}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Import server configuration from a JSON file
|
||||
{t('mcp.importFromJSONDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,12 +260,12 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
{importingJson ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Importing...
|
||||
{t('mcp.importing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileText className="h-4 w-4" />
|
||||
Choose JSON File
|
||||
{t('mcp.chooseJSONFile')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -279,9 +281,9 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<Upload className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium">Export Configuration</h4>
|
||||
<h4 className="text-sm font-medium">{t('mcp.exportConfiguration')}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Export your MCP server configuration
|
||||
{t('mcp.exportConfigurationDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -292,7 +294,7 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
className="w-full gap-2"
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Export (Coming Soon)
|
||||
{t('mcp.exportComingSoon')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -305,9 +307,9 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<Network className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-medium">Use Claude Code as MCP Server</h4>
|
||||
<h4 className="text-sm font-medium">{t('mcp.useClaudeCodeAsMCPServer')}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Start Claude Code as an MCP server that other applications can connect to
|
||||
{t('mcp.useClaudeCodeAsMCPServerDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,7 +319,7 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
className="w-full gap-2 border-green-500/20 hover:bg-green-500/10 hover:text-green-600 hover:border-green-500/50"
|
||||
>
|
||||
<Network className="h-4 w-4" />
|
||||
Start MCP Server
|
||||
{t('mcp.startMCPServer')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -328,11 +330,11 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
<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>JSON Format Examples</span>
|
||||
<span>{t('mcp.jsonFormatExamples')}</span>
|
||||
</div>
|
||||
<div className="space-y-3 text-xs">
|
||||
<div>
|
||||
<p className="font-medium text-muted-foreground mb-1">Single server:</p>
|
||||
<p className="font-medium text-muted-foreground mb-1">{t('mcp.singleServer')}:</p>
|
||||
<pre className="bg-background p-3 rounded-lg overflow-x-auto">
|
||||
{`{
|
||||
"type": "stdio",
|
||||
@@ -343,7 +345,7 @@ export const MCPImportExport: React.FC<MCPImportExportProps> = ({
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-muted-foreground mb-1">Multiple servers (.mcp.json format):</p>
|
||||
<p className="font-medium text-muted-foreground mb-1">{t('mcp.multipleServers')}:</p>
|
||||
<pre className="bg-background p-3 rounded-lg overflow-x-auto">
|
||||
{`{
|
||||
"mcpServers": {
|
||||
|
@@ -155,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" />
|
||||
{t('servers')}
|
||||
{t('mcp.servers')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="add" className="gap-2">
|
||||
<Plus className="h-4 w-4 text-green-500" />
|
||||
{t('addServer')}
|
||||
{t('mcp.addServer')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="import" className="gap-2">
|
||||
<Download className="h-4 w-4 text-purple-500" />
|
||||
{t('importExport')}
|
||||
{t('mcp.importExport')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
@@ -186,11 +186,11 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
const getScopeDisplayName = (scope: string) => {
|
||||
switch (scope) {
|
||||
case "local":
|
||||
return t('localProjectSpecific');
|
||||
return t('mcp.localProjectSpecific');
|
||||
case "project":
|
||||
return t('projectSharedMcp');
|
||||
return t('mcp.projectSharedMcp');
|
||||
case "user":
|
||||
return t('userAllProjects');
|
||||
return t('mcp.userAllProjects');
|
||||
default:
|
||||
return scope;
|
||||
}
|
||||
@@ -222,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" />
|
||||
{t('running')}
|
||||
{t('mcp.running')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -239,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" />
|
||||
{t('showFull')}
|
||||
{t('mcp.showFull')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -254,7 +254,7 @@ export const MCPServerList: React.FC<MCPServerListProps> = ({
|
||||
|
||||
{Object.keys(server.env).length > 0 && !isExpanded && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground pl-9">
|
||||
<span>Environment variables: {Object.keys(server.env).length}</span>
|
||||
<span>{t('mcp.environmentVariablesCount', { count: Object.keys(server.env).length })}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -301,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">{t('command')}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.command')}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -310,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 ? t('copied') : t('copy')}
|
||||
{isCopied ? t('mcp.copied') : t('mcp.copy')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -319,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" />
|
||||
{t('hide')}
|
||||
{t('mcp.hide')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -331,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">{t('arguments')}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.arguments')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<div key={idx} className="break-all">
|
||||
@@ -345,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">{t('url')}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.url')}</p>
|
||||
<p className="text-xs font-mono bg-muted/50 p-2 rounded break-all">
|
||||
{server.url}
|
||||
</p>
|
||||
@@ -354,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">{t('environmentVariables')}</p>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('mcp.environmentVariables')}</p>
|
||||
<div className="text-xs font-mono bg-muted/50 p-2 rounded space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="break-all">
|
||||
@@ -386,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">{t('configuredServers')}</h3>
|
||||
<h3 className="text-base font-semibold">{t('mcp.configuredServers')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{servers.length} {servers.length !== 1 ? t('servers') : 'server'} {t('serversConfigured')}
|
||||
{servers.length} {t('mcp.serversCount', { count: servers.length })}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
@@ -53,7 +53,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
setOriginalContent(prompt);
|
||||
} catch (err) {
|
||||
console.error("Failed to load system prompt:", err);
|
||||
setError(t('loadClaudemdFailed'));
|
||||
setError(t('usage.loadClaudemdFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -66,11 +66,11 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
setToast(null);
|
||||
await api.saveSystemPrompt(content);
|
||||
setOriginalContent(content);
|
||||
setToast({ message: t('claudemdSavedSuccess'), type: "success" });
|
||||
setToast({ message: t('usage.claudemdSavedSuccess'), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save system prompt:", err);
|
||||
setError(t('saveClaudemdFailed'));
|
||||
setToast({ message: t('saveClaudemdFailed'), type: "error" });
|
||||
setError(t('usage.saveClaudemdFailed'));
|
||||
setToast({ message: t('usage.saveClaudemdFailed'), type: "error" });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
const handleBack = () => {
|
||||
if (hasChanges) {
|
||||
const confirmLeave = window.confirm(
|
||||
t('unsavedChangesConfirm')
|
||||
t('usage.unsavedChangesConfirm')
|
||||
);
|
||||
if (!confirmLeave) return;
|
||||
}
|
||||
@@ -108,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">
|
||||
{t('editSystemPrompt')}
|
||||
{t('usage.editSystemPrompt')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -85,8 +85,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={onBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
||||
|
@@ -315,6 +315,22 @@ 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();
|
||||
const [hasInitialized, setHasInitialized] = React.useState(false);
|
||||
|
||||
// Auto redirect to home when no tabs (but not on initial load)
|
||||
useEffect(() => {
|
||||
if (hasInitialized && tabs.length === 0) {
|
||||
// Dispatch event to switch back to welcome view
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('switch-to-welcome'));
|
||||
}, 100);
|
||||
}
|
||||
}, [tabs.length, hasInitialized]);
|
||||
|
||||
// Mark as initialized after first render
|
||||
useEffect(() => {
|
||||
setHasInitialized(true);
|
||||
}, []);
|
||||
|
||||
// Listen for events to open sessions in tabs
|
||||
useEffect(() => {
|
||||
|
@@ -142,6 +142,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
createProjectsTab,
|
||||
closeTab,
|
||||
switchToTab,
|
||||
updateTab,
|
||||
canAddTab
|
||||
} = useTabState();
|
||||
|
||||
@@ -209,11 +210,30 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenSessionTab = (event: CustomEvent) => {
|
||||
const { session, projectPath } = event.detail;
|
||||
if (session && canAddTab()) {
|
||||
// Create a new chat tab with the session data
|
||||
const tabId = createChatTab();
|
||||
// Update the tab with session data
|
||||
setTimeout(() => {
|
||||
updateTab(tabId, {
|
||||
type: 'chat',
|
||||
title: session.project_path.split('/').pop() || 'Session',
|
||||
sessionId: session.id,
|
||||
sessionData: session,
|
||||
initialProjectPath: projectPath || session.project_path,
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('create-chat-tab', handleCreateTab);
|
||||
window.addEventListener('close-current-tab', handleCloseTab);
|
||||
window.addEventListener('switch-to-next-tab', handleNextTab);
|
||||
window.addEventListener('switch-to-previous-tab', handlePreviousTab);
|
||||
window.addEventListener('switch-to-tab-by-index', handleTabByIndex as EventListener);
|
||||
window.addEventListener('open-session-tab', handleOpenSessionTab as EventListener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('create-chat-tab', handleCreateTab);
|
||||
@@ -221,8 +241,9 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
window.removeEventListener('switch-to-next-tab', handleNextTab);
|
||||
window.removeEventListener('switch-to-previous-tab', handlePreviousTab);
|
||||
window.removeEventListener('switch-to-tab-by-index', handleTabByIndex as EventListener);
|
||||
window.removeEventListener('open-session-tab', handleOpenSessionTab as EventListener);
|
||||
};
|
||||
}, [tabs, activeTabId, createChatTab, closeTab, switchToTab]);
|
||||
}, [tabs, activeTabId, createChatTab, closeTab, switchToTab, updateTab, canAddTab]);
|
||||
|
||||
// Check scroll buttons visibility
|
||||
const checkScrollButtons = () => {
|
||||
@@ -319,7 +340,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
className={cn(
|
||||
"p-1.5 hover:bg-muted/80 rounded-sm z-20 ml-1",
|
||||
"transition-colors duration-200 flex items-center justify-center",
|
||||
"bg-background/80 backdrop-blur-sm shadow-sm border border-border/50"
|
||||
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
||||
)}
|
||||
title="Scroll tabs left"
|
||||
>
|
||||
@@ -373,7 +394,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
className={cn(
|
||||
"p-1.5 hover:bg-muted/80 rounded-sm z-20 mr-1",
|
||||
"transition-colors duration-200 flex items-center justify-center",
|
||||
"bg-background/80 backdrop-blur-sm shadow-sm border border-border/50"
|
||||
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
||||
)}
|
||||
title="Scroll tabs right"
|
||||
>
|
||||
@@ -390,7 +411,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||
disabled={!canAddTab()}
|
||||
className={cn(
|
||||
"p-2 mx-2 rounded-md transition-all duration-200 flex items-center justify-center",
|
||||
"border border-border/50 bg-background/50 backdrop-blur-sm",
|
||||
"border border-border/60 bg-background/85 backdrop-blur-xl backdrop-saturate-[1.8]",
|
||||
canAddTab()
|
||||
? "hover:bg-muted/80 hover:border-border text-muted-foreground hover:text-foreground hover:shadow-sm"
|
||||
: "opacity-50 cursor-not-allowed bg-muted/30"
|
||||
|
@@ -107,7 +107,11 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-auto py-1 px-2 hover:bg-accent"
|
||||
onClick={onSettingsClick}
|
||||
onClick={() => {
|
||||
// Emit event to return to home
|
||||
window.dispatchEvent(new CustomEvent('switch-to-welcome'));
|
||||
}}
|
||||
title="Return to Home"
|
||||
>
|
||||
<div className="flex items-center space-x-2 text-xs">
|
||||
<Circle
|
||||
@@ -172,7 +176,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className={cn(
|
||||
"flex items-center justify-between px-4 py-3 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
|
||||
"flex items-center justify-between px-4 py-3 border-b border-border bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] supports-[backdrop-filter]:bg-background/85 shadow-sm",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
@@ -16,6 +16,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
|
||||
interface WebviewPreviewProps {
|
||||
/**
|
||||
@@ -61,6 +62,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
onUrlChange,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentUrl, setCurrentUrl] = useState(initialUrl);
|
||||
const [inputUrl, setInputUrl] = useState(initialUrl);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -132,7 +134,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
onUrlChange?.(finalUrl);
|
||||
} catch (err) {
|
||||
setHasError(true);
|
||||
setErrorMessage("Invalid URL");
|
||||
setErrorMessage(t('webview.invalidUrl'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,7 +184,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Preview</span>
|
||||
<span className="text-sm font-medium">{t('webview.preview')}</span>
|
||||
{isLoading && (
|
||||
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
@@ -207,7 +209,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isMaximized ? "Exit full screen (ESC)" : "Enter full screen"}
|
||||
{isMaximized ? t('webview.exitFullScreen') : t('webview.enterFullScreen')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -270,7 +272,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
value={inputUrl}
|
||||
onChange={(e) => setInputUrl(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter URL..."
|
||||
placeholder={t('webview.enterUrl')}
|
||||
className="pr-10 h-8 text-sm font-mono"
|
||||
/>
|
||||
{inputUrl !== currentUrl && (
|
||||
@@ -300,7 +302,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<p className="text-sm text-muted-foreground">Loading preview...</p>
|
||||
<p className="text-sm text-muted-foreground">{t('webview.loadingPreview')}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -310,12 +312,12 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
{hasError ? (
|
||||
<div className="flex flex-col items-center justify-center h-full p-8">
|
||||
<AlertCircle className="h-12 w-12 text-destructive mb-4" />
|
||||
<h3 className="text-lg font-semibold mb-2">Failed to load preview</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">{t('webview.failedToLoad')}</h3>
|
||||
<p className="text-sm text-muted-foreground text-center mb-4">
|
||||
{errorMessage || "The page could not be loaded. Please check the URL and try again."}
|
||||
{errorMessage || t('webview.pageCouldNotLoad')}
|
||||
</p>
|
||||
<Button onClick={handleRefresh} variant="outline" size="sm">
|
||||
Try Again
|
||||
{t('app.retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : currentUrl ? (
|
||||
@@ -336,14 +338,14 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
||||
// Empty state when no URL is provided
|
||||
<div className="flex flex-col items-center justify-center h-full p-8 text-foreground">
|
||||
<Globe className="h-16 w-16 text-muted-foreground/50 mb-6" />
|
||||
<h3 className="text-xl font-semibold mb-3">Enter a URL to preview</h3>
|
||||
<h3 className="text-xl font-semibold mb-3">{t('webview.enterUrlToPreview')}</h3>
|
||||
<p className="text-sm text-muted-foreground text-center mb-6 max-w-md">
|
||||
Enter a URL in the address bar above to preview a website.
|
||||
{t('webview.enterUrlDescription')}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>Try entering</span>
|
||||
<span>{t('webview.tryEntering')}</span>
|
||||
<code className="px-2 py-1 bg-muted/50 text-foreground rounded font-mono text-xs">localhost:3000</code>
|
||||
<span>or any other URL</span>
|
||||
<span>{t('webview.orAnyOtherUrl')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
207
src/components/WelcomePage.tsx
Normal file
207
src/components/WelcomePage.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Bot, FolderCode, BarChart, ServerCog, FileText, Settings } from "lucide-react";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ClaudiaLogoMinimal } from "@/components/ClaudiaLogo";
|
||||
import { BorderGlowCard } from "@/components/ui/glow-card";
|
||||
|
||||
interface WelcomePageProps {
|
||||
onNavigate: (view: string) => void;
|
||||
onNewSession: () => void;
|
||||
}
|
||||
|
||||
export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const mainFeatures = [
|
||||
{
|
||||
id: "agents",
|
||||
icon: Bot,
|
||||
title: t("welcome.agentManagement"),
|
||||
subtitle: t("welcome.agentManagementDesc"),
|
||||
color: "text-orange-500",
|
||||
bgColor: "bg-orange-500/10",
|
||||
view: "cc-agents"
|
||||
},
|
||||
{
|
||||
id: "projects",
|
||||
icon: FolderCode,
|
||||
title: t("welcome.projectManagement"),
|
||||
subtitle: t("welcome.projectManagementDesc"),
|
||||
color: "text-blue-500",
|
||||
bgColor: "bg-blue-500/10",
|
||||
view: "projects"
|
||||
}
|
||||
];
|
||||
|
||||
const bottomFeatures = [
|
||||
{
|
||||
id: "usage",
|
||||
icon: BarChart,
|
||||
title: t("welcome.usageStatistics"),
|
||||
subtitle: t("welcome.usageStatisticsDesc"),
|
||||
color: "text-green-500",
|
||||
bgColor: "bg-green-500/10",
|
||||
view: "usage-dashboard"
|
||||
},
|
||||
{
|
||||
id: "mcp",
|
||||
icon: ServerCog,
|
||||
title: t("welcome.mcpBroker"),
|
||||
subtitle: t("welcome.mcpBrokerDesc"),
|
||||
color: "text-purple-500",
|
||||
bgColor: "bg-purple-500/10",
|
||||
view: "mcp"
|
||||
},
|
||||
{
|
||||
id: "claude-md",
|
||||
icon: FileText,
|
||||
title: t("welcome.claudeMd"),
|
||||
subtitle: t("welcome.claudeMdDesc"),
|
||||
color: "text-cyan-500",
|
||||
bgColor: "bg-cyan-500/10",
|
||||
view: "editor"
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
icon: Settings,
|
||||
title: t("welcome.settings"),
|
||||
subtitle: t("welcome.settingsDesc"),
|
||||
color: "text-gray-500",
|
||||
bgColor: "bg-gray-500/10",
|
||||
view: "settings"
|
||||
}
|
||||
];
|
||||
|
||||
const handleCardClick = (view: string) => {
|
||||
onNavigate(view);
|
||||
};
|
||||
|
||||
const handleButtonClick = () => {
|
||||
onNewSession();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-background overflow-hidden">
|
||||
<div className="w-full max-w-6xl px-8">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
className="text-center mb-16"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<h1 className="text-5xl font-bold mb-4 flex items-center justify-center gap-4 bg-gradient-to-r from-orange-400 via-pink-500 to-purple-600 bg-clip-text text-transparent">
|
||||
<ClaudiaLogoMinimal size={56} />
|
||||
{t("app.welcome")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-xl">
|
||||
{t("app.tagline")}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Feature Cards */}
|
||||
<div className="grid grid-cols-2 gap-8 mb-12">
|
||||
{mainFeatures.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.id}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.4,
|
||||
delay: 0.1 * index,
|
||||
type: "spring",
|
||||
stiffness: 100
|
||||
}}
|
||||
>
|
||||
<BorderGlowCard
|
||||
className="h-full group"
|
||||
onClick={() => handleCardClick(feature.view)}
|
||||
>
|
||||
<div className="p-10">
|
||||
<div className="flex items-start gap-6">
|
||||
<div className={`p-4 ${feature.bgColor} rounded-2xl transition-transform duration-300 group-hover:scale-110 group-hover:rotate-3`}>
|
||||
<feature.icon className={`h-10 w-10 ${feature.color}`} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold mb-3 group-hover:text-primary transition-colors">
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-base leading-relaxed">
|
||||
{feature.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BorderGlowCard>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom Feature Cards */}
|
||||
<div className="grid grid-cols-4 gap-6 mb-12">
|
||||
{bottomFeatures.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 0.4,
|
||||
delay: 0.3 + 0.05 * index,
|
||||
type: "spring",
|
||||
stiffness: 100
|
||||
}}
|
||||
>
|
||||
<BorderGlowCard
|
||||
className="h-36 group"
|
||||
onClick={() => handleCardClick(feature.view)}
|
||||
>
|
||||
<div className="h-full flex flex-col items-center justify-center p-6">
|
||||
<div className={`p-3 ${feature.bgColor} rounded-xl mb-3 transition-transform duration-300 group-hover:scale-110 group-hover:rotate-6`}>
|
||||
<feature.icon className={`h-8 w-8 ${feature.color}`} strokeWidth={1.5} />
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold mb-1 group-hover:text-primary transition-colors">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground text-center line-clamp-2">
|
||||
{feature.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</BorderGlowCard>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Quick Action Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: 0.6,
|
||||
type: "spring",
|
||||
stiffness: 100
|
||||
}}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="relative px-10 py-7 text-lg font-semibold bg-gradient-to-r from-orange-500 to-pink-500 hover:from-orange-600 hover:to-pink-600 text-white border-0 shadow-2xl hover:shadow-orange-500/25 transition-all duration-300 hover:scale-105 rounded-2xl group overflow-hidden"
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
{/* Shimmer effect on button */}
|
||||
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" />
|
||||
</div>
|
||||
|
||||
<span className="relative z-10 flex items-center gap-3">
|
||||
<span className="text-2xl">✨</span>
|
||||
{t("welcome.quickStartSession")}
|
||||
<span className="text-2xl">🚀</span>
|
||||
</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
309
src/components/ui/glow-card.tsx
Normal file
309
src/components/ui/glow-card.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface GlowCardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
glowClassName?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const GlowCard: React.FC<GlowCardProps> = ({
|
||||
children,
|
||||
className,
|
||||
glowClassName,
|
||||
onClick,
|
||||
}) => {
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
setMousePosition({ x, y });
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-xl transition-all duration-300",
|
||||
"bg-card hover:shadow-2xl",
|
||||
className
|
||||
)}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Glow effect layer */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 opacity-0 transition-opacity duration-300",
|
||||
isHovered && "opacity-100",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
style={{
|
||||
background: `radial-gradient(600px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(120, 119, 198, 0.1), transparent 40%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Border glow effect */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 rounded-xl opacity-0 transition-opacity duration-300",
|
||||
isHovered && "opacity-100",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
style={{
|
||||
background: `radial-gradient(400px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(120, 119, 198, 0.3), transparent 40%)`,
|
||||
padding: "1px",
|
||||
maskImage: "linear-gradient(#000, #000)",
|
||||
maskClip: "content-box, border-box",
|
||||
maskComposite: "exclude",
|
||||
WebkitMaskImage: "linear-gradient(#000, #000)",
|
||||
WebkitMaskClip: "content-box, border-box",
|
||||
WebkitMaskComposite: "xor",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Animated gradient border */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 rounded-xl",
|
||||
"bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500",
|
||||
"opacity-0 transition-opacity duration-500",
|
||||
isHovered && "opacity-20 animate-pulse",
|
||||
"pointer-events-none",
|
||||
glowClassName
|
||||
)}
|
||||
style={{
|
||||
padding: "2px",
|
||||
maskImage: "linear-gradient(#000, #000)",
|
||||
maskClip: "content-box, border-box",
|
||||
maskComposite: "exclude",
|
||||
WebkitMaskImage: "linear-gradient(#000, #000)",
|
||||
WebkitMaskClip: "content-box, border-box",
|
||||
WebkitMaskComposite: "xor",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 高级炫光卡片组件,带有更多动画效果
|
||||
export const AdvancedGlowCard: React.FC<GlowCardProps> = ({
|
||||
children,
|
||||
className,
|
||||
glowClassName,
|
||||
onClick,
|
||||
}) => {
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const [gradientAngle, setGradientAngle] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHovered) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setGradientAngle((prev) => (prev + 1) % 360);
|
||||
}, 50);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [isHovered]);
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!cardRef.current) return;
|
||||
|
||||
const rect = cardRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
setMousePosition({ x, y });
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
setGradientAngle(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-xl transition-all duration-500",
|
||||
"bg-card/80 backdrop-blur-sm",
|
||||
"hover:shadow-2xl hover:shadow-primary/20",
|
||||
"hover:scale-[1.02] hover:-translate-y-1",
|
||||
"cursor-pointer",
|
||||
className
|
||||
)}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Main glow effect */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 opacity-0 transition-opacity duration-500",
|
||||
isHovered && "opacity-100",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
style={{
|
||||
background: `radial-gradient(800px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(147, 51, 234, 0.15), transparent 40%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Secondary glow effect */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 opacity-0 transition-opacity duration-500",
|
||||
isHovered && "opacity-60",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
style={{
|
||||
background: `radial-gradient(600px circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(59, 130, 246, 0.1), transparent 40%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Animated gradient border */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 rounded-xl",
|
||||
"opacity-0 transition-opacity duration-300",
|
||||
isHovered && "opacity-100",
|
||||
"pointer-events-none",
|
||||
glowClassName
|
||||
)}
|
||||
style={{
|
||||
background: `linear-gradient(${gradientAngle}deg, #3b82f6, #8b5cf6, #ec4899, #3b82f6)`,
|
||||
padding: "2px",
|
||||
maskImage: "linear-gradient(#000, #000)",
|
||||
maskClip: "content-box, border-box",
|
||||
maskComposite: "exclude",
|
||||
WebkitMaskImage: "linear-gradient(#000, #000)",
|
||||
WebkitMaskClip: "content-box, border-box",
|
||||
WebkitMaskComposite: "xor",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Shimmer effect */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 opacity-0",
|
||||
isHovered && "opacity-100 animate-shimmer",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
style={{
|
||||
background: "linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.1) 50%, transparent 60%)",
|
||||
animation: isHovered ? "shimmer 1.5s infinite" : "none",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 简约边框跑马灯卡片组件
|
||||
export const BorderGlowCard: React.FC<GlowCardProps> = ({
|
||||
children,
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-xl transition-all duration-500",
|
||||
"bg-card",
|
||||
"hover:shadow-lg hover:shadow-orange-500/10",
|
||||
"hover:scale-[1.01]",
|
||||
"cursor-pointer",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* 橙色边框跑马灯效果 - 只有一小段 */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 rounded-xl",
|
||||
"opacity-0 transition-opacity duration-300",
|
||||
isHovered && "opacity-100",
|
||||
"pointer-events-none"
|
||||
)}
|
||||
>
|
||||
{/* 旋转的渐变边框 - 一小段光带 */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 rounded-xl",
|
||||
isHovered && "animate-spin-slow"
|
||||
)}
|
||||
style={{
|
||||
background: `conic-gradient(from 0deg,
|
||||
transparent 0deg,
|
||||
transparent 85deg,
|
||||
#fb923c 90deg,
|
||||
#f97316 95deg,
|
||||
#fb923c 100deg,
|
||||
transparent 105deg,
|
||||
transparent 360deg
|
||||
)`,
|
||||
}}
|
||||
/>
|
||||
{/* 内部遮罩,只显示边框 */}
|
||||
<div
|
||||
className="absolute inset-[2px] rounded-xl bg-card"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 静态边框 */}
|
||||
<div className="absolute inset-0 rounded-xl border border-border/50" />
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import {initReactI18next} from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// 引入语言资源文件
|
||||
@@ -8,50 +8,50 @@ import zh from '@/locales/zh/common.json';
|
||||
|
||||
// 配置语言检测器
|
||||
const languageDetectorOptions = {
|
||||
// 检测顺序
|
||||
order: ['localStorage', 'navigator', 'htmlTag'],
|
||||
// 缓存语言到localStorage
|
||||
caches: ['localStorage'],
|
||||
// 检查所有可用语言
|
||||
checkWhitelist: true,
|
||||
// 检测顺序
|
||||
order: ['localStorage', 'navigator', 'htmlTag'],
|
||||
// 缓存语言到localStorage
|
||||
caches: ['localStorage'],
|
||||
// 检查所有可用语言
|
||||
checkWhitelist: true,
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
// 回退语言
|
||||
fallbackLng: 'en',
|
||||
// 调试模式(开发环境)
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
|
||||
// 语言资源
|
||||
resources: {
|
||||
en: {
|
||||
common: en,
|
||||
},
|
||||
zh: {
|
||||
common: zh,
|
||||
},
|
||||
},
|
||||
|
||||
// 命名空间配置
|
||||
defaultNS: 'common',
|
||||
ns: ['common'],
|
||||
|
||||
// 语言检测选项
|
||||
detection: languageDetectorOptions,
|
||||
|
||||
// 插值配置
|
||||
interpolation: {
|
||||
escapeValue: false, // React 已经默认防止XSS
|
||||
},
|
||||
|
||||
// 白名单支持的语言
|
||||
supportedLngs: ['en', 'zh'],
|
||||
|
||||
// 非显式支持的语言回退到en
|
||||
nonExplicitSupportedLngs: true,
|
||||
});
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
// 回退语言
|
||||
fallbackLng: 'en',
|
||||
// 调试模式(开发环境)
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
|
||||
export default i18n;
|
||||
// 语言资源
|
||||
resources: {
|
||||
en: {
|
||||
common: en,
|
||||
},
|
||||
zh: {
|
||||
common: zh,
|
||||
},
|
||||
},
|
||||
|
||||
// 命名空间配置
|
||||
defaultNS: 'common',
|
||||
ns: ['common'],
|
||||
|
||||
// 语言检测选项
|
||||
detection: languageDetectorOptions,
|
||||
|
||||
// 插值配置
|
||||
interpolation: {
|
||||
escapeValue: false, // React 已经默认防止XSS
|
||||
},
|
||||
|
||||
// 白名单支持的语言
|
||||
supportedLngs: ['en', 'zh'],
|
||||
|
||||
// 非显式支持的语言回退到en
|
||||
nonExplicitSupportedLngs: true,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"pleaseSelectInstallation": "Select Claude installation",
|
||||
"saveSelection": "Save Selection",
|
||||
"saving": "Saving...",
|
||||
"ccProjects": "CC Projects",
|
||||
"browseClaudeCodeSessions": "Browse your Claude Code sessions",
|
||||
"newClaudeCodeSession": "New Claude Code session",
|
||||
@@ -8,6 +11,7 @@
|
||||
"app": {
|
||||
"name": "Claudia",
|
||||
"welcome": "Welcome to Claudia",
|
||||
"tagline": "Powerful Claude Code session management tool",
|
||||
"loading": "Loading...",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
@@ -40,6 +44,21 @@
|
||||
"mcp": "MCP Manager",
|
||||
"about": "About"
|
||||
},
|
||||
"welcome": {
|
||||
"agentManagement": "Agent Management",
|
||||
"agentManagementDesc": "Create and manage intelligent agents",
|
||||
"projectManagement": "Project Management",
|
||||
"projectManagementDesc": "Browse and manage project sessions",
|
||||
"usageStatistics": "Usage Statistics",
|
||||
"usageStatisticsDesc": "View usage and statistics",
|
||||
"mcpBroker": "MCP Broker",
|
||||
"mcpBrokerDesc": "Manage MCP servers",
|
||||
"claudeMd": "CLAUDE.md",
|
||||
"claudeMdDesc": "Edit Claude configuration files",
|
||||
"settings": "Settings",
|
||||
"settingsDesc": "App settings and configuration",
|
||||
"quickStartSession": "Quick Start New Session"
|
||||
},
|
||||
"projects": {
|
||||
"title": "Projects",
|
||||
"noProjects": "No projects found",
|
||||
@@ -58,8 +77,24 @@
|
||||
"editAgent": "Edit Agent",
|
||||
"deleteAgent": "Delete Agent",
|
||||
"executeAgent": "Execute Agent",
|
||||
"agentName": "Agent Name",
|
||||
"agentNameRequired": "Agent name is required",
|
||||
"agentIcon": "Agent Icon",
|
||||
"chooseAnIcon": "Choose an icon",
|
||||
"searchIcons": "Search icons...",
|
||||
"noIconsFound": "No icons found for",
|
||||
"clickToSelect": "Click an icon to select",
|
||||
"iconsAvailable": "icons available",
|
||||
"iconCategories": {
|
||||
"interfaceNavigation": "Interface & Navigation",
|
||||
"developmentTech": "Development & Tech",
|
||||
"businessFinance": "Business & Finance",
|
||||
"creativeDesign": "Creative & Design",
|
||||
"natureScience": "Nature & Science",
|
||||
"gamingEntertainment": "Gaming & Entertainment",
|
||||
"communication": "Communication",
|
||||
"miscellaneous": "Miscellaneous"
|
||||
},
|
||||
"systemPrompt": "System Prompt",
|
||||
"systemPromptRequired": "System prompt is required",
|
||||
"defaultTask": "Default Task",
|
||||
@@ -110,7 +145,10 @@
|
||||
"noAgentsAvailable": "No agents available",
|
||||
"availableAgents": "Available Agents",
|
||||
"runningAgents": "Running Agents",
|
||||
"createFirstAgentToGetStarted": "Create your first agent to get started"
|
||||
"createFirstAgentToGetStarted": "Create your first agent to get started",
|
||||
"noRunningAgents": "No running agents",
|
||||
"agentExecutionsWillAppear": "Agent executions will appear here when started",
|
||||
"view": "View"
|
||||
},
|
||||
"slashCommands": {
|
||||
"slashCommands": "Slash Commands",
|
||||
@@ -131,7 +169,46 @@
|
||||
"hooksConfiguration": "Hooks Configuration",
|
||||
"configureShellCommands": "Configure shell commands to execute at various points in Claude Code's lifecycle.",
|
||||
"localSettingsNote": " These settings are not committed to version control.",
|
||||
"unsavedChanges": "You have unsaved changes. Click Save to persist them."
|
||||
"unsavedChanges": "You have unsaved changes. Click Save to persist them.",
|
||||
"preToolUse": "Pre Tool Use",
|
||||
"postToolUse": "Post Tool Use",
|
||||
"notification": "Notification",
|
||||
"stop": "Stop",
|
||||
"subagentStop": "Subagent Stop",
|
||||
"runsBeforeToolCalls": "Runs before tool calls, can block and provide feedback",
|
||||
"runsAfterToolCompletion": "Runs after successful tool completion",
|
||||
"customizesNotifications": "Customizes notifications when Claude needs attention",
|
||||
"runsWhenClaudeFinishes": "Runs when Claude finishes responding",
|
||||
"runsWhenSubagentFinishes": "Runs when a Claude subagent (Task) finishes",
|
||||
"noHooksConfigured": "No hooks configured for this event",
|
||||
"userScope": "User Scope",
|
||||
"projectScope": "Project Scope",
|
||||
"localScope": "Local Scope",
|
||||
"templates": "Templates",
|
||||
"addHook": "Add Hook",
|
||||
"addAnotherMatcher": "Add Another Matcher",
|
||||
"addAnotherCommand": "Add Another Command",
|
||||
"pattern": "Pattern",
|
||||
"commonPatterns": "Common patterns",
|
||||
"commands": "Commands",
|
||||
"addCommand": "Add Command",
|
||||
"hookTemplates": "Hook Templates",
|
||||
"quickStartTemplates": "Quick start with preconfigured templates",
|
||||
"validationErrors": "Validation Errors",
|
||||
"securityWarnings": "Security Warnings",
|
||||
"saving": "Saving...",
|
||||
"save": "Save",
|
||||
"loadingHooksConfiguration": "Loading hooks configuration...",
|
||||
"toolNamePatternTooltip": "Tool name pattern (regex supported). Leave empty to match all tools.",
|
||||
"seconds": "seconds",
|
||||
"timeout": "Timeout",
|
||||
"command": "Command",
|
||||
"block": "Block",
|
||||
"addCondition": "Add Condition",
|
||||
"condition": "Condition",
|
||||
"matchesRegex": "Matches regex",
|
||||
"message": "Message",
|
||||
"enterShellCommand": "Enter shell command..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -195,13 +272,29 @@
|
||||
"chatRetention": "Chat Transcript Retention (days)",
|
||||
"chatRetentionDesc": "How long to retain chat transcripts locally (default: 30 days)",
|
||||
"claudeCodeInstallation": "Claude Code Installation",
|
||||
"choosePreferredInstallation": "Choose your preferred Claude Code installation.",
|
||||
"loadingAvailableInstallations": "Loading available installations...",
|
||||
"errorLoadingInstallations": "Error loading installations",
|
||||
"availableInstallations": "Available Installations",
|
||||
"choosePreferredInstallation": "Choose your preferred Claude Code installation.",
|
||||
"loadingAvailableInstallations": "Loading available installations...",
|
||||
"errorLoadingInstallations": "Error loading installations",
|
||||
"availableInstallations": "Available Installations",
|
||||
"claudeCodeInstallationDesc": "Select which Claude Code installation to use.",
|
||||
"binaryPathChanged": "⚠️ Claude binary path has been changed. Remember to save your settings."
|
||||
},
|
||||
"placeholders": {
|
||||
"envVarKey": "KEY",
|
||||
"envVarValue": "value",
|
||||
"allowRuleExample": "e.g., Bash(npm run test:*)",
|
||||
"denyRuleExample": "e.g., Bash(curl:*)",
|
||||
"apiKeyHelperPath": "/path/to/generate_api_key.sh"
|
||||
},
|
||||
"system": "System",
|
||||
"custom": "Custom",
|
||||
"systemInstallations": "System Installations",
|
||||
"customInstallations": "Custom Installations",
|
||||
"selectedInstallation": "Selected Installation",
|
||||
"path": "Path",
|
||||
"source": "Source",
|
||||
"version": "Version",
|
||||
"versionUnknown": "Version unknown",
|
||||
"permissions": {
|
||||
"permissionRules": "Permission Rules",
|
||||
"permissionRulesDesc": "Control which tools Claude Code can use without manual approval",
|
||||
@@ -257,8 +350,8 @@
|
||||
"analyticsEnabled": "Analytics enabled",
|
||||
"analyticsDisabled": "Analytics disabled",
|
||||
"allDataDeleted": "All analytics data deleted",
|
||||
"helpImproveClaudia": "Help Improve Claudia",
|
||||
"collectAnonymousData": "We'd like to collect anonymous usage data to improve your experience.",
|
||||
"helpImproveClaudia": "Help improve Claudia",
|
||||
"collectAnonymousData": "We collect anonymous usage data to improve your experience. No personal data is collected.",
|
||||
"featureUsageDesc": "Feature usage (which tools and commands you use)",
|
||||
"performanceMetricsDesc": "Performance metrics (app speed and reliability)",
|
||||
"errorReportsDesc": "Error reports (to fix bugs and improve stability)",
|
||||
@@ -289,6 +382,7 @@
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP Server Management",
|
||||
"servers": "Servers",
|
||||
"addServer": "Add Server",
|
||||
"serverName": "Server Name",
|
||||
"serverCommand": "Server Command",
|
||||
@@ -298,7 +392,72 @@
|
||||
"connectionFailed": "Connection failed",
|
||||
"importFromClaude": "Import from Claude Desktop",
|
||||
"exportConfig": "Export Configuration",
|
||||
"noServers": "No MCP servers configured"
|
||||
"noServers": "No MCP servers configured",
|
||||
"importExport": "Import & Export",
|
||||
"importExportDescription": "Import MCP servers from other sources or export your configuration",
|
||||
"importScope": "Import Scope",
|
||||
"localProjectOnly": "Local (this project only)",
|
||||
"projectShared": "Project (shared via .mcp.json)",
|
||||
"userAllProjects": "User (all projects)",
|
||||
"chooseImportLocation": "Choose where to save imported servers from JSON files",
|
||||
"importFromClaudeDesktop": "Import from Claude Desktop",
|
||||
"importFromClaudeDesktopDescription": "Automatically imports all MCP servers from Claude Desktop. Installs to user scope (available across all projects).",
|
||||
"importing": "Importing...",
|
||||
"importFromJSON": "Import from JSON",
|
||||
"importFromJSONDescription": "Import server configuration from a JSON file",
|
||||
"chooseJSONFile": "Choose JSON File",
|
||||
"exportConfiguration": "Export Configuration",
|
||||
"exportConfigurationDescription": "Export your MCP server configuration",
|
||||
"exportComingSoon": "Export (Coming Soon)",
|
||||
"useClaudeCodeAsMCPServer": "Use Claude Code as MCP Server",
|
||||
"useClaudeCodeAsMCPServerDescription": "Start Claude Code as an MCP server that other applications can connect to",
|
||||
"startMCPServer": "Start MCP Server",
|
||||
"jsonFormatExamples": "JSON Format Examples",
|
||||
"singleServer": "Single server",
|
||||
"multipleServers": "Multiple servers (.mcp.json format)",
|
||||
"environmentVariablesCount": "Environment variables: {{count}}",
|
||||
"serversCount": "{{count}} servers configured",
|
||||
"mcpServerAdded": "MCP server added successfully!",
|
||||
"serverRemovedSuccess": "Server \"{{name}}\" removed successfully!",
|
||||
"importedServersSuccess": "Successfully imported {{count}} servers!",
|
||||
"importedServersFailed": "Imported {{imported}} servers, {{failed}} failed",
|
||||
"loadMcpServersFailed": "Failed to load MCP servers. Make sure Claude Code is installed.",
|
||||
"addMcpServer": "Add MCP Server",
|
||||
"configureNewMcpServer": "Configure a new Model Context Protocol server",
|
||||
"serverNameRequired": "Server name is required",
|
||||
"commandRequired": "Command is required",
|
||||
"urlRequired": "URL is required",
|
||||
"failedToAddServer": "Failed to add server",
|
||||
"environmentVariables": "Environment Variables",
|
||||
"addVariable": "Add Variable",
|
||||
"command": "Command",
|
||||
"argumentsOptional": "Arguments (optional)",
|
||||
"scope": "Scope",
|
||||
"localProjectOnly": "Local (this project only)",
|
||||
"projectSharedViaMcp": "Project (shared via .mcp.json)",
|
||||
"addingServer": "Adding Server...",
|
||||
"addStdioServer": "Add Stdio Server",
|
||||
"url": "URL",
|
||||
"addSseServer": "Add SSE Server",
|
||||
"exampleCommands": "Example Commands",
|
||||
"uniqueNameToIdentify": "A unique name to identify this server",
|
||||
"commandToExecuteServer": "The command to execute the server",
|
||||
"spaceSeparatedArgs": "Space-separated command arguments",
|
||||
"sseEndpointUrl": "The SSE endpoint URL",
|
||||
"running": "Running",
|
||||
"showFull": "Show full",
|
||||
"hide": "Hide",
|
||||
"copied": "Copied!",
|
||||
"copy": "Copy",
|
||||
"arguments": "Arguments",
|
||||
"configuredServers": "Configured Servers",
|
||||
"serversConfigured": "configured",
|
||||
"refresh": "Refresh",
|
||||
"noMcpServersConfigured": "No MCP servers configured",
|
||||
"addServerToGetStarted": "Add a server to get started with Model Context Protocol",
|
||||
"localProjectSpecific": "Local (Project-specific)",
|
||||
"projectSharedMcp": "Project (Shared via .mcp.json)",
|
||||
"userAllProjects": "User (All projects)"
|
||||
},
|
||||
"usage": {
|
||||
"title": "Usage Dashboard",
|
||||
@@ -398,7 +557,9 @@
|
||||
"addServerToGetStarted": "Add a server to get started with Model Context Protocol",
|
||||
"localProjectSpecific": "Local (Project-specific)",
|
||||
"projectSharedMcp": "Project (Shared via .mcp.json)",
|
||||
"userAllProjects": "User (All projects)",
|
||||
"userAllProjects": "User (All projects)"
|
||||
},
|
||||
"messages": {
|
||||
"welcomeToClaudia": "Welcome to Claudia",
|
||||
"backToHome": "Back to Home",
|
||||
"noProjectsFound": "No projects found in ~/.claude/projects",
|
||||
@@ -421,7 +582,41 @@
|
||||
"loadFileFailed": "Failed to load CLAUDE.md file",
|
||||
"editProjectSpecificPrompt": "Edit project-specific Claude Code system prompt",
|
||||
"maximumTabsReached": "Maximum number of tabs ({{max}}) reached",
|
||||
"saving": "Saving..."
|
||||
"saving": "Saving...",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"operationFailed": "Operation failed",
|
||||
"confirmAction": "Are you sure you want to perform this action?",
|
||||
"unsavedChanges": "You have unsaved changes",
|
||||
"networkError": "Network error occurred",
|
||||
"unknownError": "Unknown error occurred",
|
||||
"claudeCodeNotFound": "Claude Code not found",
|
||||
"selectClaudeInstallation": "Select Claude Installation",
|
||||
"installClaudeCode": "Install Claude Code",
|
||||
"noTabsOpen": "No tabs open",
|
||||
"clickPlusToStartChat": "Click the + button to start a new chat",
|
||||
"noAgentRunIdSpecified": "No agent run ID specified",
|
||||
"noClaudeFileIdSpecified": "No Claude file ID specified",
|
||||
"claudeFileEditorNotImplemented": "Claude file editor not yet implemented in tabs",
|
||||
"noAgentDataSpecified": "No agent data specified",
|
||||
"importAgentComingSoon": "Import agent functionality coming soon...",
|
||||
"unknownTabType": "Unknown tab type",
|
||||
"letClaudeDecide": "Let Claude decide",
|
||||
"basicReasoning": "Basic reasoning",
|
||||
"deeperAnalysis": "Deeper analysis",
|
||||
"extensiveReasoning": "Extensive reasoning",
|
||||
"maximumAnalysis": "Maximum analysis",
|
||||
"typeYourPromptHere": "Type your prompt here...",
|
||||
"dropImagesHere": "Drop images here...",
|
||||
"askClaudeAnything": "Ask Claude anything...",
|
||||
"usage": "Usage",
|
||||
"mcpServersTitle": "MCP Servers",
|
||||
"claudemdTitle": "CLAUDE.md",
|
||||
"runAgent": "Run: {{name}}",
|
||||
"createAgent": "Create Agent",
|
||||
"importAgent": "Import Agent",
|
||||
"unsavedChangesCloseConfirm": "Tab \"{{title}}\" has unsaved changes. Close anyway?",
|
||||
"session": "Session"
|
||||
},
|
||||
"checkpoint": {
|
||||
"title": "Checkpoints",
|
||||
@@ -477,41 +672,35 @@
|
||||
"minLength": "Minimum {{count}} characters required",
|
||||
"maxLength": "Maximum {{count}} characters allowed"
|
||||
},
|
||||
"messages": {
|
||||
"saveSuccess": "Saved successfully",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"operationFailed": "Operation failed",
|
||||
"confirmAction": "Are you sure you want to perform this action?",
|
||||
"unsavedChanges": "You have unsaved changes",
|
||||
"networkError": "Network error occurred",
|
||||
"unknownError": "Unknown error occurred",
|
||||
"claudeCodeNotFound": "Claude Code not found",
|
||||
"selectClaudeInstallation": "Select Claude Installation",
|
||||
"installClaudeCode": "Install Claude Code",
|
||||
"noTabsOpen": "No tabs open",
|
||||
"clickPlusToStartChat": "Click the + button to start a new chat",
|
||||
"noAgentRunIdSpecified": "No agent run ID specified",
|
||||
"noClaudeFileIdSpecified": "No Claude file ID specified",
|
||||
"claudeFileEditorNotImplemented": "Claude file editor not yet implemented in tabs",
|
||||
"noAgentDataSpecified": "No agent data specified",
|
||||
"importAgentComingSoon": "Import agent functionality coming soon...",
|
||||
"unknownTabType": "Unknown tab type",
|
||||
"letClaudeDecide": "Let Claude decide",
|
||||
"basicReasoning": "Basic reasoning",
|
||||
"deeperAnalysis": "Deeper analysis",
|
||||
"extensiveReasoning": "Extensive reasoning",
|
||||
"maximumAnalysis": "Maximum analysis",
|
||||
"typeYourPromptHere": "Type your prompt here...",
|
||||
"dropImagesHere": "Drop images here...",
|
||||
"askClaudeAnything": "Ask Claude anything...",
|
||||
"usage": "Usage",
|
||||
"mcpServersTitle": "MCP Servers",
|
||||
"claudemdTitle": "CLAUDE.md",
|
||||
"runAgent": "Run: {{name}}",
|
||||
"createAgent": "Create Agent",
|
||||
"importAgent": "Import Agent",
|
||||
"unsavedChangesCloseConfirm": "Tab \"{{title}}\" has unsaved changes. Close anyway?",
|
||||
"session": "Session"
|
||||
"errorBoundary": {
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"errorOccurred": "An error occurred while rendering this component.",
|
||||
"errorDetails": "Error details",
|
||||
"tryAgain": "Try again"
|
||||
},
|
||||
"webview": {
|
||||
"preview": "Preview",
|
||||
"exitFullScreen": "Exit full screen (ESC)",
|
||||
"enterFullScreen": "Enter full screen",
|
||||
"enterUrl": "Enter URL...",
|
||||
"loadingPreview": "Loading preview...",
|
||||
"failedToLoad": "Failed to load preview",
|
||||
"pageCouldNotLoad": "The page could not be loaded. Please check the URL and try again.",
|
||||
"invalidUrl": "Invalid URL",
|
||||
"enterUrlToPreview": "Enter a URL to preview",
|
||||
"enterUrlDescription": "Enter a URL in the address bar above to preview a website.",
|
||||
"tryEntering": "Try entering",
|
||||
"orAnyOtherUrl": "or any other URL",
|
||||
"unknownTime": "Unknown time",
|
||||
"sessions": "Sessions",
|
||||
"selectProjectDirectory": "Select Project Directory",
|
||||
"failedToSendPrompt": "Failed to send prompt",
|
||||
"sessionOutputCopiedJsonl": "Session output copied as JSONL",
|
||||
"sessionOutputCopiedMarkdown": "Session output copied as Markdown",
|
||||
"failedToCopy": "Failed to copy",
|
||||
"forkSession": "Fork Session from Checkpoint",
|
||||
"newSessionName": "New Session Name",
|
||||
"enterNameForForkedSession": "Enter a name for the forked session"
|
||||
},
|
||||
"input": {
|
||||
"pressEnterToSend": "Press Enter to send, Shift+Enter for new line",
|
||||
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"pleaseSelectInstallation": "请选择 Claude 安装",
|
||||
"saveSelection": "保存选择",
|
||||
"saving": "保存中...",
|
||||
"ccProjects": "Claude Code 项目",
|
||||
"browseClaudeCodeSessions": "浏览您的 Claude Code 会话",
|
||||
"newClaudeCodeSession": "新建 Claude Code 会话",
|
||||
@@ -8,6 +11,7 @@
|
||||
"app": {
|
||||
"name": "Claudia",
|
||||
"welcome": "欢迎使用 Claudia",
|
||||
"tagline": "强大的 Claude Code 会话管理工具",
|
||||
"loading": "加载中...",
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
@@ -37,6 +41,21 @@
|
||||
"mcp": "MCP 管理器",
|
||||
"about": "关于"
|
||||
},
|
||||
"welcome": {
|
||||
"agentManagement": "Agent 管理",
|
||||
"agentManagementDesc": "创建和管理智能 Agent",
|
||||
"projectManagement": "项目管理",
|
||||
"projectManagementDesc": "浏览和管理项目会话",
|
||||
"usageStatistics": "使用统计",
|
||||
"usageStatisticsDesc": "查看使用情况和统计",
|
||||
"mcpBroker": "MCP 管理",
|
||||
"mcpBrokerDesc": "管理 MCP 服务器",
|
||||
"claudeMd": "CLAUDE.md",
|
||||
"claudeMdDesc": "编辑 Claude 配置文件",
|
||||
"settings": "设置",
|
||||
"settingsDesc": "应用设置和配置",
|
||||
"quickStartSession": "快速开始新会话"
|
||||
},
|
||||
"projects": {
|
||||
"title": "项目",
|
||||
"noProjects": "未找到项目",
|
||||
@@ -55,8 +74,24 @@
|
||||
"editAgent": "编辑智能体",
|
||||
"deleteAgent": "删除智能体",
|
||||
"executeAgent": "执行智能体",
|
||||
"agentName": "智能体名称",
|
||||
"agentNameRequired": "代理名称为必填项",
|
||||
"agentIcon": "智能体图标",
|
||||
"chooseAnIcon": "选择图标",
|
||||
"searchIcons": "搜索图标...",
|
||||
"noIconsFound": "未找到图标",
|
||||
"clickToSelect": "点击选择图标",
|
||||
"iconsAvailable": "个可用图标",
|
||||
"iconCategories": {
|
||||
"interfaceNavigation": "界面与导航",
|
||||
"developmentTech": "开发与技术",
|
||||
"businessFinance": "商业与金融",
|
||||
"creativeDesign": "创意与设计",
|
||||
"natureScience": "自然与科学",
|
||||
"gamingEntertainment": "游戏与娱乐",
|
||||
"communication": "通讯",
|
||||
"miscellaneous": "其他"
|
||||
},
|
||||
"systemPrompt": "系统提示",
|
||||
"systemPromptRequired": "系统提示为必填项",
|
||||
"defaultTask": "默认任务",
|
||||
@@ -107,7 +142,10 @@
|
||||
"noAgentsAvailable": "无可用代理",
|
||||
"availableAgents": "可用代理",
|
||||
"runningAgents": "运行中的代理",
|
||||
"createFirstAgentToGetStarted": "创建您的第一个代理开始使用"
|
||||
"createFirstAgentToGetStarted": "创建您的第一个代理开始使用",
|
||||
"noRunningAgents": "无运行中的智能体",
|
||||
"agentExecutionsWillAppear": "智能体执行将在启动后显示在这里",
|
||||
"view": "查看"
|
||||
},
|
||||
"slashCommands": {
|
||||
"slashCommands": "斜杠命令",
|
||||
@@ -128,7 +166,46 @@
|
||||
"hooksConfiguration": "钩子配置",
|
||||
"configureShellCommands": "配置在 Claude Code 生命周期的各个阶段执行的 shell 命令。",
|
||||
"localSettingsNote": " 这些设置不会提交到版本控制中。",
|
||||
"unsavedChanges": "您有未保存的更改。点击保存以持久化它们。"
|
||||
"unsavedChanges": "您有未保存的更改。点击保存以持久化它们。",
|
||||
"preToolUse": "工具使用前",
|
||||
"postToolUse": "工具使用后",
|
||||
"notification": "通知",
|
||||
"stop": "停止",
|
||||
"subagentStop": "子代理停止",
|
||||
"runsBeforeToolCalls": "在工具调用前运行,可以阻止并提供反馈",
|
||||
"runsAfterToolCompletion": "在工具成功完成后运行",
|
||||
"customizesNotifications": "自定义 Claude 需要注意时的通知",
|
||||
"runsWhenClaudeFinishes": "在 Claude 完成响应时运行",
|
||||
"runsWhenSubagentFinishes": "在 Claude 子代理(任务)完成时运行",
|
||||
"noHooksConfigured": "此事件未配置钩子",
|
||||
"userScope": "用户范围",
|
||||
"projectScope": "项目范围",
|
||||
"localScope": "本地范围",
|
||||
"templates": "模板",
|
||||
"addHook": "添加钩子",
|
||||
"addAnotherMatcher": "添加另一个匹配器",
|
||||
"addAnotherCommand": "添加另一个命令",
|
||||
"pattern": "模式",
|
||||
"commonPatterns": "常用模式",
|
||||
"commands": "命令",
|
||||
"addCommand": "添加命令",
|
||||
"hookTemplates": "钩子模板",
|
||||
"quickStartTemplates": "使用预配置模板快速开始",
|
||||
"validationErrors": "验证错误",
|
||||
"securityWarnings": "安全警告",
|
||||
"saving": "保存中...",
|
||||
"save": "保存",
|
||||
"loadingHooksConfiguration": "加载钩子配置中...",
|
||||
"toolNamePatternTooltip": "工具名称模式(支持正则表达式)。留空以匹配所有工具。",
|
||||
"seconds": "秒",
|
||||
"timeout": "超时",
|
||||
"command": "命令",
|
||||
"block": "阻止",
|
||||
"addCondition": "添加条件",
|
||||
"condition": "条件",
|
||||
"matchesRegex": "匹配正则表达式",
|
||||
"message": "消息",
|
||||
"enterShellCommand": "输入 shell 命令..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
@@ -192,10 +269,10 @@
|
||||
"chatRetention": "聊天记录保留期 (天)",
|
||||
"chatRetentionDesc": "本地保留聊天记录的时长(默认:30 天)",
|
||||
"claudeCodeInstallation": "Claude Code 安装",
|
||||
"choosePreferredInstallation": "选择您偏好的 Claude Code 安装。",
|
||||
"loadingAvailableInstallations": "正在加载可用安装...",
|
||||
"errorLoadingInstallations": "加载安装时出错",
|
||||
"availableInstallations": "可用安装",
|
||||
"choosePreferredInstallation": "选择您偏好的 Claude Code 安装。",
|
||||
"loadingAvailableInstallations": "正在加载可用安装...",
|
||||
"errorLoadingInstallations": "加载安装时出错",
|
||||
"availableInstallations": "可用安装",
|
||||
"claudeCodeInstallationDesc": "选择要使用的 Claude Code 安装。",
|
||||
"binaryPathChanged": "⚠️ Claude 二进制路径已更改。请记住保存您的设置。"
|
||||
},
|
||||
@@ -255,7 +332,7 @@
|
||||
"analyticsDisabled": "分析已禁用",
|
||||
"allDataDeleted": "所有分析数据已删除",
|
||||
"helpImproveClaudia": "帮助改善 Claudia",
|
||||
"collectAnonymousData": "我们希望收集匿名使用数据以改善您的使用体验。",
|
||||
"collectAnonymousData": "我们收集匿名使用数据以改善您的体验。不收集个人数据。",
|
||||
"featureUsageDesc": "功能使用(您使用的工具和命令)",
|
||||
"performanceMetricsDesc": "性能指标(应用速度和可靠性)",
|
||||
"errorReportsDesc": "错误报告(用于修复错误和提高稳定性)",
|
||||
@@ -282,10 +359,20 @@
|
||||
"allowRuleExample": "例如:Bash(npm run test:*)",
|
||||
"denyRuleExample": "例如:Bash(curl:*)",
|
||||
"apiKeyHelperPath": "/path/to/generate_api_key.sh"
|
||||
}
|
||||
},
|
||||
"system": "系统",
|
||||
"custom": "自定义",
|
||||
"systemInstallations": "系统安装",
|
||||
"customInstallations": "自定义安装",
|
||||
"selectedInstallation": "已选择的安装",
|
||||
"path": "路径",
|
||||
"source": "来源",
|
||||
"version": "版本",
|
||||
"versionUnknown": "版本未知"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP 服务器管理",
|
||||
"servers": "服务器",
|
||||
"addServer": "添加服务器",
|
||||
"serverName": "服务器名称",
|
||||
"serverCommand": "服务器命令",
|
||||
@@ -295,7 +382,71 @@
|
||||
"connectionFailed": "连接失败",
|
||||
"importFromClaude": "从 Claude Desktop 导入",
|
||||
"exportConfig": "导出配置",
|
||||
"noServers": "未配置 MCP 服务器"
|
||||
"noServers": "未配置 MCP 服务器",
|
||||
"importExport": "导入与导出",
|
||||
"importExportDescription": "从其他来源导入 MCP 服务器或导出您的配置",
|
||||
"importScope": "导入范围",
|
||||
"localProjectOnly": "本地(仅此项目)",
|
||||
"projectShared": "项目(通过 .mcp.json 共享)",
|
||||
"userAllProjects": "用户(所有项目)",
|
||||
"chooseImportLocation": "选择从 JSON 文件导入的服务器保存位置",
|
||||
"importFromClaudeDesktop": "从 Claude Desktop 导入",
|
||||
"importFromClaudeDesktopDescription": "自动导入 Claude Desktop 中的所有 MCP 服务器。安装到用户范围(所有项目可用)。",
|
||||
"importing": "导入中...",
|
||||
"importFromJSON": "从 JSON 导入",
|
||||
"importFromJSONDescription": "从 JSON 文件导入服务器配置",
|
||||
"chooseJSONFile": "选择 JSON 文件",
|
||||
"exportConfiguration": "导出配置",
|
||||
"exportConfigurationDescription": "导出您的 MCP 服务器配置",
|
||||
"exportComingSoon": "导出(即将推出)",
|
||||
"useClaudeCodeAsMCPServer": "将 Claude Code 用作 MCP 服务器",
|
||||
"useClaudeCodeAsMCPServerDescription": "启动 Claude Code 作为 MCP 服务器,其他应用程序可以连接到它",
|
||||
"startMCPServer": "启动 MCP 服务器",
|
||||
"jsonFormatExamples": "JSON 格式示例",
|
||||
"singleServer": "单个服务器",
|
||||
"multipleServers": "多个服务器(.mcp.json 格式)",
|
||||
"environmentVariablesCount": "环境变量:{{count}} 个",
|
||||
"serversCount": "已配置 {{count}} 个服务器",
|
||||
"mcpServerAdded": "MCP 服务器添加成功!",
|
||||
"serverRemovedSuccess": "服务器 \"{{name}}\" 删除成功!",
|
||||
"importedServersSuccess": "成功导入 {{count}} 个服务器!",
|
||||
"importedServersFailed": "导入 {{imported}} 个服务器,{{failed}} 个失败",
|
||||
"loadMcpServersFailed": "加载 MCP 服务器失败。请确保 Claude Code 已安装。",
|
||||
"addMcpServer": "添加 MCP 服务器",
|
||||
"configureNewMcpServer": "配置新的模型上下文协议服务器",
|
||||
"serverNameRequired": "服务器名称为必填项",
|
||||
"commandRequired": "命令为必填项",
|
||||
"urlRequired": "URL为必填项",
|
||||
"failedToAddServer": "添加服务器失败",
|
||||
"environmentVariables": "环境变量",
|
||||
"addVariable": "添加变量",
|
||||
"command": "命令",
|
||||
"argumentsOptional": "参数(可选)",
|
||||
"scope": "作用域",
|
||||
"localProjectOnly": "本地(仅此项目)",
|
||||
"projectSharedViaMcp": "项目(通过 .mcp.json 共享)",
|
||||
"addingServer": "添加服务器中...",
|
||||
"addStdioServer": "添加 Stdio 服务器",
|
||||
"url": "URL",
|
||||
"addSseServer": "添加 SSE 服务器",
|
||||
"exampleCommands": "示例命令",
|
||||
"uniqueNameToIdentify": "用于标识此服务器的唯一名称",
|
||||
"commandToExecuteServer": "执行服务器的命令",
|
||||
"spaceSeparatedArgs": "空格分隔的命令参数",
|
||||
"sseEndpointUrl": "SSE 端点 URL",
|
||||
"running": "运行中",
|
||||
"showFull": "显示全部",
|
||||
"hide": "隐藏",
|
||||
"copied": "已复制",
|
||||
"copy": "复制",
|
||||
"arguments": "参数",
|
||||
"configuredServers": "已配置的服务器",
|
||||
"serversConfigured": "个服务器已配置",
|
||||
"refresh": "刷新",
|
||||
"noMcpServersConfigured": "未配置 MCP 服务器",
|
||||
"addServerToGetStarted": "添加一个服务器开始使用模型上下文协议",
|
||||
"localProjectSpecific": "本地(项目特定)",
|
||||
"projectSharedMcp": "项目(通过 .mcp.json 共享)"
|
||||
},
|
||||
"usage": {
|
||||
"title": "用量仪表板",
|
||||
@@ -348,68 +499,9 @@
|
||||
"saveClaudemdFailed": "保存 CLAUDE.md 文件失败",
|
||||
"claudemdSavedSuccess": "CLAUDE.md 保存成功",
|
||||
"saveClaudemd": "保存 CLAUDE.md",
|
||||
"unsavedChangesConfirm": "您有未保存的更改。确定要离开吗?",
|
||||
"servers": "服务器",
|
||||
"addServer": "添加服务器",
|
||||
"importExport": "导入/导出",
|
||||
"mcpServerAdded": "MCP 服务器添加成功!",
|
||||
"serverRemovedSuccess": "服务器 \"{{name}}\" 删除成功!",
|
||||
"importedServersSuccess": "成功导入 {{count}} 个服务器!",
|
||||
"importedServersFailed": "导入 {{imported}} 个服务器,{{failed}} 个失败",
|
||||
"loadMcpServersFailed": "加载 MCP 服务器失败。请确保 Claude Code 已安装。",
|
||||
"addMcpServer": "添加 MCP 服务器",
|
||||
"configureNewMcpServer": "配置新的模型上下文协议服务器",
|
||||
"serverNameRequired": "服务器名称为必填项",
|
||||
"commandRequired": "命令为必填项",
|
||||
"urlRequired": "URL为必填项",
|
||||
"failedToAddServer": "添加服务器失败",
|
||||
"environmentVariables": "环境变量",
|
||||
"addVariable": "添加变量",
|
||||
"serverName": "服务器名称",
|
||||
"command": "命令",
|
||||
"argumentsOptional": "参数(可选)",
|
||||
"scope": "作用域",
|
||||
"localProjectOnly": "本地(仅此项目)",
|
||||
"projectSharedViaMcp": "项目(通过 .mcp.json 共享)",
|
||||
"addingServer": "添加服务器中...",
|
||||
"addStdioServer": "添加 Stdio 服务器",
|
||||
"url": "URL",
|
||||
"addSseServer": "添加 SSE 服务器",
|
||||
"exampleCommands": "示例命令",
|
||||
"uniqueNameToIdentify": "用于标识此服务器的唯一名称",
|
||||
"commandToExecuteServer": "执行服务器的命令",
|
||||
"spaceSeparatedArgs": "空格分隔的命令参数",
|
||||
"sseEndpointUrl": "SSE 端点 URL",
|
||||
"running": "运行中",
|
||||
"showFull": "显示全部",
|
||||
"hide": "隐藏",
|
||||
"copied": "已复制",
|
||||
"copy": "复制",
|
||||
"arguments": "参数",
|
||||
"environmentVariables": "环境变量",
|
||||
"configuredServers": "已配置的服务器",
|
||||
"serversConfigured": "个服务器已配置",
|
||||
"refresh": "刷新",
|
||||
"noMcpServersConfigured": "未配置 MCP 服务器",
|
||||
"addServerToGetStarted": "添加一个服务器开始使用模型上下文协议",
|
||||
"localProjectSpecific": "本地(项目特定)",
|
||||
"projectSharedMcp": "项目(通过 .mcp.json 共享)",
|
||||
"userAllProjects": "用户(所有项目)",
|
||||
"letClaudeDecide": "让 Claude 决定",
|
||||
"basicReasoning": "基础推理",
|
||||
"deeperAnalysis": "更深入的分析",
|
||||
"extensiveReasoning": "广泛推理",
|
||||
"maximumAnalysis": "最大化分析",
|
||||
"typeYourPromptHere": "在此输入您的提示...",
|
||||
"dropImagesHere": "在此拖放图像...",
|
||||
"askClaudeAnything": "向 Claude 提问任何问题...",
|
||||
"usage": "用量",
|
||||
"mcpServersTitle": "MCP 服务器",
|
||||
"claudemdTitle": "CLAUDE.md",
|
||||
"runAgent": "运行: {{name}}",
|
||||
"createAgent": "创建智能体",
|
||||
"importAgent": "导入智能体",
|
||||
"unsavedChangesCloseConfirm": "标签页 \"{{title}}\" 有未保存的更改。仍要关闭吗?",
|
||||
"unsavedChangesConfirm": "您有未保存的更改。确定要离开吗?"
|
||||
},
|
||||
"messages": {
|
||||
"welcomeToClaudia": "欢迎使用 Claudia",
|
||||
"backToHome": "返回首页",
|
||||
"noProjectsFound": "在 ~/.claude/projects 中未找到项目",
|
||||
@@ -440,7 +532,18 @@
|
||||
"loadFileFailed": "加载 CLAUDE.md 文件失败",
|
||||
"editProjectSpecificPrompt": "编辑项目特定的 Claude Code 系统提示",
|
||||
"maximumTabsReached": "已达到最大标签页数量 ({{max}})",
|
||||
"saving": "保存中..."
|
||||
"saving": "保存中...",
|
||||
"saveSuccess": "保存成功",
|
||||
"deleteSuccess": "删除成功",
|
||||
"operationFailed": "操作失败",
|
||||
"confirmAction": "确认要执行此操作吗?",
|
||||
"unsavedChanges": "您有未保存的更改",
|
||||
"networkError": "网络错误",
|
||||
"unknownError": "未知错误",
|
||||
"claudeCodeNotFound": "未找到 Claude Code",
|
||||
"selectClaudeInstallation": "选择 Claude 安装",
|
||||
"installClaudeCode": "安装 Claude Code",
|
||||
"session": "会话"
|
||||
},
|
||||
"checkpoint": {
|
||||
"title": "检查点",
|
||||
@@ -451,11 +554,6 @@
|
||||
"checkpointMessage": "检查点消息",
|
||||
"timeline": "时间线",
|
||||
"diff": "差异",
|
||||
"deleteCheckpoint": "删除检查点",
|
||||
"checkpointName": "检查点名称",
|
||||
"checkpointMessage": "检查点消息",
|
||||
"timeline": "时间线",
|
||||
"diff": "差异",
|
||||
"noCheckpoints": "未找到检查点",
|
||||
"checkpointSettingsTitle": "检查点设置",
|
||||
"experimentalFeature": "实验性功能",
|
||||
@@ -501,18 +599,35 @@
|
||||
"minLength": "至少需要 {{count}} 个字符",
|
||||
"maxLength": "最多允许 {{count}} 个字符"
|
||||
},
|
||||
"messages": {
|
||||
"saveSuccess": "保存成功",
|
||||
"deleteSuccess": "删除成功",
|
||||
"operationFailed": "操作失败",
|
||||
"confirmAction": "确认要执行此操作吗?",
|
||||
"unsavedChanges": "您有未保存的更改",
|
||||
"networkError": "网络错误",
|
||||
"unknownError": "未知错误",
|
||||
"claudeCodeNotFound": "未找到 Claude Code",
|
||||
"selectClaudeInstallation": "选择 Claude 安装",
|
||||
"installClaudeCode": "安装 Claude Code",
|
||||
"session": "会话"
|
||||
"errorBoundary": {
|
||||
"somethingWentWrong": "出错了",
|
||||
"errorOccurred": "渲染此组件时发生错误。",
|
||||
"errorDetails": "错误详情",
|
||||
"tryAgain": "重试"
|
||||
},
|
||||
"webview": {
|
||||
"preview": "预览",
|
||||
"exitFullScreen": "退出全屏 (ESC)",
|
||||
"enterFullScreen": "进入全屏",
|
||||
"enterUrl": "输入 URL...",
|
||||
"loadingPreview": "加载预览中...",
|
||||
"failedToLoad": "加载预览失败",
|
||||
"pageCouldNotLoad": "页面无法加载。请检查 URL 并重试。",
|
||||
"invalidUrl": "无效的 URL",
|
||||
"enterUrlToPreview": "输入 URL 进行预览",
|
||||
"enterUrlDescription": "在上方地址栏输入 URL 以预览网站。",
|
||||
"tryEntering": "尝试输入",
|
||||
"orAnyOtherUrl": "或任何其他 URL",
|
||||
"unknownTime": "未知时间",
|
||||
"sessions": "会话",
|
||||
"selectProjectDirectory": "选择项目目录",
|
||||
"failedToSendPrompt": "发送提示失败",
|
||||
"sessionOutputCopiedJsonl": "会话输出已复制为 JSONL",
|
||||
"sessionOutputCopiedMarkdown": "会话输出已复制为 Markdown",
|
||||
"failedToCopy": "复制失败",
|
||||
"forkSession": "从检查点分叉会话",
|
||||
"newSessionName": "新会话名称",
|
||||
"enterNameForForkedSession": "输入分叉会话的名称"
|
||||
},
|
||||
"input": {
|
||||
"pressEnterToSend": "按 Enter 发送,Shift+Enter 换行",
|
||||
|
@@ -10,6 +10,21 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Custom animations */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation utilities */
|
||||
.animate-spin-slow {
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
/* Dark theme configuration */
|
||||
@theme {
|
||||
/* Colors */
|
||||
@@ -45,8 +60,8 @@
|
||||
--radius-xl: 1rem;
|
||||
|
||||
/* Fonts */
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
--font-sans: "Maple Mono", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
--font-mono: "Maple Mono", ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
|
||||
/* Transitions */
|
||||
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -56,26 +71,26 @@
|
||||
/* Theme Variations */
|
||||
/* Default is dark theme - already defined above */
|
||||
|
||||
/* Light Theme */
|
||||
/* Light Theme - Enhanced Contrast */
|
||||
.theme-light {
|
||||
--color-background: oklch(0.98 0.01 240);
|
||||
--color-foreground: oklch(0.12 0.01 240);
|
||||
--color-card: oklch(0.96 0.01 240);
|
||||
--color-card-foreground: oklch(0.12 0.01 240);
|
||||
--color-popover: oklch(0.98 0.01 240);
|
||||
--color-popover-foreground: oklch(0.12 0.01 240);
|
||||
--color-primary: oklch(0.12 0.01 240);
|
||||
--color-primary-foreground: oklch(0.98 0.01 240);
|
||||
--color-secondary: oklch(0.94 0.01 240);
|
||||
--color-secondary-foreground: oklch(0.12 0.01 240);
|
||||
--color-muted: oklch(0.94 0.01 240);
|
||||
--color-background: oklch(0.99 0.005 240);
|
||||
--color-foreground: oklch(0.08 0.01 240);
|
||||
--color-card: oklch(0.97 0.005 240);
|
||||
--color-card-foreground: oklch(0.08 0.01 240);
|
||||
--color-popover: oklch(0.99 0.005 240);
|
||||
--color-popover-foreground: oklch(0.08 0.01 240);
|
||||
--color-primary: oklch(0.08 0.01 240);
|
||||
--color-primary-foreground: oklch(0.99 0.005 240);
|
||||
--color-secondary: oklch(0.95 0.005 240);
|
||||
--color-secondary-foreground: oklch(0.08 0.01 240);
|
||||
--color-muted: oklch(0.95 0.005 240);
|
||||
--color-muted-foreground: oklch(0.45 0.01 240);
|
||||
--color-accent: oklch(0.94 0.01 240);
|
||||
--color-accent-foreground: oklch(0.12 0.01 240);
|
||||
--color-accent: oklch(0.95 0.005 240);
|
||||
--color-accent-foreground: oklch(0.08 0.01 240);
|
||||
--color-destructive: oklch(0.6 0.2 25);
|
||||
--color-destructive-foreground: oklch(0.98 0.01 240);
|
||||
--color-border: oklch(0.90 0.01 240);
|
||||
--color-input: oklch(0.90 0.01 240);
|
||||
--color-destructive-foreground: oklch(0.99 0.005 240);
|
||||
--color-border: oklch(0.85 0.005 240);
|
||||
--color-input: oklch(0.85 0.005 240);
|
||||
--color-ring: oklch(0.52 0.015 240);
|
||||
|
||||
/* Additional colors for status messages */
|
||||
@@ -155,6 +170,11 @@ body {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--font-sans);
|
||||
/* Optimize Maple Mono rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga" 1, "calt" 1;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Placeholder text styling */
|
||||
@@ -591,6 +611,12 @@ button:focus-visible,
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for shimmer effect */
|
||||
.animate-shimmer {
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.shimmer-hover {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@@ -776,4 +802,34 @@ code::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
.image-move-to-input {
|
||||
animation: moveToInput 0.8s ease-in-out forwards;
|
||||
}
|
||||
|
||||
/* Glass Morphism Effects - Enhanced for better contrast */
|
||||
.glass-panel {
|
||||
background: var(--color-background);
|
||||
background: color-mix(in srgb, var(--color-background) 98%, transparent);
|
||||
backdrop-filter: blur(12px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(180%);
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent);
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgb(0 0 0 / 0.1),
|
||||
0 1px 2px -1px rgb(0 0 0 / 0.1),
|
||||
inset 0 1px 0 0 rgb(255 255 255 / 0.05);
|
||||
}
|
||||
|
||||
.theme-light .glass-panel {
|
||||
background: color-mix(in srgb, var(--color-background) 85%, transparent);
|
||||
box-shadow:
|
||||
0 2px 8px 0 rgb(0 0 0 / 0.08),
|
||||
0 1px 3px -1px rgb(0 0 0 / 0.06),
|
||||
inset 0 1px 0 0 rgb(255 255 255 / 0.8);
|
||||
}
|
||||
|
||||
/* Enhanced text contrast for light theme */
|
||||
.theme-light .text-muted-foreground {
|
||||
color: oklch(0.45 0.01 240);
|
||||
}
|
||||
|
||||
.theme-light .hover\:text-muted-foreground:hover {
|
||||
color: oklch(0.40 0.01 240);
|
||||
}
|
Reference in New Issue
Block a user