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
+
+
+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.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.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")}
+
- 点击复制路径
+ {t("sessionManager.clickToCopyPath")}
@@ -493,84 +454,32 @@ export function SessionManagerPage() {
{/* 右侧:操作按钮组 */}
{isMac() && (
- <>
-
- setTerminalTarget(value as TerminalTarget)
- }
- >
-
-
-
-
-
-
-
-
- Terminal
-
-
-
-
-
- iTerm2 (Untested)
-
-
-
-
-
- Ghostty
-
-
-
-
-
- Kitty
-
-
-
-
-
- WezTerm (Untested)
-
-
-
-
-
-
Alacritty (Untested)
-
-
-
-
-
-
-
- void handleResume()}
- disabled={!selectedSession.resumeCommand}
- >
-
-
- {t("sessionManager.resume", {
- defaultValue: "恢复会话",
- })}
-
-
-
-
- {selectedSession.resumeCommand
- ? t("sessionManager.resumeTooltip", {
- defaultValue: "在终端中恢复此会话",
- })
- : t("sessionManager.noResumeCommand", {
- defaultValue: "此会话无法恢复",
- })}
-
-
- >
+
+
+ void handleResume()}
+ disabled={!selectedSession.resumeCommand}
+ >
+
+
+ {t("sessionManager.resume", {
+ 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,