mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 18:33:04 +08:00
merge: resolve conflict in SessionManagerPage with main
Accept main's removal of local terminalTarget state, which was replaced by global settings in refactor(terminal) commit.
This commit is contained in:
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [3.10.3] - 2026-01-30
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original pric
|
|||||||
<td>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! <a href="https://www.dmxapi.cn/register?aff=bUHu">Register here</a></td>
|
<td>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! <a href="https://www.dmxapi.cn/register?aff=bUHu">Register here</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://aicoding.sh/i/CCSWITCH"><img src="assets/partners/logos/aicoding.jpg" alt="AICoding" width="150"></a></td>
|
||||||
|
<td>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 <a href="https://aicoding.sh/i/CCSWITCH">this link</a> get 10% off their first top-up!</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
|
|||||||
<td>DMXAPI のご支援に感謝します!DMXAPI は 200 社以上の企業ユーザーにグローバル大規模モデル API サービスを提供しています。1 つの API キーで全世界のモデルにアクセス可能。即時請求書発行、同時接続数無制限、最低 $0.15 から、24 時間年中無休のテクニカルサポート。GPT/Claude/Gemini が全て 32% オフ、国内モデルは 20〜50% オフ、Claude Code 専用モデルは 66% オフ実施中!<a href="https://www.dmxapi.cn/register?aff=bUHu">登録はこちら</a></td>
|
<td>DMXAPI のご支援に感謝します!DMXAPI は 200 社以上の企業ユーザーにグローバル大規模モデル API サービスを提供しています。1 つの API キーで全世界のモデルにアクセス可能。即時請求書発行、同時接続数無制限、最低 $0.15 から、24 時間年中無休のテクニカルサポート。GPT/Claude/Gemini が全て 32% オフ、国内モデルは 20〜50% オフ、Claude Code 専用モデルは 66% オフ実施中!<a href="https://www.dmxapi.cn/register?aff=bUHu">登録はこちら</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://aicoding.sh/i/CCSWITCH"><img src="assets/partners/logos/aicoding.jpg" alt="AICoding" width="150"></a></td>
|
||||||
|
<td>AICoding.sh のご支援に感謝します!AICoding.sh —— グローバル AI モデル API 超お得な中継サービス!Claude Code 81% オフ、GPT 99% オフ!数百社の企業に高コストパフォーマンスの AI サービスを提供。Claude Code、GPT、Gemini および国内主要モデルに対応、エンタープライズ級の高同時接続、迅速な請求書発行、24 時間年中無休の専属テクニカルサポート。<a href="https://aicoding.sh/i/CCSWITCH">こちらのリンク</a>から登録した CC Switch ユーザーは、初回チャージ 10% オフ!</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## スクリーンショット
|
## スクリーンショット
|
||||||
|
|||||||
@@ -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折进行中!<a href="https://www.dmxapi.cn/register?aff=bUHu">点击这里注册</a></td>
|
为200多家企业用户提供全球大模型API服务。· 充值即开票 ·当天开票 ·并发不限制 ·1元起充 · 7x24 在线技术辅导,GPT/Claude/Gemini全部6.8折,国内模型5~8折,Claude Code 专属模型3.4折进行中!<a href="https://www.dmxapi.cn/register?aff=bUHu">点击这里注册</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="180"><a href="https://aicoding.sh/i/CCSWITCH"><img src="assets/partners/logos/aicoding.jpg" alt="AICoding" width="150"></a></td>
|
||||||
|
<td>感谢 AICoding.sh 赞助了本项目!AICoding.sh —— 全球大模型 API 超值中转服务!Claude Code 1.9 折,GPT 0.1 折,已为数百家企业提供高性价比 AI 服务。支持 Claude Code、GPT、Gemini 及国内主流模型,企业级高并发、极速开票、7×24 专属技术支持,通过<a href="https://aicoding.sh/i/CCSWITCH">此链接</a> 注册的 CC Switch 用户,首充可享受九折优惠!</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## 界面预览
|
## 界面预览
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -652,6 +652,7 @@ exec bash --norc --noprofile
|
|||||||
"alacritty" => launch_macos_open_app("Alacritty", &script_file, true),
|
"alacritty" => launch_macos_open_app("Alacritty", &script_file, true),
|
||||||
"kitty" => launch_macos_open_app("kitty", &script_file, false),
|
"kitty" => launch_macos_open_app("kitty", &script_file, false),
|
||||||
"ghostty" => launch_macos_open_app("Ghostty", &script_file, true),
|
"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
|
_ => 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(())
|
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())
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,16 +26,24 @@ pub async fn get_session_messages(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn launch_session_terminal(
|
pub async fn launch_session_terminal(
|
||||||
target: String,
|
|
||||||
command: String,
|
command: String,
|
||||||
cwd: Option<String>,
|
cwd: Option<String>,
|
||||||
custom_config: Option<String>,
|
custom_config: Option<String>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
let target = target.clone();
|
|
||||||
let cwd = cwd.clone();
|
let cwd = cwd.clone();
|
||||||
let custom_config = custom_config.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 || {
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
session_manager::terminal::launch_terminal(
|
session_manager::terminal::launch_terminal(
|
||||||
&target,
|
&target,
|
||||||
|
|||||||
+42
-5
@@ -7,16 +7,25 @@ use crate::error::AppError;
|
|||||||
|
|
||||||
/// 获取用户主目录,带回退和日志
|
/// 获取用户主目录,带回退和日志
|
||||||
///
|
///
|
||||||
/// On Windows, respects the `HOME` environment variable (if set) to support
|
/// ## Windows 注意事项
|
||||||
/// test isolation. Falls back to `dirs::home_dir()` otherwise.
|
///
|
||||||
|
/// - `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 {
|
pub fn get_home_dir() -> PathBuf {
|
||||||
#[cfg(windows)]
|
if let Ok(home) = std::env::var("CC_SWITCH_TEST_HOME") {
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
|
||||||
let trimmed = home.trim();
|
let trimmed = home.trim();
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
return PathBuf::from(trimmed);
|
return PathBuf::from(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dirs::home_dir().unwrap_or_else(|| {
|
dirs::home_dir().unwrap_or_else(|| {
|
||||||
log::warn!("无法获取用户主目录,回退到当前目录");
|
log::warn!("无法获取用户主目录,回退到当前目录");
|
||||||
PathBuf::from(".")
|
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() {
|
if let Some(custom) = crate::app_store::get_app_config_dir_override() {
|
||||||
return custom;
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取应用配置文件路径
|
/// 获取应用配置文件路径
|
||||||
|
|||||||
@@ -959,6 +959,8 @@ pub fn run() {
|
|||||||
commands::test_proxy_url,
|
commands::test_proxy_url,
|
||||||
commands::get_upstream_proxy_status,
|
commands::get_upstream_proxy_status,
|
||||||
commands::scan_local_proxies,
|
commands::scan_local_proxies,
|
||||||
|
// Window theme control
|
||||||
|
commands::set_window_theme,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let app = builder
|
let app = builder
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"height": 650,
|
"height": 650,
|
||||||
"minWidth": 900,
|
"minWidth": 900,
|
||||||
"minHeight": 600,
|
"minHeight": 600,
|
||||||
|
"visible": false,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"center": true
|
"center": true
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"label": "main",
|
"label": "main",
|
||||||
"title": "CC Switch",
|
"title": "CC Switch",
|
||||||
"titleBarStyle": "Visible",
|
"titleBarStyle": "Visible",
|
||||||
|
"visible": false,
|
||||||
"minWidth": 900,
|
"minWidth": 900,
|
||||||
"minHeight": 600
|
"minHeight": 600
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ pub fn ensure_test_home() -> &'static Path {
|
|||||||
let _ = std::fs::remove_dir_all(&base);
|
let _ = std::fs::remove_dir_all(&base);
|
||||||
}
|
}
|
||||||
std::fs::create_dir_all(&base).expect("create test home");
|
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);
|
std::env::set_var("HOME", &base);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
std::env::set_var("USERPROFILE", &base);
|
std::env::set_var("USERPROFILE", &base);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import type { Provider } from "@/types";
|
|||||||
import type { AppId } from "@/lib/api";
|
import type { AppId } from "@/lib/api";
|
||||||
import { providersApi } from "@/lib/api/providers";
|
import { providersApi } from "@/lib/api/providers";
|
||||||
import { useDragSort } from "@/hooks/useDragSort";
|
import { useDragSort } from "@/hooks/useDragSort";
|
||||||
import { useStreamCheck } from "@/hooks/useStreamCheck";
|
// import { useStreamCheck } from "@/hooks/useStreamCheck"; // 测试功能已隐藏
|
||||||
import { ProviderCard } from "@/components/providers/ProviderCard";
|
import { ProviderCard } from "@/components/providers/ProviderCard";
|
||||||
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
|
||||||
import {
|
import {
|
||||||
@@ -93,8 +93,8 @@ export function ProviderList({
|
|||||||
[appId, opencodeLiveIds],
|
[appId, opencodeLiveIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 流式健康检查
|
// 流式健康检查 - 功能已隐藏
|
||||||
const { checkProvider, isChecking } = useStreamCheck(appId);
|
// const { checkProvider, isChecking } = useStreamCheck(appId);
|
||||||
|
|
||||||
// 故障转移相关
|
// 故障转移相关
|
||||||
const { data: isAutoFailoverEnabled } = useAutoFailoverEnabled(appId);
|
const { data: isAutoFailoverEnabled } = useAutoFailoverEnabled(appId);
|
||||||
@@ -139,9 +139,10 @@ export function ProviderList({
|
|||||||
[appId, addToQueue, removeFromQueue],
|
[appId, addToQueue, removeFromQueue],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTest = (provider: Provider) => {
|
// handleTest 功能已隐藏 - 供应商请求格式复杂难以统一测试
|
||||||
checkProvider(provider.id, provider.name);
|
// const handleTest = (provider: Provider) => {
|
||||||
};
|
// checkProvider(provider.id, provider.name);
|
||||||
|
// };
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
@@ -229,8 +230,9 @@ export function ProviderList({
|
|||||||
onConfigureUsage={onConfigureUsage}
|
onConfigureUsage={onConfigureUsage}
|
||||||
onOpenWebsite={onOpenWebsite}
|
onOpenWebsite={onOpenWebsite}
|
||||||
onOpenTerminal={onOpenTerminal}
|
onOpenTerminal={onOpenTerminal}
|
||||||
onTest={appId !== "opencode" ? handleTest : undefined}
|
// onTest 功能已隐藏 - 供应商请求格式复杂难以统一测试
|
||||||
isTesting={isChecking(provider.id)}
|
// onTest={appId !== "opencode" ? handleTest : undefined}
|
||||||
|
isTesting={false} // isChecking(provider.id) - 测试功能已隐藏
|
||||||
isProxyRunning={isProxyRunning}
|
isProxyRunning={isProxyRunning}
|
||||||
isProxyTakeover={isProxyTakeover}
|
isProxyTakeover={isProxyTakeover}
|
||||||
// 故障转移相关:联动状态
|
// 故障转移相关:联动状态
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ export function ProviderForm({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenCode: validate provider key
|
// OpenCode: validate provider key and models
|
||||||
if (appId === "opencode") {
|
if (appId === "opencode") {
|
||||||
const keyPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
const keyPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||||
if (!opencodeProviderKey.trim()) {
|
if (!opencodeProviderKey.trim()) {
|
||||||
@@ -761,6 +761,11 @@ export function ProviderForm({
|
|||||||
toast.error(t("opencode.providerKeyDuplicate"));
|
toast.error(t("opencode.providerKeyDuplicate"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Validate that at least one model is configured
|
||||||
|
if (Object.keys(opencodeModels).length === 0) {
|
||||||
|
toast.error(t("opencode.modelsRequired"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非官方供应商必填校验:端点和 API Key
|
// 非官方供应商必填校验:端点和 API Key
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function SessionItem({
|
|||||||
<div className="flex items-center gap-1 text-[11px] text-muted-foreground">
|
<div className="flex items-center gap-1 text-[11px] text-muted-foreground">
|
||||||
<Clock className="size-3" />
|
<Clock className="size-3" />
|
||||||
<span>
|
<span>
|
||||||
{lastActive ? formatRelativeTime(lastActive) : t("common.unknown")}
|
{lastActive ? formatRelativeTime(lastActive, t) : t("common.unknown")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import {
|
|||||||
MessageSquare,
|
MessageSquare,
|
||||||
Clock,
|
Clock,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
Terminal,
|
|
||||||
X,
|
X,
|
||||||
SquareTerminal,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useSessionMessagesQuery, useSessionsQuery } from "@/lib/query";
|
import { useSessionMessagesQuery, useSessionsQuery } from "@/lib/query";
|
||||||
import { sessionsApi } from "@/lib/api";
|
import { sessionsApi } from "@/lib/api";
|
||||||
@@ -24,7 +22,6 @@ import {
|
|||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
@@ -37,13 +34,6 @@ import {
|
|||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
import { isMac } from "@/lib/platform";
|
import { isMac } from "@/lib/platform";
|
||||||
import { ProviderIcon } from "@/components/ProviderIcon";
|
import { ProviderIcon } from "@/components/ProviderIcon";
|
||||||
import {
|
|
||||||
AlacrittyIcon,
|
|
||||||
GhosttyIcon,
|
|
||||||
ITermIcon,
|
|
||||||
KittyIcon,
|
|
||||||
WezTermIcon,
|
|
||||||
} from "@/components/icons/TerminalIcons";
|
|
||||||
import { SessionItem } from "./SessionItem";
|
import { SessionItem } from "./SessionItem";
|
||||||
import { SessionMessageItem } from "./SessionMessageItem";
|
import { SessionMessageItem } from "./SessionMessageItem";
|
||||||
import { SessionTocDialog, SessionTocSidebar } from "./SessionToc";
|
import { SessionTocDialog, SessionTocSidebar } from "./SessionToc";
|
||||||
@@ -56,16 +46,6 @@ import {
|
|||||||
getSessionKey,
|
getSessionKey,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
const TERMINAL_TARGET_KEY = "session_manager_terminal_target";
|
|
||||||
|
|
||||||
type TerminalTarget =
|
|
||||||
| "terminal"
|
|
||||||
| "iterm"
|
|
||||||
| "ghostty"
|
|
||||||
| "kitty"
|
|
||||||
| "wezterm"
|
|
||||||
| "alacritty";
|
|
||||||
|
|
||||||
type ProviderFilter = "all" | "codex" | "claude";
|
type ProviderFilter = "all" | "codex" | "claude";
|
||||||
|
|
||||||
export function SessionManagerPage() {
|
export function SessionManagerPage() {
|
||||||
@@ -85,26 +65,6 @@ export function SessionManagerPage() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [providerFilter, setProviderFilter] = useState<ProviderFilter>("all");
|
const [providerFilter, setProviderFilter] = useState<ProviderFilter>("all");
|
||||||
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
||||||
const [terminalTarget, setTerminalTarget] =
|
|
||||||
useState<TerminalTarget>("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 全文搜索
|
// 使用 FlexSearch 全文搜索
|
||||||
const { search: searchSessions } = useSessionSearch({
|
const { search: searchSessions } = useSessionSearch({
|
||||||
@@ -205,7 +165,6 @@ export function SessionManagerPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await sessionsApi.launchTerminal({
|
await sessionsApi.launchTerminal({
|
||||||
target: terminalTarget,
|
|
||||||
command: selectedSession.resumeCommand,
|
command: selectedSession.resumeCommand,
|
||||||
cwd: selectedSession.projectDir ?? undefined,
|
cwd: selectedSession.projectDir ?? undefined,
|
||||||
});
|
});
|
||||||
@@ -288,7 +247,9 @@ export function SessionManagerPage() {
|
|||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>搜索会话</TooltipContent>
|
<TooltipContent>
|
||||||
|
{t("sessionManager.searchSessions")}
|
||||||
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -482,7 +443,7 @@ export function SessionManagerPage() {
|
|||||||
{selectedSession.projectDir}
|
{selectedSession.projectDir}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-muted-foreground mt-1">
|
<p className="text-muted-foreground mt-1">
|
||||||
点击复制路径
|
{t("sessionManager.clickToCopyPath")}
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -493,84 +454,32 @@ export function SessionManagerPage() {
|
|||||||
{/* 右侧:操作按钮组 */}
|
{/* 右侧:操作按钮组 */}
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
{isMac() && (
|
{isMac() && (
|
||||||
<>
|
<Tooltip>
|
||||||
<Select
|
<TooltipTrigger asChild>
|
||||||
value={terminalTarget}
|
<Button
|
||||||
onValueChange={(value) =>
|
size="sm"
|
||||||
setTerminalTarget(value as TerminalTarget)
|
className="gap-1.5"
|
||||||
}
|
onClick={() => void handleResume()}
|
||||||
>
|
disabled={!selectedSession.resumeCommand}
|
||||||
<SelectTrigger className="h-8 min-w-[110px] w-auto text-xs px-2.5">
|
>
|
||||||
<Terminal className="size-3 mr-1.5" />
|
<Play className="size-3.5" />
|
||||||
<SelectValue />
|
<span className="hidden sm:inline">
|
||||||
</SelectTrigger>
|
{t("sessionManager.resume", {
|
||||||
<SelectContent align="end">
|
defaultValue: "恢复会话",
|
||||||
<SelectItem value="terminal">
|
})}
|
||||||
<div className="flex items-center gap-2">
|
</span>
|
||||||
<SquareTerminal className="size-3.5" />
|
</Button>
|
||||||
<span>Terminal</span>
|
</TooltipTrigger>
|
||||||
</div>
|
<TooltipContent>
|
||||||
</SelectItem>
|
{selectedSession.resumeCommand
|
||||||
<SelectItem value="iterm">
|
? t("sessionManager.resumeTooltip", {
|
||||||
<div className="flex items-center gap-2">
|
defaultValue: "在终端中恢复此会话",
|
||||||
<ITermIcon className="size-3.5" />
|
})
|
||||||
<span>iTerm2 (Untested)</span>
|
: t("sessionManager.noResumeCommand", {
|
||||||
</div>
|
defaultValue: "此会话无法恢复",
|
||||||
</SelectItem>
|
})}
|
||||||
<SelectItem value="ghostty">
|
</TooltipContent>
|
||||||
<div className="flex items-center gap-2">
|
</Tooltip>
|
||||||
<GhosttyIcon className="size-3.5" />
|
|
||||||
<span>Ghostty</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="kitty">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<KittyIcon className="size-3.5" />
|
|
||||||
<span>Kitty</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="wezterm">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<WezTermIcon className="size-3.5" />
|
|
||||||
<span>WezTerm (Untested)</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="alacritty">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AlacrittyIcon className="size-3.5" />
|
|
||||||
<span>Alacritty (Untested)</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="gap-1.5"
|
|
||||||
onClick={() => void handleResume()}
|
|
||||||
disabled={!selectedSession.resumeCommand}
|
|
||||||
>
|
|
||||||
<Play className="size-3.5" />
|
|
||||||
<span className="hidden sm:inline">
|
|
||||||
{t("sessionManager.resume", {
|
|
||||||
defaultValue: "恢复会话",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
{selectedSession.resumeCommand
|
|
||||||
? t("sessionManager.resumeTooltip", {
|
|
||||||
defaultValue: "在终端中恢复此会话",
|
|
||||||
})
|
|
||||||
: t("sessionManager.noResumeCommand", {
|
|
||||||
defaultValue: "此会话无法恢复",
|
|
||||||
})}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function SessionMessageItem({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className="flex items-center justify-between text-xs mb-1.5 pr-6">
|
<div className="flex items-center justify-between text-xs mb-1.5 pr-6">
|
||||||
<span className={cn("font-semibold", getRoleTone(message.role))}>
|
<span className={cn("font-semibold", getRoleTone(message.role))}>
|
||||||
{getRoleLabel(message.role)}
|
{getRoleLabel(message.role, t)}
|
||||||
</span>
|
</span>
|
||||||
{message.ts && (
|
{message.ts && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { List, X } from "lucide-react";
|
import { List, X } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
@@ -26,6 +27,7 @@ export function SessionTocSidebar({
|
|||||||
items,
|
items,
|
||||||
onItemClick,
|
onItemClick,
|
||||||
}: SessionTocSidebarProps) {
|
}: SessionTocSidebarProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (items.length <= 2) return null;
|
if (items.length <= 2) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,7 +35,7 @@ export function SessionTocSidebar({
|
|||||||
<div className="p-3 border-b">
|
<div className="p-3 border-b">
|
||||||
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
||||||
<List className="size-3.5" />
|
<List className="size-3.5" />
|
||||||
<span>对话目录</span>
|
<span>{t("sessionManager.tocTitle")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="h-[calc(100%-40px)]">
|
<ScrollArea className="h-[calc(100%-40px)]">
|
||||||
@@ -74,6 +76,7 @@ export function SessionTocDialog({
|
|||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: SessionTocDialogProps) {
|
}: SessionTocDialogProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (items.length <= 2) return null;
|
if (items.length <= 2) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -95,11 +98,11 @@ export function SessionTocDialog({
|
|||||||
<DialogHeader className="px-4 py-3 relative border-b">
|
<DialogHeader className="px-4 py-3 relative border-b">
|
||||||
<DialogTitle className="flex items-center gap-2 text-base font-semibold">
|
<DialogTitle className="flex items-center gap-2 text-base font-semibold">
|
||||||
<List className="size-4 text-primary" />
|
<List className="size-4 text-primary" />
|
||||||
对话目录
|
{t("sessionManager.tocTitle")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogClose
|
<DialogClose
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1.5 hover:bg-muted transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1.5 hover:bg-muted transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
aria-label="关闭"
|
aria-label={t("common.close")}
|
||||||
>
|
>
|
||||||
<X className="size-4 text-muted-foreground" />
|
<X className="size-4 text-muted-foreground" />
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ export const formatTimestamp = (value?: number) => {
|
|||||||
return new Date(value).toLocaleString();
|
return new Date(value).toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatRelativeTime = (value?: number) => {
|
export const formatRelativeTime = (
|
||||||
|
value: number | undefined,
|
||||||
|
t: (key: string, options?: Record<string, unknown>) => string,
|
||||||
|
) => {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const diff = now - value;
|
const diff = now - value;
|
||||||
@@ -25,10 +28,10 @@ export const formatRelativeTime = (value?: number) => {
|
|||||||
const hours = Math.floor(diff / 3600000);
|
const hours = Math.floor(diff / 3600000);
|
||||||
const days = Math.floor(diff / 86400000);
|
const days = Math.floor(diff / 86400000);
|
||||||
|
|
||||||
if (minutes < 1) return "刚刚";
|
if (minutes < 1) return t("sessionManager.justNow");
|
||||||
if (minutes < 60) return `${minutes} 分钟前`;
|
if (minutes < 60) return t("sessionManager.minutesAgo", { count: minutes });
|
||||||
if (hours < 24) return `${hours} 小时前`;
|
if (hours < 24) return t("sessionManager.hoursAgo", { count: hours });
|
||||||
if (days < 7) return `${days} 天前`;
|
if (days < 7) return t("sessionManager.daysAgo", { count: days });
|
||||||
return new Date(value).toLocaleDateString();
|
return new Date(value).toLocaleDateString();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,12 +60,12 @@ export const getRoleTone = (role: string) => {
|
|||||||
return "text-muted-foreground";
|
return "text-muted-foreground";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRoleLabel = (role: string) => {
|
export const getRoleLabel = (role: string, t: (key: string) => string) => {
|
||||||
const normalized = role.toLowerCase();
|
const normalized = role.toLowerCase();
|
||||||
if (normalized === "assistant") return "AI";
|
if (normalized === "assistant") return "AI";
|
||||||
if (normalized === "user") return "用户";
|
if (normalized === "user") return t("sessionManager.roleUser");
|
||||||
if (normalized === "system") return "系统";
|
if (normalized === "system") return t("sessionManager.roleSystem");
|
||||||
if (normalized === "tool") return "工具";
|
if (normalized === "tool") return t("sessionManager.roleTool");
|
||||||
return role;
|
return role;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const MACOS_TERMINALS = [
|
|||||||
{ value: "alacritty", labelKey: "settings.terminal.options.macos.alacritty" },
|
{ value: "alacritty", labelKey: "settings.terminal.options.macos.alacritty" },
|
||||||
{ value: "kitty", labelKey: "settings.terminal.options.macos.kitty" },
|
{ value: "kitty", labelKey: "settings.terminal.options.macos.kitty" },
|
||||||
{ value: "ghostty", labelKey: "settings.terminal.options.macos.ghostty" },
|
{ value: "ghostty", labelKey: "settings.terminal.options.macos.ghostty" },
|
||||||
|
{ value: "wezterm", labelKey: "settings.terminal.options.macos.wezterm" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const WINDOWS_TERMINALS = [
|
const WINDOWS_TERMINALS = [
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
type Theme = "light" | "dark" | "system";
|
type Theme = "light" | "dark" | "system";
|
||||||
|
|
||||||
@@ -94,6 +95,54 @@ export function ThemeProvider({
|
|||||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||||
}, [theme]);
|
}, [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<ThemeContextValue>(
|
const value = useMemo<ThemeContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
@@ -308,7 +308,8 @@
|
|||||||
"iterm2": "iTerm2",
|
"iterm2": "iTerm2",
|
||||||
"alacritty": "Alacritty",
|
"alacritty": "Alacritty",
|
||||||
"kitty": "Kitty",
|
"kitty": "Kitty",
|
||||||
"ghostty": "Ghostty"
|
"ghostty": "Ghostty",
|
||||||
|
"wezterm": "WezTerm"
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
"cmd": "Command Prompt",
|
"cmd": "Command Prompt",
|
||||||
@@ -412,6 +413,7 @@
|
|||||||
"title": "Session Manager",
|
"title": "Session Manager",
|
||||||
"subtitle": "Manage Codex and Claude Code sessions",
|
"subtitle": "Manage Codex and Claude Code sessions",
|
||||||
"searchPlaceholder": "Search by content, directory, or ID",
|
"searchPlaceholder": "Search by content, directory, or ID",
|
||||||
|
"searchSessions": "Search sessions",
|
||||||
"providerFilterAll": "All",
|
"providerFilterAll": "All",
|
||||||
"sessionList": "Sessions",
|
"sessionList": "Sessions",
|
||||||
"loadingSessions": "Loading sessions...",
|
"loadingSessions": "Loading sessions...",
|
||||||
@@ -435,7 +437,16 @@
|
|||||||
"copySourcePath": "Copy source file",
|
"copySourcePath": "Copy source file",
|
||||||
"sourcePathCopied": "Source file copied",
|
"sourcePathCopied": "Source file copied",
|
||||||
"loadingMessages": "Loading transcript...",
|
"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": {
|
"console": {
|
||||||
"providerSwitchReceived": "Received provider switch event:",
|
"providerSwitchReceived": "Received provider switch event:",
|
||||||
@@ -640,6 +651,7 @@
|
|||||||
"modelId": "Model ID",
|
"modelId": "Model ID",
|
||||||
"modelName": "Display Name",
|
"modelName": "Display Name",
|
||||||
"noModels": "No models configured",
|
"noModels": "No models configured",
|
||||||
|
"modelsRequired": "Please add at least one model",
|
||||||
"providerKey": "Provider Key",
|
"providerKey": "Provider Key",
|
||||||
"providerKeyPlaceholder": "my-provider",
|
"providerKeyPlaceholder": "my-provider",
|
||||||
"providerKeyHint": "Unique identifier in config file. Cannot be changed after creation. Use lowercase letters, numbers, and hyphens only.",
|
"providerKeyHint": "Unique identifier in config file. Cannot be changed after creation. Use lowercase letters, numbers, and hyphens only.",
|
||||||
|
|||||||
@@ -308,7 +308,8 @@
|
|||||||
"iterm2": "iTerm2",
|
"iterm2": "iTerm2",
|
||||||
"alacritty": "Alacritty",
|
"alacritty": "Alacritty",
|
||||||
"kitty": "Kitty",
|
"kitty": "Kitty",
|
||||||
"ghostty": "Ghostty"
|
"ghostty": "Ghostty",
|
||||||
|
"wezterm": "WezTerm"
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
"cmd": "コマンドプロンプト",
|
"cmd": "コマンドプロンプト",
|
||||||
@@ -412,6 +413,7 @@
|
|||||||
"title": "セッション管理",
|
"title": "セッション管理",
|
||||||
"subtitle": "Codex / Claude Code のセッションを管理",
|
"subtitle": "Codex / Claude Code のセッションを管理",
|
||||||
"searchPlaceholder": "内容・ディレクトリ・ID で検索",
|
"searchPlaceholder": "内容・ディレクトリ・ID で検索",
|
||||||
|
"searchSessions": "セッションを検索",
|
||||||
"providerFilterAll": "すべて",
|
"providerFilterAll": "すべて",
|
||||||
"sessionList": "セッション一覧",
|
"sessionList": "セッション一覧",
|
||||||
"loadingSessions": "セッションを読み込み中...",
|
"loadingSessions": "セッションを読み込み中...",
|
||||||
@@ -435,7 +437,16 @@
|
|||||||
"copySourcePath": "元ファイルをコピー",
|
"copySourcePath": "元ファイルをコピー",
|
||||||
"sourcePathCopied": "元ファイルをコピーしました",
|
"sourcePathCopied": "元ファイルをコピーしました",
|
||||||
"loadingMessages": "内容を読み込み中...",
|
"loadingMessages": "内容を読み込み中...",
|
||||||
"emptySession": "表示できる内容がありません"
|
"emptySession": "表示できる内容がありません",
|
||||||
|
"clickToCopyPath": "クリックしてパスをコピー",
|
||||||
|
"tocTitle": "目次",
|
||||||
|
"justNow": "たった今",
|
||||||
|
"minutesAgo": "{{count}}分前",
|
||||||
|
"hoursAgo": "{{count}}時間前",
|
||||||
|
"daysAgo": "{{count}}日前",
|
||||||
|
"roleUser": "ユーザー",
|
||||||
|
"roleSystem": "システム",
|
||||||
|
"roleTool": "ツール"
|
||||||
},
|
},
|
||||||
"console": {
|
"console": {
|
||||||
"providerSwitchReceived": "プロバイダー切り替えイベントを受信:",
|
"providerSwitchReceived": "プロバイダー切り替えイベントを受信:",
|
||||||
@@ -640,6 +651,7 @@
|
|||||||
"modelId": "モデル ID",
|
"modelId": "モデル ID",
|
||||||
"modelName": "表示名",
|
"modelName": "表示名",
|
||||||
"noModels": "モデルが設定されていません",
|
"noModels": "モデルが設定されていません",
|
||||||
|
"modelsRequired": "モデルを少なくとも1つ追加してください",
|
||||||
"providerKey": "プロバイダーキー",
|
"providerKey": "プロバイダーキー",
|
||||||
"providerKeyPlaceholder": "my-provider",
|
"providerKeyPlaceholder": "my-provider",
|
||||||
"providerKeyHint": "設定ファイルの一意の識別子。作成後は変更できません。小文字、数字、ハイフンのみ使用できます。",
|
"providerKeyHint": "設定ファイルの一意の識別子。作成後は変更できません。小文字、数字、ハイフンのみ使用できます。",
|
||||||
|
|||||||
@@ -308,7 +308,8 @@
|
|||||||
"iterm2": "iTerm2",
|
"iterm2": "iTerm2",
|
||||||
"alacritty": "Alacritty",
|
"alacritty": "Alacritty",
|
||||||
"kitty": "Kitty",
|
"kitty": "Kitty",
|
||||||
"ghostty": "Ghostty"
|
"ghostty": "Ghostty",
|
||||||
|
"wezterm": "WezTerm"
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
"cmd": "命令提示符",
|
"cmd": "命令提示符",
|
||||||
@@ -412,6 +413,7 @@
|
|||||||
"title": "会话管理",
|
"title": "会话管理",
|
||||||
"subtitle": "管理 Codex 与 Claude Code 会话记录",
|
"subtitle": "管理 Codex 与 Claude Code 会话记录",
|
||||||
"searchPlaceholder": "搜索会话内容、目录或 ID",
|
"searchPlaceholder": "搜索会话内容、目录或 ID",
|
||||||
|
"searchSessions": "搜索会话",
|
||||||
"providerFilterAll": "全部",
|
"providerFilterAll": "全部",
|
||||||
"sessionList": "会话列表",
|
"sessionList": "会话列表",
|
||||||
"loadingSessions": "加载会话中...",
|
"loadingSessions": "加载会话中...",
|
||||||
@@ -435,7 +437,16 @@
|
|||||||
"copySourcePath": "复制原始文件",
|
"copySourcePath": "复制原始文件",
|
||||||
"sourcePathCopied": "原始文件已复制",
|
"sourcePathCopied": "原始文件已复制",
|
||||||
"loadingMessages": "加载会话内容中...",
|
"loadingMessages": "加载会话内容中...",
|
||||||
"emptySession": "该会话暂无可展示内容"
|
"emptySession": "该会话暂无可展示内容",
|
||||||
|
"clickToCopyPath": "点击复制路径",
|
||||||
|
"tocTitle": "对话目录",
|
||||||
|
"justNow": "刚刚",
|
||||||
|
"minutesAgo": "{{count}} 分钟前",
|
||||||
|
"hoursAgo": "{{count}} 小时前",
|
||||||
|
"daysAgo": "{{count}} 天前",
|
||||||
|
"roleUser": "用户",
|
||||||
|
"roleSystem": "系统",
|
||||||
|
"roleTool": "工具"
|
||||||
},
|
},
|
||||||
"console": {
|
"console": {
|
||||||
"providerSwitchReceived": "收到供应商切换事件:",
|
"providerSwitchReceived": "收到供应商切换事件:",
|
||||||
@@ -640,6 +651,7 @@
|
|||||||
"modelId": "模型 ID",
|
"modelId": "模型 ID",
|
||||||
"modelName": "显示名称",
|
"modelName": "显示名称",
|
||||||
"noModels": "暂无模型配置",
|
"noModels": "暂无模型配置",
|
||||||
|
"modelsRequired": "请至少添加一个模型配置",
|
||||||
"providerKey": "供应商标识",
|
"providerKey": "供应商标识",
|
||||||
"providerKeyPlaceholder": "my-provider",
|
"providerKeyPlaceholder": "my-provider",
|
||||||
"providerKeyHint": "配置文件中的唯一标识符,创建后无法修改,只能使用小写字母、数字和连字符",
|
"providerKeyHint": "配置文件中的唯一标识符,创建后无法修改,只能使用小写字母、数字和连字符",
|
||||||
|
|||||||
@@ -14,14 +14,12 @@ export const sessionsApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async launchTerminal(options: {
|
async launchTerminal(options: {
|
||||||
target: string;
|
|
||||||
command: string;
|
command: string;
|
||||||
cwd?: string | null;
|
cwd?: string | null;
|
||||||
customConfig?: string | null;
|
customConfig?: string | null;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
const { target, command, cwd, customConfig } = options;
|
const { command, cwd, customConfig } = options;
|
||||||
return await invoke("launch_session_terminal", {
|
return await invoke("launch_session_terminal", {
|
||||||
target,
|
|
||||||
command,
|
command,
|
||||||
cwd,
|
cwd,
|
||||||
customConfig,
|
customConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user