美化页面
This commit is contained in:
214
src-tauri/src/claude_config.rs
Normal file
214
src-tauri/src/claude_config.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use dirs::home_dir;
|
||||||
|
use crate::commands::relay_stations::RelayStation;
|
||||||
|
|
||||||
|
/// Claude 配置文件结构
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ClaudeConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub env: ClaudeEnv,
|
||||||
|
#[serde(default)]
|
||||||
|
pub permissions: Option<ClaudePermissions>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub model: Option<String>,
|
||||||
|
#[serde(rename = "apiKeyHelper")]
|
||||||
|
pub api_key_helper: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub other: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct ClaudeEnv {
|
||||||
|
#[serde(rename = "ANTHROPIC_AUTH_TOKEN")]
|
||||||
|
pub anthropic_auth_token: Option<String>,
|
||||||
|
#[serde(rename = "ANTHROPIC_BASE_URL")]
|
||||||
|
pub anthropic_base_url: Option<String>,
|
||||||
|
#[serde(rename = "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC")]
|
||||||
|
pub disable_nonessential_traffic: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub other: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct ClaudePermissions {
|
||||||
|
#[serde(default)]
|
||||||
|
pub allow: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub deny: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 Claude 配置文件路径
|
||||||
|
pub fn get_claude_config_path() -> Result<PathBuf, String> {
|
||||||
|
let home = home_dir().ok_or_else(|| "无法获取主目录".to_string())?;
|
||||||
|
Ok(home.join(".claude").join("settings.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取配置备份文件路径
|
||||||
|
pub fn get_config_backup_path() -> Result<PathBuf, String> {
|
||||||
|
let home = home_dir().ok_or_else(|| "无法获取主目录".to_string())?;
|
||||||
|
Ok(home.join(".claude").join("settings.backup.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读取 Claude 配置文件
|
||||||
|
pub fn read_claude_config() -> Result<ClaudeConfig, String> {
|
||||||
|
let config_path = get_claude_config_path()?;
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
// 如果配置文件不存在,创建默认配置
|
||||||
|
return Ok(ClaudeConfig {
|
||||||
|
env: ClaudeEnv::default(),
|
||||||
|
permissions: Some(ClaudePermissions::default()),
|
||||||
|
model: None,
|
||||||
|
api_key_helper: None,
|
||||||
|
other: json!({}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs::read_to_string(&config_path)
|
||||||
|
.map_err(|e| format!("读取配置文件失败: {}", e))?;
|
||||||
|
|
||||||
|
serde_json::from_str(&content)
|
||||||
|
.map_err(|e| format!("解析配置文件失败: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 写入 Claude 配置文件
|
||||||
|
pub fn write_claude_config(config: &ClaudeConfig) -> Result<(), String> {
|
||||||
|
let config_path = get_claude_config_path()?;
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
if let Some(parent) = config_path.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| format!("创建配置目录失败: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = serde_json::to_string_pretty(config)
|
||||||
|
.map_err(|e| format!("序列化配置失败: {}", e))?;
|
||||||
|
|
||||||
|
fs::write(&config_path, content)
|
||||||
|
.map_err(|e| format!("写入配置文件失败: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 备份当前配置
|
||||||
|
pub fn backup_claude_config() -> Result<(), String> {
|
||||||
|
let config_path = get_claude_config_path()?;
|
||||||
|
let backup_path = get_config_backup_path()?;
|
||||||
|
|
||||||
|
if config_path.exists() {
|
||||||
|
fs::copy(&config_path, &backup_path)
|
||||||
|
.map_err(|e| format!("备份配置文件失败: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 恢复配置备份
|
||||||
|
pub fn restore_claude_config() -> Result<(), String> {
|
||||||
|
let config_path = get_claude_config_path()?;
|
||||||
|
let backup_path = get_config_backup_path()?;
|
||||||
|
|
||||||
|
if !backup_path.exists() {
|
||||||
|
return Err("备份文件不存在".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::copy(&backup_path, &config_path)
|
||||||
|
.map_err(|e| format!("恢复配置文件失败: {}", e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据中转站配置更新 Claude 配置
|
||||||
|
pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), String> {
|
||||||
|
// 先备份当前配置
|
||||||
|
backup_claude_config()?;
|
||||||
|
|
||||||
|
// 读取当前配置
|
||||||
|
let mut config = read_claude_config()?;
|
||||||
|
|
||||||
|
// 更新 API URL
|
||||||
|
config.env.anthropic_base_url = Some(station.api_url.clone());
|
||||||
|
|
||||||
|
// 更新 API Token
|
||||||
|
config.env.anthropic_auth_token = Some(station.system_token.clone());
|
||||||
|
|
||||||
|
// 将中转站的 token 也设置到 apiKeyHelper
|
||||||
|
// 格式:echo 'token'
|
||||||
|
config.api_key_helper = Some(format!("echo '{}'", station.system_token));
|
||||||
|
|
||||||
|
// 如果是自定义适配器,可能需要特殊处理
|
||||||
|
match station.adapter.as_str() {
|
||||||
|
"newapi" | "oneapi" => {
|
||||||
|
// NewAPI 和 OneAPI 兼容 OpenAI 格式,不需要特殊处理
|
||||||
|
}
|
||||||
|
"yourapi" => {
|
||||||
|
// YourAPI 可能需要特殊的路径格式
|
||||||
|
if !station.api_url.ends_with("/v1") {
|
||||||
|
config.env.anthropic_base_url = Some(format!("{}/v1", station.api_url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"custom" => {
|
||||||
|
// 自定义适配器,使用原始配置
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入更新后的配置
|
||||||
|
write_claude_config(&config)?;
|
||||||
|
|
||||||
|
log::info!("已将中转站 {} 的配置应用到 Claude 配置文件", station.name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清除中转站配置(恢复默认)
|
||||||
|
pub fn clear_relay_station_from_config() -> Result<(), String> {
|
||||||
|
// 尝试从备份恢复原始的配置
|
||||||
|
let backup_config = if let Ok(backup_path) = get_config_backup_path() {
|
||||||
|
if backup_path.exists() {
|
||||||
|
let content = fs::read_to_string(&backup_path).ok();
|
||||||
|
content.and_then(|c| serde_json::from_str::<ClaudeConfig>(&c).ok())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// 读取当前配置
|
||||||
|
let mut config = read_claude_config()?;
|
||||||
|
|
||||||
|
// 清除 API URL 和 Token
|
||||||
|
config.env.anthropic_base_url = None;
|
||||||
|
config.env.anthropic_auth_token = None;
|
||||||
|
|
||||||
|
// 恢复原始的 apiKeyHelper(如果有备份的话)
|
||||||
|
if let Some(backup) = backup_config {
|
||||||
|
config.api_key_helper = backup.api_key_helper;
|
||||||
|
// 如果备份中有 ANTHROPIC_AUTH_TOKEN,也恢复它
|
||||||
|
if backup.env.anthropic_auth_token.is_some() {
|
||||||
|
config.env.anthropic_auth_token = backup.env.anthropic_auth_token;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有备份,清除 apiKeyHelper
|
||||||
|
config.api_key_helper = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入更新后的配置
|
||||||
|
write_claude_config(&config)?;
|
||||||
|
|
||||||
|
log::info!("已清除 Claude 配置文件中的中转站设置");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取当前配置中的 API URL
|
||||||
|
pub fn get_current_api_url() -> Result<Option<String>, String> {
|
||||||
|
let config = read_claude_config()?;
|
||||||
|
Ok(config.env.anthropic_base_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取当前配置中的 API Token
|
||||||
|
pub fn get_current_api_token() -> Result<Option<String>, String> {
|
||||||
|
let config = read_claude_config()?;
|
||||||
|
Ok(config.env.anthropic_auth_token)
|
||||||
|
}
|
20
src/App.tsx
20
src/App.tsx
@@ -29,6 +29,7 @@ import { useAppLifecycle, useTrackEvent } from "@/hooks";
|
|||||||
import { useTranslation } from "@/hooks/useTranslation";
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
import { WelcomePage } from "@/components/WelcomePage";
|
import { WelcomePage } from "@/components/WelcomePage";
|
||||||
import RelayStationManager from "@/components/RelayStationManager";
|
import RelayStationManager from "@/components/RelayStationManager";
|
||||||
|
import i18n from "@/lib/i18n";
|
||||||
|
|
||||||
type View =
|
type View =
|
||||||
| "welcome"
|
| "welcome"
|
||||||
@@ -75,6 +76,25 @@ function AppContent() {
|
|||||||
const [hasTrackedFirstChat] = useState(false);
|
const [hasTrackedFirstChat] = useState(false);
|
||||||
// const [hasTrackedFirstAgent] = useState(false);
|
// const [hasTrackedFirstAgent] = useState(false);
|
||||||
|
|
||||||
|
// Initialize backend language on app startup
|
||||||
|
useEffect(() => {
|
||||||
|
const initializeBackendLanguage = async () => {
|
||||||
|
try {
|
||||||
|
// Get the current frontend language
|
||||||
|
const frontendLang = i18n.language;
|
||||||
|
// Map to backend format
|
||||||
|
const backendLocale = frontendLang === 'zh' ? 'zh-CN' : 'en-US';
|
||||||
|
// Sync to backend
|
||||||
|
await api.setLanguage(backendLocale);
|
||||||
|
console.log('Backend language initialized to:', backendLocale);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize backend language:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeBackendLanguage();
|
||||||
|
}, []); // Run once on app startup
|
||||||
|
|
||||||
// Track when user reaches different journey stages
|
// Track when user reaches different journey stages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (view === "projects" && projects.length > 0 && !hasTrackedFirstChat) {
|
if (view === "projects" && projects.length > 0 && !hasTrackedFirstChat) {
|
||||||
|
@@ -159,9 +159,17 @@ export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
|||||||
const checkConsent = async () => {
|
const checkConsent = async () => {
|
||||||
if (hasChecked) return;
|
if (hasChecked) return;
|
||||||
|
|
||||||
|
// Check if we've already shown the consent dialog before
|
||||||
|
const hasShownBefore = localStorage.getItem('claudia-analytics-consent-shown');
|
||||||
|
if (hasShownBefore === 'true') {
|
||||||
|
setHasChecked(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await analytics.initialize();
|
await analytics.initialize();
|
||||||
const settings = analytics.getSettings();
|
const settings = analytics.getSettings();
|
||||||
|
|
||||||
|
// Only show if user hasn't made a decision yet
|
||||||
if (!settings?.hasConsented) {
|
if (!settings?.hasConsented) {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
@@ -175,11 +183,21 @@ export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
|||||||
|
|
||||||
const handleAccept = async () => {
|
const handleAccept = async () => {
|
||||||
await analytics.enable();
|
await analytics.enable();
|
||||||
|
// Mark that we've shown the consent dialog
|
||||||
|
localStorage.setItem('claudia-analytics-consent-shown', 'true');
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecline = async () => {
|
const handleDecline = async () => {
|
||||||
await analytics.disable();
|
await analytics.disable();
|
||||||
|
// Mark that we've shown the consent dialog
|
||||||
|
localStorage.setItem('claudia-analytics-consent-shown', 'true');
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
// Even if they close without choosing, mark as shown
|
||||||
|
localStorage.setItem('claudia-analytics-consent-shown', 'true');
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,7 +241,7 @@ export const AnalyticsConsentBanner: React.FC<AnalyticsConsentBannerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setVisible(false)}
|
onClick={handleClose}
|
||||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
|
@@ -121,78 +121,59 @@ export function ClaudiaLogoMinimal({ size = 48, className = "" }: ClaudiaLogoPro
|
|||||||
className={`relative inline-flex items-center justify-center ${className}`}
|
className={`relative inline-flex items-center justify-center ${className}`}
|
||||||
style={{ width: size, height: size }}
|
style={{ width: size, height: size }}
|
||||||
animate={{
|
animate={{
|
||||||
rotate: [0, 5, -5, 5, 0],
|
rotate: [0, 3, -3, 3, 0],
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 6,
|
duration: 8,
|
||||||
repeat: Infinity,
|
repeat: Infinity,
|
||||||
ease: "easeInOut",
|
ease: "easeInOut",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Gradient background */}
|
{/* Simple orange circle background */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 rounded-2xl bg-gradient-to-br from-orange-400 via-orange-500 to-orange-600"
|
className="absolute inset-0 rounded-2xl bg-orange-500"
|
||||||
animate={{
|
animate={{
|
||||||
scale: [1, 1.1, 1],
|
scale: [1, 1.05, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Subtle inner shadow for depth */}
|
||||||
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-transparent to-black/10" />
|
||||||
|
|
||||||
|
{/* Letter C - clean and simple */}
|
||||||
|
<motion.div
|
||||||
|
className="relative z-10 text-white font-bold flex items-center justify-center"
|
||||||
|
style={{ fontSize: size * 0.5, fontFamily: 'system-ui, -apple-system, sans-serif' }}
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.05, 1],
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 3,
|
duration: 3,
|
||||||
repeat: Infinity,
|
repeat: Infinity,
|
||||||
ease: "easeInOut",
|
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
|
C
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Pulse rings */}
|
{/* Single subtle pulse ring */}
|
||||||
{[...Array(2)].map((_, i) => (
|
<motion.div
|
||||||
<motion.div
|
className="absolute inset-0 rounded-2xl border border-orange-400/30"
|
||||||
key={i}
|
animate={{
|
||||||
className="absolute inset-0 rounded-2xl border-2 border-orange-400"
|
scale: [1, 1.3, 1.5],
|
||||||
animate={{
|
opacity: [0.3, 0.1, 0],
|
||||||
scale: [1, 1.5, 2],
|
}}
|
||||||
opacity: [0.5, 0.2, 0],
|
transition={{
|
||||||
}}
|
duration: 4,
|
||||||
transition={{
|
repeat: Infinity,
|
||||||
duration: 3,
|
ease: "easeOut",
|
||||||
repeat: Infinity,
|
}}
|
||||||
delay: i * 1.5,
|
/>
|
||||||
ease: "easeOut",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@@ -3,7 +3,6 @@ import { Bot, FolderCode, BarChart, ServerCog, FileText, Settings, Network } fro
|
|||||||
import { useTranslation } from "@/hooks/useTranslation";
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ClaudiaLogoMinimal } from "@/components/ClaudiaLogo";
|
import { ClaudiaLogoMinimal } from "@/components/ClaudiaLogo";
|
||||||
import { BorderGlowCard } from "@/components/ui/glow-card";
|
|
||||||
|
|
||||||
interface WelcomePageProps {
|
interface WelcomePageProps {
|
||||||
onNavigate: (view: string) => void;
|
onNavigate: (view: string) => void;
|
||||||
@@ -19,8 +18,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: Network,
|
icon: Network,
|
||||||
title: t("welcome.relayStationManagement"),
|
title: t("welcome.relayStationManagement"),
|
||||||
subtitle: t("welcome.relayStationManagementDesc"),
|
subtitle: t("welcome.relayStationManagementDesc"),
|
||||||
color: "text-indigo-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-indigo-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "relay-stations"
|
view: "relay-stations"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,8 +36,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: FolderCode,
|
icon: FolderCode,
|
||||||
title: t("welcome.projectManagement"),
|
title: t("welcome.projectManagement"),
|
||||||
subtitle: t("welcome.projectManagementDesc"),
|
subtitle: t("welcome.projectManagementDesc"),
|
||||||
color: "text-blue-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-blue-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "projects"
|
view: "projects"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -49,8 +48,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: BarChart,
|
icon: BarChart,
|
||||||
title: t("welcome.usageStatistics"),
|
title: t("welcome.usageStatistics"),
|
||||||
subtitle: t("welcome.usageStatisticsDesc"),
|
subtitle: t("welcome.usageStatisticsDesc"),
|
||||||
color: "text-green-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-green-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "usage-dashboard"
|
view: "usage-dashboard"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,8 +57,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: ServerCog,
|
icon: ServerCog,
|
||||||
title: t("welcome.mcpBroker"),
|
title: t("welcome.mcpBroker"),
|
||||||
subtitle: t("welcome.mcpBrokerDesc"),
|
subtitle: t("welcome.mcpBrokerDesc"),
|
||||||
color: "text-purple-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-purple-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "mcp"
|
view: "mcp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,8 +66,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: FileText,
|
icon: FileText,
|
||||||
title: t("welcome.claudeMd"),
|
title: t("welcome.claudeMd"),
|
||||||
subtitle: t("welcome.claudeMdDesc"),
|
subtitle: t("welcome.claudeMdDesc"),
|
||||||
color: "text-cyan-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-cyan-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "editor"
|
view: "editor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,8 +75,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
icon: Settings,
|
icon: Settings,
|
||||||
title: t("welcome.settings"),
|
title: t("welcome.settings"),
|
||||||
subtitle: t("welcome.settingsDesc"),
|
subtitle: t("welcome.settingsDesc"),
|
||||||
color: "text-gray-500",
|
color: "text-orange-500",
|
||||||
bgColor: "bg-gray-500/10",
|
bgColor: "bg-orange-500/10",
|
||||||
view: "settings"
|
view: "settings"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -92,25 +91,25 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-background overflow-hidden">
|
<div className="flex items-center justify-center min-h-screen bg-background overflow-hidden">
|
||||||
<div className="w-full max-w-6xl px-8">
|
<div className="w-full max-w-6xl px-8 -mt-20">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="text-center mb-16"
|
className="text-center mb-10"
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
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">
|
<h1 className="text-5xl font-bold mb-4 flex items-center justify-center gap-4 text-white">
|
||||||
<ClaudiaLogoMinimal size={56} />
|
<ClaudiaLogoMinimal size={56} />
|
||||||
{t("app.welcome")}
|
{t("app.welcome")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground text-xl">
|
<p className="text-white/90 text-xl">
|
||||||
{t("app.tagline")}
|
{t("app.tagline")}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Main Feature Cards */}
|
{/* Main Feature Cards */}
|
||||||
<div className="grid grid-cols-3 gap-8 mb-12">
|
<div className="grid grid-cols-3 gap-8 mb-10">
|
||||||
{mainFeatures.map((feature, index) => (
|
{mainFeatures.map((feature, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={feature.id}
|
key={feature.id}
|
||||||
@@ -123,8 +122,8 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
stiffness: 100
|
stiffness: 100
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BorderGlowCard
|
<div
|
||||||
className="h-full group"
|
className="h-full group bg-white/5 border border-white/10 rounded-lg hover:bg-white/10 hover:border-orange-500/50 transition-all duration-300 cursor-pointer"
|
||||||
onClick={() => handleCardClick(feature.view)}
|
onClick={() => handleCardClick(feature.view)}
|
||||||
>
|
>
|
||||||
<div className="p-10">
|
<div className="p-10">
|
||||||
@@ -133,22 +132,22 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
<feature.icon className={`h-10 w-10 ${feature.color}`} strokeWidth={1.5} />
|
<feature.icon className={`h-10 w-10 ${feature.color}`} strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h2 className="text-2xl font-bold mb-3 group-hover:text-primary transition-colors">
|
<h2 className="text-2xl font-bold mb-3 text-white group-hover:text-primary transition-colors">
|
||||||
{feature.title}
|
{feature.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground text-base leading-relaxed">
|
<p className="text-white/80 text-base leading-relaxed">
|
||||||
{feature.subtitle}
|
{feature.subtitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BorderGlowCard>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Feature Cards */}
|
{/* Bottom Feature Cards */}
|
||||||
<div className="grid grid-cols-4 gap-6 mb-12">
|
<div className="grid grid-cols-4 gap-6 mb-10">
|
||||||
{bottomFeatures.map((feature, index) => (
|
{bottomFeatures.map((feature, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={feature.id}
|
key={feature.id}
|
||||||
@@ -161,22 +160,24 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
stiffness: 100
|
stiffness: 100
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BorderGlowCard
|
<div
|
||||||
className="h-36 group"
|
className="h-32 group bg-white/5 border border-white/10 rounded-lg hover:bg-white/10 hover:border-orange-500/50 transition-all duration-300 cursor-pointer"
|
||||||
onClick={() => handleCardClick(feature.view)}
|
onClick={() => handleCardClick(feature.view)}
|
||||||
>
|
>
|
||||||
<div className="h-full flex flex-col items-center justify-center p-6">
|
<div className="h-full flex items-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`}>
|
<div className={`p-3 ${feature.bgColor} rounded-xl transition-transform duration-300 group-hover:scale-110 group-hover:rotate-6 mr-4 flex-shrink-0`}>
|
||||||
<feature.icon className={`h-8 w-8 ${feature.color}`} strokeWidth={1.5} />
|
<feature.icon className={`h-7 w-7 ${feature.color}`} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className="text-sm font-semibold mb-1 text-white group-hover:text-primary transition-colors truncate">
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-white/70 line-clamp-2">
|
||||||
|
{feature.subtitle}
|
||||||
|
</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</BorderGlowCard>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +196,7 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
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"
|
className="relative px-10 py-7 text-lg font-semibold bg-orange-500 hover:bg-orange-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}
|
onClick={handleButtonClick}
|
||||||
>
|
>
|
||||||
{/* Shimmer effect on button */}
|
{/* Shimmer effect on button */}
|
||||||
@@ -203,10 +204,9 @@ export function WelcomePage({ onNavigate, onNewSession }: WelcomePageProps) {
|
|||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" />
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="relative z-10 flex items-center gap-3">
|
<span className="relative z-10 flex items-center gap-2">
|
||||||
<span className="text-2xl">✨</span>
|
<span className="text-2xl">+</span>
|
||||||
{t("welcome.quickStartSession")}
|
{t("welcome.quickStartSession")}
|
||||||
<span className="text-2xl">🚀</span>
|
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
"ccProjects": "CC Projects",
|
"ccProjects": "CC Projects",
|
||||||
"browseClaudeCodeSessions": "Browse your Claude Code sessions",
|
"browseClaudeCodeSessions": "Browse your Claude Code sessions",
|
||||||
"newClaudeCodeSession": "New Claude Code session",
|
"newClaudeCodeSession": "New Claude Code session",
|
||||||
"ccAgents": "CC Agents",
|
"ccAgents": "Agent Management",
|
||||||
"mcpServers": "MCP Servers",
|
"mcpServers": "MCP Servers",
|
||||||
"manageMcpServers": "Manage Model Context Protocol servers",
|
"manageMcpServers": "Manage Model Context Protocol servers",
|
||||||
"app": {
|
"app": {
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "CC Projects",
|
"projects": "CC Projects",
|
||||||
"agents": "CC Agents",
|
"agents": "Agent Management",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"usage": "Usage Dashboard",
|
"usage": "Usage Dashboard",
|
||||||
"mcp": "MCP Manager",
|
"mcp": "MCP Manager",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"sessionHistory": "Session History"
|
"sessionHistory": "Session History"
|
||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"title": "CC Agents",
|
"title": "Agent Management",
|
||||||
"newAgent": "New Agent",
|
"newAgent": "New Agent",
|
||||||
"createAgent": "Create Agent",
|
"createAgent": "Create Agent",
|
||||||
"editAgent": "Edit Agent",
|
"editAgent": "Edit Agent",
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
"ccProjects": "Claude Code 项目",
|
"ccProjects": "Claude Code 项目",
|
||||||
"browseClaudeCodeSessions": "浏览您的 Claude Code 会话",
|
"browseClaudeCodeSessions": "浏览您的 Claude Code 会话",
|
||||||
"newClaudeCodeSession": "新建 Claude Code 会话",
|
"newClaudeCodeSession": "新建 Claude Code 会话",
|
||||||
"ccAgents": "CC 智能体",
|
"ccAgents": "Agent 管理",
|
||||||
"mcpServers": "MCP 服务器",
|
"mcpServers": "MCP 服务器",
|
||||||
"manageMcpServers": "管理模型上下文协议服务器",
|
"manageMcpServers": "管理模型上下文协议服务器",
|
||||||
"app": {
|
"app": {
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "Claude Code 项目",
|
"projects": "Claude Code 项目",
|
||||||
"agents": "CC 智能体",
|
"agents": "Agent 管理",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"usage": "用量仪表板",
|
"usage": "用量仪表板",
|
||||||
"mcp": "MCP 管理器",
|
"mcp": "MCP 管理器",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"sessionHistory": "会话历史"
|
"sessionHistory": "会话历史"
|
||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"title": "CC 智能体",
|
"title": "Agent 管理",
|
||||||
"newAgent": "新建智能体",
|
"newAgent": "新建智能体",
|
||||||
"createAgent": "创建智能体",
|
"createAgent": "创建智能体",
|
||||||
"editAgent": "编辑智能体",
|
"editAgent": "编辑智能体",
|
||||||
|
Reference in New Issue
Block a user