diff --git a/CHANGELOG.md b/CHANGELOG.md index 614ce4ee0..3e9a09874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **Windows Home Dir Regression**: Prevent providers/settings “disappearing” after upgrading from v3.10.2 → v3.10.3 when `HOME` differs from the real user profile directory; restore default path resolution and auto-detect the v3.10.3 legacy database location. + --- ## [3.10.3] - 2026-01-30 diff --git a/README.md b/README.md index 7b5f03102..737480ac1 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original pric Thanks to DMXAPI for sponsoring this project! DMXAPI provides global large model API services to 200+ enterprise users. One API key for all global models. Features include: instant invoicing, unlimited concurrency, starting from $0.15, 24/7 technical support. GPT/Claude/Gemini all at 32% off, domestic models 20-50% off, Claude Code exclusive models at 66% off! Register here + +AICoding +Thanks to AICoding.sh for sponsoring this project! AICoding.sh — Global AI Model API Relay Service at Unbeatable Prices! Claude Code at 19% of original price, GPT at just 1%! Trusted by hundreds of enterprises for cost-effective AI services. Supports Claude Code, GPT, Gemini and major domestic models, with enterprise-grade high concurrency, fast invoicing, and 24/7 dedicated technical support. CC Switch users who register via this link get 10% off their first top-up! + + ## Screenshots diff --git a/README_JA.md b/README_JA.md index 2926e4e78..303712258 100644 --- a/README_JA.md +++ b/README_JA.md @@ -48,6 +48,11 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% / DMXAPI のご支援に感謝します!DMXAPI は 200 社以上の企業ユーザーにグローバル大規模モデル API サービスを提供しています。1 つの API キーで全世界のモデルにアクセス可能。即時請求書発行、同時接続数無制限、最低 $0.15 から、24 時間年中無休のテクニカルサポート。GPT/Claude/Gemini が全て 32% オフ、国内モデルは 20〜50% オフ、Claude Code 専用モデルは 66% オフ実施中!登録はこちら + +AICoding +AICoding.sh のご支援に感謝します!AICoding.sh —— グローバル AI モデル API 超お得な中継サービス!Claude Code 81% オフ、GPT 99% オフ!数百社の企業に高コストパフォーマンスの AI サービスを提供。Claude Code、GPT、Gemini および国内主要モデルに対応、エンタープライズ級の高同時接続、迅速な請求書発行、24 時間年中無休の専属テクニカルサポート。こちらのリンクから登録した CC Switch ユーザーは、初回チャージ 10% オフ! + + ## スクリーンショット diff --git a/README_ZH.md b/README_ZH.md index e9715cd1d..e33a32f39 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -49,6 +49,11 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更 为200多家企业用户提供全球大模型API服务。· 充值即开票 ·当天开票 ·并发不限制 ·1元起充 · 7x24 在线技术辅导,GPT/Claude/Gemini全部6.8折,国内模型5~8折,Claude Code 专属模型3.4折进行中!点击这里注册 + +AICoding +感谢 AICoding.sh 赞助了本项目!AICoding.sh —— 全球大模型 API 超值中转服务!Claude Code 1.9 折,GPT 0.1 折,已为数百家企业提供高性价比 AI 服务。支持 Claude Code、GPT、Gemini 及国内主流模型,企业级高并发、极速开票、7×24 专属技术支持,通过此链接 注册的 CC Switch 用户,首充可享受九折优惠! + + ## 界面预览 diff --git a/assets/partners/logos/aicoding.jpg b/assets/partners/logos/aicoding.jpg new file mode 100644 index 000000000..6f5704ead Binary files /dev/null and b/assets/partners/logos/aicoding.jpg differ diff --git a/src-tauri/src/commands/misc.rs b/src-tauri/src/commands/misc.rs index b159bd8b2..8e6da2bb4 100644 --- a/src-tauri/src/commands/misc.rs +++ b/src-tauri/src/commands/misc.rs @@ -652,6 +652,7 @@ exec bash --norc --noprofile "alacritty" => launch_macos_open_app("Alacritty", &script_file, true), "kitty" => launch_macos_open_app("kitty", &script_file, false), "ghostty" => launch_macos_open_app("Ghostty", &script_file, true), + "wezterm" => launch_macos_open_app("WezTerm", &script_file, true), _ => launch_macos_terminal_app(&script_file), // "terminal" or default }; @@ -954,3 +955,18 @@ fn run_windows_start_command(args: &[&str], terminal_name: &str) -> Result<(), S Ok(()) } + +/// 设置窗口主题(Windows/macOS 标题栏颜色) +/// theme: "dark" | "light" | "system" +#[tauri::command] +pub async fn set_window_theme(window: tauri::Window, theme: String) -> Result<(), String> { + use tauri::Theme; + + let tauri_theme = match theme.as_str() { + "dark" => Some(Theme::Dark), + "light" => Some(Theme::Light), + _ => None, // system default + }; + + window.set_theme(tauri_theme).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/session_manager.rs b/src-tauri/src/commands/session_manager.rs index 4eb1994a8..8a9f910d8 100644 --- a/src-tauri/src/commands/session_manager.rs +++ b/src-tauri/src/commands/session_manager.rs @@ -26,16 +26,24 @@ pub async fn get_session_messages( #[tauri::command] pub async fn launch_session_terminal( - target: String, command: String, cwd: Option, custom_config: Option, ) -> Result { let command = command.clone(); - let target = target.clone(); let cwd = cwd.clone(); let custom_config = custom_config.clone(); + // Read preferred terminal from global settings + let preferred = crate::settings::get_preferred_terminal(); + // Map global setting terminal names to session terminal names + // Global uses "iterm2", session terminal uses "iterm" + let target = match preferred.as_deref() { + Some("iterm2") => "iterm".to_string(), + Some(t) => t.to_string(), + None => "terminal".to_string(), // Default to Terminal.app on macOS + }; + tauri::async_runtime::spawn_blocking(move || { session_manager::terminal::launch_terminal( &target, diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index aea9c4c8f..11a3c0758 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -7,16 +7,25 @@ use crate::error::AppError; /// 获取用户主目录,带回退和日志 /// -/// On Windows, respects the `HOME` environment variable (if set) to support -/// test isolation. Falls back to `dirs::home_dir()` otherwise. +/// ## Windows 注意事项 +/// +/// - `dirs::home_dir()` 在 Windows 上使用 `SHGetKnownFolderPath(FOLDERID_Profile)`, +/// 返回的是真实用户目录(类似 `C:\\Users\\Alice`),与 v3.10.2 行为一致。 +/// - 不要直接使用 `HOME` 环境变量:它可能由 Git/Cygwin/MSYS 等第三方工具注入, +/// 且不一定等于用户目录,可能导致 `.cc-switch/cc-switch.db` 路径变化,从而“看起来像数据丢失”。 +/// +/// ## 测试隔离 +/// +/// 为了让 Windows CI/本地测试能稳定隔离真实用户数据,可通过 `CC_SWITCH_TEST_HOME` +/// 显式覆盖 home dir(仅用于测试/调试场景)。 pub fn get_home_dir() -> PathBuf { - #[cfg(windows)] - if let Ok(home) = std::env::var("HOME") { + if let Ok(home) = std::env::var("CC_SWITCH_TEST_HOME") { let trimmed = home.trim(); if !trimmed.is_empty() { return PathBuf::from(trimmed); } } + dirs::home_dir().unwrap_or_else(|| { log::warn!("无法获取用户主目录,回退到当前目录"); PathBuf::from(".") @@ -81,7 +90,35 @@ pub fn get_app_config_dir() -> PathBuf { if let Some(custom) = crate::app_store::get_app_config_dir_override() { return custom; } - get_home_dir().join(".cc-switch") + + let default_dir = get_home_dir().join(".cc-switch"); + + // 兼容 v3.10.3:当用户环境存在 `HOME` 且与真实用户目录不同, + // v3.10.3 可能在 `HOME/.cc-switch/` 下创建/使用了数据库。 + // 这里仅在“默认位置没有数据库”时回退到旧位置,避免再次出现“供应商消失”问题, + // 同时也避免新安装因为 `HOME` 被设置而写入非预期路径。 + #[cfg(windows)] + { + let default_db = default_dir.join("cc-switch.db"); + if !default_db.exists() { + if let Ok(home_env) = std::env::var("HOME") { + let trimmed = home_env.trim(); + if !trimmed.is_empty() { + let legacy_dir = PathBuf::from(trimmed).join(".cc-switch"); + if legacy_dir.join("cc-switch.db").exists() { + log::info!( + "Detected v3.10.3 legacy database at {}, using it instead of {}", + legacy_dir.display(), + default_dir.display() + ); + return legacy_dir; + } + } + } + } + } + + default_dir } /// 获取应用配置文件路径 diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 30644bb57..c360b6599 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -959,6 +959,8 @@ pub fn run() { commands::test_proxy_url, commands::get_upstream_proxy_status, commands::scan_local_proxies, + // Window theme control + commands::set_window_theme, ]); let app = builder diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 504c10fba..e076577cd 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -19,6 +19,7 @@ "height": 650, "minWidth": 900, "minHeight": 600, + "visible": false, "resizable": true, "fullscreen": false, "center": true diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index a58255315..d3733a4ee 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -6,6 +6,7 @@ "label": "main", "title": "CC Switch", "titleBarStyle": "Visible", + "visible": false, "minWidth": 900, "minHeight": 600 } diff --git a/src-tauri/tests/support.rs b/src-tauri/tests/support.rs index 78f18759f..3a45f0e0e 100644 --- a/src-tauri/tests/support.rs +++ b/src-tauri/tests/support.rs @@ -14,6 +14,9 @@ pub fn ensure_test_home() -> &'static Path { let _ = std::fs::remove_dir_all(&base); } std::fs::create_dir_all(&base).expect("create test home"); + // Windows 上 `dirs::home_dir()` 不受 HOME/USERPROFILE 影响(走 Known Folder API), + // 用 CC_SWITCH_TEST_HOME 显式覆盖,以确保测试不会污染真实用户目录。 + std::env::set_var("CC_SWITCH_TEST_HOME", &base); std::env::set_var("HOME", &base); #[cfg(windows)] std::env::set_var("USERPROFILE", &base); diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index b9d499719..dd1d281d8 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -20,7 +20,7 @@ import type { Provider } from "@/types"; import type { AppId } from "@/lib/api"; import { providersApi } from "@/lib/api/providers"; import { useDragSort } from "@/hooks/useDragSort"; -import { useStreamCheck } from "@/hooks/useStreamCheck"; +// import { useStreamCheck } from "@/hooks/useStreamCheck"; // 测试功能已隐藏 import { ProviderCard } from "@/components/providers/ProviderCard"; import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState"; import { @@ -93,8 +93,8 @@ export function ProviderList({ [appId, opencodeLiveIds], ); - // 流式健康检查 - const { checkProvider, isChecking } = useStreamCheck(appId); + // 流式健康检查 - 功能已隐藏 + // const { checkProvider, isChecking } = useStreamCheck(appId); // 故障转移相关 const { data: isAutoFailoverEnabled } = useAutoFailoverEnabled(appId); @@ -139,9 +139,10 @@ export function ProviderList({ [appId, addToQueue, removeFromQueue], ); - const handleTest = (provider: Provider) => { - checkProvider(provider.id, provider.name); - }; + // handleTest 功能已隐藏 - 供应商请求格式复杂难以统一测试 + // const handleTest = (provider: Provider) => { + // checkProvider(provider.id, provider.name); + // }; const [searchTerm, setSearchTerm] = useState(""); const [isSearchOpen, setIsSearchOpen] = useState(false); @@ -229,8 +230,9 @@ export function ProviderList({ onConfigureUsage={onConfigureUsage} onOpenWebsite={onOpenWebsite} onOpenTerminal={onOpenTerminal} - onTest={appId !== "opencode" ? handleTest : undefined} - isTesting={isChecking(provider.id)} + // onTest 功能已隐藏 - 供应商请求格式复杂难以统一测试 + // onTest={appId !== "opencode" ? handleTest : undefined} + isTesting={false} // isChecking(provider.id) - 测试功能已隐藏 isProxyRunning={isProxyRunning} isProxyTakeover={isProxyTakeover} // 故障转移相关:联动状态 diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index ee7f19b02..8db5ca19e 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -746,7 +746,7 @@ export function ProviderForm({ return; } - // OpenCode: validate provider key + // OpenCode: validate provider key and models if (appId === "opencode") { const keyPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/; if (!opencodeProviderKey.trim()) { @@ -761,6 +761,11 @@ export function ProviderForm({ toast.error(t("opencode.providerKeyDuplicate")); return; } + // Validate that at least one model is configured + if (Object.keys(opencodeModels).length === 0) { + toast.error(t("opencode.modelsRequired")); + return; + } } // 非官方供应商必填校验:端点和 API Key diff --git a/src/components/sessions/SessionItem.tsx b/src/components/sessions/SessionItem.tsx index 9a6558f27..15de67e65 100644 --- a/src/components/sessions/SessionItem.tsx +++ b/src/components/sessions/SessionItem.tsx @@ -70,7 +70,7 @@ export function SessionItem({
- {lastActive ? formatRelativeTime(lastActive) : t("common.unknown")} + {lastActive ? formatRelativeTime(lastActive, t) : t("common.unknown")}
diff --git a/src/components/sessions/SessionManagerPage.tsx b/src/components/sessions/SessionManagerPage.tsx index 2654b8076..ad0825e55 100644 --- a/src/components/sessions/SessionManagerPage.tsx +++ b/src/components/sessions/SessionManagerPage.tsx @@ -10,9 +10,7 @@ import { MessageSquare, Clock, FolderOpen, - Terminal, X, - SquareTerminal, } from "lucide-react"; import { useSessionMessagesQuery, useSessionsQuery } from "@/lib/query"; import { sessionsApi } from "@/lib/api"; @@ -24,7 +22,6 @@ import { SelectContent, SelectItem, SelectTrigger, - SelectValue, } from "@/components/ui/select"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -37,13 +34,6 @@ import { import { extractErrorMessage } from "@/utils/errorUtils"; import { isMac } from "@/lib/platform"; import { ProviderIcon } from "@/components/ProviderIcon"; -import { - AlacrittyIcon, - GhosttyIcon, - ITermIcon, - KittyIcon, - WezTermIcon, -} from "@/components/icons/TerminalIcons"; import { SessionItem } from "./SessionItem"; import { SessionMessageItem } from "./SessionMessageItem"; import { SessionTocDialog, SessionTocSidebar } from "./SessionToc"; @@ -56,16 +46,6 @@ import { getSessionKey, } from "./utils"; -const TERMINAL_TARGET_KEY = "session_manager_terminal_target"; - -type TerminalTarget = - | "terminal" - | "iterm" - | "ghostty" - | "kitty" - | "wezterm" - | "alacritty"; - type ProviderFilter = "all" | "codex" | "claude"; export function SessionManagerPage() { @@ -85,26 +65,6 @@ export function SessionManagerPage() { const [search, setSearch] = useState(""); const [providerFilter, setProviderFilter] = useState("all"); const [selectedKey, setSelectedKey] = useState(null); - const [terminalTarget, setTerminalTarget] = - useState("terminal"); - const [isLoaded, setIsLoaded] = useState(false); - - useEffect(() => { - const storedTarget = window.localStorage.getItem( - TERMINAL_TARGET_KEY, - ) as TerminalTarget | null; - if (storedTarget) { - setTerminalTarget(storedTarget); - } - - setIsLoaded(true); - }, []); - - useEffect(() => { - if (isLoaded) { - window.localStorage.setItem(TERMINAL_TARGET_KEY, terminalTarget); - } - }, [terminalTarget, isLoaded]); // 使用 FlexSearch 全文搜索 const { search: searchSessions } = useSessionSearch({ @@ -205,7 +165,6 @@ export function SessionManagerPage() { try { await sessionsApi.launchTerminal({ - target: terminalTarget, command: selectedSession.resumeCommand, cwd: selectedSession.projectDir ?? undefined, }); @@ -288,7 +247,9 @@ export function SessionManagerPage() { - 搜索会话 + + {t("sessionManager.searchSessions")} + - setTerminalTarget(value as TerminalTarget) - } - > - - - - - - -
- - Terminal -
-
- -
- - iTerm2 (Untested) -
-
- -
- - Ghostty -
-
- -
- - Kitty -
-
- -
- - WezTerm (Untested) -
-
- -
- - Alacritty (Untested) -
-
-
- - - - - - - - {selectedSession.resumeCommand - ? t("sessionManager.resumeTooltip", { - defaultValue: "在终端中恢复此会话", - }) - : t("sessionManager.noResumeCommand", { - defaultValue: "此会话无法恢复", - })} - - - + + + + + + {selectedSession.resumeCommand + ? t("sessionManager.resumeTooltip", { + defaultValue: "在终端中恢复此会话", + }) + : t("sessionManager.noResumeCommand", { + defaultValue: "此会话无法恢复", + })} + + )} diff --git a/src/components/sessions/SessionMessageItem.tsx b/src/components/sessions/SessionMessageItem.tsx index 81d420314..010f9f74e 100644 --- a/src/components/sessions/SessionMessageItem.tsx +++ b/src/components/sessions/SessionMessageItem.tsx @@ -59,7 +59,7 @@ export function SessionMessageItem({
- {getRoleLabel(message.role)} + {getRoleLabel(message.role, t)} {message.ts && ( diff --git a/src/components/sessions/SessionToc.tsx b/src/components/sessions/SessionToc.tsx index daefab4d0..83fda86bd 100644 --- a/src/components/sessions/SessionToc.tsx +++ b/src/components/sessions/SessionToc.tsx @@ -1,4 +1,5 @@ import { List, X } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -26,6 +27,7 @@ export function SessionTocSidebar({ items, onItemClick, }: SessionTocSidebarProps) { + const { t } = useTranslation(); if (items.length <= 2) return null; return ( @@ -33,7 +35,7 @@ export function SessionTocSidebar({
- 对话目录 + {t("sessionManager.tocTitle")}
@@ -74,6 +76,7 @@ export function SessionTocDialog({ open, onOpenChange, }: SessionTocDialogProps) { + const { t } = useTranslation(); if (items.length <= 2) return null; return ( @@ -95,11 +98,11 @@ export function SessionTocDialog({ - 对话目录 + {t("sessionManager.tocTitle")} diff --git a/src/components/sessions/utils.ts b/src/components/sessions/utils.ts index c3d0bb127..d6dd9f327 100644 --- a/src/components/sessions/utils.ts +++ b/src/components/sessions/utils.ts @@ -17,7 +17,10 @@ export const formatTimestamp = (value?: number) => { return new Date(value).toLocaleString(); }; -export const formatRelativeTime = (value?: number) => { +export const formatRelativeTime = ( + value: number | undefined, + t: (key: string, options?: Record) => string, +) => { if (!value) return ""; const now = Date.now(); const diff = now - value; @@ -25,10 +28,10 @@ export const formatRelativeTime = (value?: number) => { const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); - if (minutes < 1) return "刚刚"; - if (minutes < 60) return `${minutes} 分钟前`; - if (hours < 24) return `${hours} 小时前`; - if (days < 7) return `${days} 天前`; + if (minutes < 1) return t("sessionManager.justNow"); + if (minutes < 60) return t("sessionManager.minutesAgo", { count: minutes }); + if (hours < 24) return t("sessionManager.hoursAgo", { count: hours }); + if (days < 7) return t("sessionManager.daysAgo", { count: days }); return new Date(value).toLocaleDateString(); }; @@ -57,12 +60,12 @@ export const getRoleTone = (role: string) => { return "text-muted-foreground"; }; -export const getRoleLabel = (role: string) => { +export const getRoleLabel = (role: string, t: (key: string) => string) => { const normalized = role.toLowerCase(); if (normalized === "assistant") return "AI"; - if (normalized === "user") return "用户"; - if (normalized === "system") return "系统"; - if (normalized === "tool") return "工具"; + if (normalized === "user") return t("sessionManager.roleUser"); + if (normalized === "system") return t("sessionManager.roleSystem"); + if (normalized === "tool") return t("sessionManager.roleTool"); return role; }; diff --git a/src/components/settings/TerminalSettings.tsx b/src/components/settings/TerminalSettings.tsx index e1e77d7c5..c1d62a158 100644 --- a/src/components/settings/TerminalSettings.tsx +++ b/src/components/settings/TerminalSettings.tsx @@ -15,6 +15,7 @@ const MACOS_TERMINALS = [ { value: "alacritty", labelKey: "settings.terminal.options.macos.alacritty" }, { value: "kitty", labelKey: "settings.terminal.options.macos.kitty" }, { value: "ghostty", labelKey: "settings.terminal.options.macos.ghostty" }, + { value: "wezterm", labelKey: "settings.terminal.options.macos.wezterm" }, ] as const; const WINDOWS_TERMINALS = [ diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index b1a45072d..0a1fec2ae 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -5,6 +5,7 @@ import React, { useMemo, useState, } from "react"; +import { invoke } from "@tauri-apps/api/core"; type Theme = "light" | "dark" | "system"; @@ -94,6 +95,54 @@ export function ThemeProvider({ return () => mediaQuery.removeEventListener("change", handleChange); }, [theme]); + // Sync native window theme (Windows/macOS title bar) + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + let isCancelled = false; + + const updateNativeTheme = async (nativeTheme: string) => { + if (isCancelled) return; + try { + await invoke("set_window_theme", { theme: nativeTheme }); + } catch (e) { + // Ignore errors (e.g., when not running in Tauri) + console.debug("Failed to set native window theme:", e); + } + }; + + // Determine current effective theme + if (theme === "system") { + const isDark = + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches; + updateNativeTheme(isDark ? "dark" : "light"); + } else { + updateNativeTheme(theme); + } + + // Listen to system theme changes for native window when in "system" mode + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = () => { + if (theme === "system" && !isCancelled) { + updateNativeTheme(mediaQuery.matches ? "dark" : "light"); + } + }; + + if (theme === "system") { + mediaQuery.addEventListener("change", handleChange); + } + + return () => { + isCancelled = true; + if (theme === "system") { + mediaQuery.removeEventListener("change", handleChange); + } + }; + }, [theme]); + const value = useMemo( () => ({ theme, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index ec1a724ec..a7d8f6d4d 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -308,7 +308,8 @@ "iterm2": "iTerm2", "alacritty": "Alacritty", "kitty": "Kitty", - "ghostty": "Ghostty" + "ghostty": "Ghostty", + "wezterm": "WezTerm" }, "windows": { "cmd": "Command Prompt", @@ -412,6 +413,7 @@ "title": "Session Manager", "subtitle": "Manage Codex and Claude Code sessions", "searchPlaceholder": "Search by content, directory, or ID", + "searchSessions": "Search sessions", "providerFilterAll": "All", "sessionList": "Sessions", "loadingSessions": "Loading sessions...", @@ -435,7 +437,16 @@ "copySourcePath": "Copy source file", "sourcePathCopied": "Source file copied", "loadingMessages": "Loading transcript...", - "emptySession": "No messages available" + "emptySession": "No messages available", + "clickToCopyPath": "Click to copy path", + "tocTitle": "Contents", + "justNow": "Just now", + "minutesAgo": "{{count}} min ago", + "hoursAgo": "{{count}} hr ago", + "daysAgo": "{{count}} days ago", + "roleUser": "User", + "roleSystem": "System", + "roleTool": "Tool" }, "console": { "providerSwitchReceived": "Received provider switch event:", @@ -640,6 +651,7 @@ "modelId": "Model ID", "modelName": "Display Name", "noModels": "No models configured", + "modelsRequired": "Please add at least one model", "providerKey": "Provider Key", "providerKeyPlaceholder": "my-provider", "providerKeyHint": "Unique identifier in config file. Cannot be changed after creation. Use lowercase letters, numbers, and hyphens only.", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 7c2af658f..c1cb8ad38 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -308,7 +308,8 @@ "iterm2": "iTerm2", "alacritty": "Alacritty", "kitty": "Kitty", - "ghostty": "Ghostty" + "ghostty": "Ghostty", + "wezterm": "WezTerm" }, "windows": { "cmd": "コマンドプロンプト", @@ -412,6 +413,7 @@ "title": "セッション管理", "subtitle": "Codex / Claude Code のセッションを管理", "searchPlaceholder": "内容・ディレクトリ・ID で検索", + "searchSessions": "セッションを検索", "providerFilterAll": "すべて", "sessionList": "セッション一覧", "loadingSessions": "セッションを読み込み中...", @@ -435,7 +437,16 @@ "copySourcePath": "元ファイルをコピー", "sourcePathCopied": "元ファイルをコピーしました", "loadingMessages": "内容を読み込み中...", - "emptySession": "表示できる内容がありません" + "emptySession": "表示できる内容がありません", + "clickToCopyPath": "クリックしてパスをコピー", + "tocTitle": "目次", + "justNow": "たった今", + "minutesAgo": "{{count}}分前", + "hoursAgo": "{{count}}時間前", + "daysAgo": "{{count}}日前", + "roleUser": "ユーザー", + "roleSystem": "システム", + "roleTool": "ツール" }, "console": { "providerSwitchReceived": "プロバイダー切り替えイベントを受信:", @@ -640,6 +651,7 @@ "modelId": "モデル ID", "modelName": "表示名", "noModels": "モデルが設定されていません", + "modelsRequired": "モデルを少なくとも1つ追加してください", "providerKey": "プロバイダーキー", "providerKeyPlaceholder": "my-provider", "providerKeyHint": "設定ファイルの一意の識別子。作成後は変更できません。小文字、数字、ハイフンのみ使用できます。", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 12f787388..ac19244cb 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -308,7 +308,8 @@ "iterm2": "iTerm2", "alacritty": "Alacritty", "kitty": "Kitty", - "ghostty": "Ghostty" + "ghostty": "Ghostty", + "wezterm": "WezTerm" }, "windows": { "cmd": "命令提示符", @@ -412,6 +413,7 @@ "title": "会话管理", "subtitle": "管理 Codex 与 Claude Code 会话记录", "searchPlaceholder": "搜索会话内容、目录或 ID", + "searchSessions": "搜索会话", "providerFilterAll": "全部", "sessionList": "会话列表", "loadingSessions": "加载会话中...", @@ -435,7 +437,16 @@ "copySourcePath": "复制原始文件", "sourcePathCopied": "原始文件已复制", "loadingMessages": "加载会话内容中...", - "emptySession": "该会话暂无可展示内容" + "emptySession": "该会话暂无可展示内容", + "clickToCopyPath": "点击复制路径", + "tocTitle": "对话目录", + "justNow": "刚刚", + "minutesAgo": "{{count}} 分钟前", + "hoursAgo": "{{count}} 小时前", + "daysAgo": "{{count}} 天前", + "roleUser": "用户", + "roleSystem": "系统", + "roleTool": "工具" }, "console": { "providerSwitchReceived": "收到供应商切换事件:", @@ -640,6 +651,7 @@ "modelId": "模型 ID", "modelName": "显示名称", "noModels": "暂无模型配置", + "modelsRequired": "请至少添加一个模型配置", "providerKey": "供应商标识", "providerKeyPlaceholder": "my-provider", "providerKeyHint": "配置文件中的唯一标识符,创建后无法修改,只能使用小写字母、数字和连字符", diff --git a/src/lib/api/sessions.ts b/src/lib/api/sessions.ts index 7233c18bd..4c621bce6 100644 --- a/src/lib/api/sessions.ts +++ b/src/lib/api/sessions.ts @@ -14,14 +14,12 @@ export const sessionsApi = { }, async launchTerminal(options: { - target: string; command: string; cwd?: string | null; customConfig?: string | null; }): Promise { - const { target, command, cwd, customConfig } = options; + const { command, cwd, customConfig } = options; return await invoke("launch_session_terminal", { - target, command, cwd, customConfig,