mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(profiles): split Claude Desktop into independent profile scope
This commit is contained in:
@@ -44,6 +44,7 @@ impl From<Profile> for ProfileDto {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CurrentProfileIds {
|
pub struct CurrentProfileIds {
|
||||||
pub claude: Option<String>,
|
pub claude: Option<String>,
|
||||||
|
pub claude_desktop: Option<String>,
|
||||||
pub codex: Option<String>,
|
pub codex: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +100,10 @@ pub fn list_profiles(state: State<'_, AppState>) -> Result<ProfilesResponse, Str
|
|||||||
.db
|
.db
|
||||||
.get_current_profile_id(ProfileScope::Claude.as_str())
|
.get_current_profile_id(ProfileScope::Claude.as_str())
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
|
claude_desktop: state
|
||||||
|
.db
|
||||||
|
.get_current_profile_id(ProfileScope::ClaudeDesktop.as_str())
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
codex: state
|
codex: state
|
||||||
.db
|
.db
|
||||||
.get_current_profile_id(ProfileScope::Codex.as_str())
|
.get_current_profile_id(ProfileScope::Codex.as_str())
|
||||||
|
|||||||
@@ -23,27 +23,32 @@ use crate::error::AppError;
|
|||||||
use crate::services::{McpService, PromptService, ProviderService, SkillService};
|
use crate::services::{McpService, PromptService, ProviderService, SkillService};
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
|
||||||
/// Profile 操作的应用分组:项目实体全应用共享,但快照/应用/当前指针按组进行
|
/// Profile 操作的应用分组:项目实体全应用共享,但快照/应用/当前指针按组进行。
|
||||||
///
|
///
|
||||||
/// Claude Desktop 并入 Claude 组:它在 cc-switch 里只有聊天侧供应商一个
|
/// Claude Code 与 Claude Desktop 的供应商在 cc-switch 中是独立切换的,
|
||||||
/// 受管维度,且其内嵌 Code 标签页读的就是 Claude Code 的那套 live 文件
|
/// 因此各自拥有独立的项目分组。两者 live 文件零交集
|
||||||
/// (`~/.claude/*`,天然跟随),不足以构成独立的"项目"维度。
|
///(`~/.claude` / `Application Support/Claude-3p`),分组切换互不干扰。
|
||||||
/// 供应商 live 文件两组零交集(`~/.claude` / `~/.codex` /
|
|
||||||
/// `Application Support/Claude-3p`),分组切换互不干扰。
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ProfileScope {
|
pub enum ProfileScope {
|
||||||
Claude,
|
Claude,
|
||||||
|
#[serde(rename = "claude-desktop")]
|
||||||
|
ClaudeDesktop,
|
||||||
Codex,
|
Codex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProfileScope {
|
impl ProfileScope {
|
||||||
/// 全部分组(扩展新分组时同步扩展 apps/for_app 与前端 scope.ts 镜像)
|
/// 全部分组(扩展新分组时同步扩展 apps/for_app 与前端 scope.ts 镜像)
|
||||||
pub const ALL: [ProfileScope; 2] = [ProfileScope::Claude, ProfileScope::Codex];
|
pub const ALL: [ProfileScope; 3] = [
|
||||||
|
ProfileScope::Claude,
|
||||||
|
ProfileScope::ClaudeDesktop,
|
||||||
|
ProfileScope::Codex,
|
||||||
|
];
|
||||||
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ProfileScope::Claude => "claude",
|
ProfileScope::Claude => "claude",
|
||||||
|
ProfileScope::ClaudeDesktop => "claude-desktop",
|
||||||
ProfileScope::Codex => "codex",
|
ProfileScope::Codex => "codex",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +56,7 @@ impl ProfileScope {
|
|||||||
pub fn parse(value: &str) -> Result<Self, AppError> {
|
pub fn parse(value: &str) -> Result<Self, AppError> {
|
||||||
match value {
|
match value {
|
||||||
"claude" => Ok(ProfileScope::Claude),
|
"claude" => Ok(ProfileScope::Claude),
|
||||||
|
"claude-desktop" => Ok(ProfileScope::ClaudeDesktop),
|
||||||
"codex" => Ok(ProfileScope::Codex),
|
"codex" => Ok(ProfileScope::Codex),
|
||||||
other => Err(AppError::InvalidInput(format!(
|
other => Err(AppError::InvalidInput(format!(
|
||||||
"Unknown profile scope: {other}"
|
"Unknown profile scope: {other}"
|
||||||
@@ -59,13 +65,10 @@ impl ProfileScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 组内受管应用(快照与 apply 只作用于这些 app 的槽位)
|
/// 组内受管应用(快照与 apply 只作用于这些 app 的槽位)
|
||||||
///
|
|
||||||
/// Claude Desktop 只有供应商一个活跃维度:MCP/Skills 的 `is_enabled_for`
|
|
||||||
/// 对它恒为 false(快照天然为空集)、prompt 无 live 文件(快照为 None),
|
|
||||||
/// apply 时空集 diff 与 None 都是 no-op,无需按维度特判。
|
|
||||||
pub fn apps(&self) -> &'static [AppType] {
|
pub fn apps(&self) -> &'static [AppType] {
|
||||||
match self {
|
match self {
|
||||||
ProfileScope::Claude => &[AppType::Claude, AppType::ClaudeDesktop],
|
ProfileScope::Claude => &[AppType::Claude],
|
||||||
|
ProfileScope::ClaudeDesktop => &[AppType::ClaudeDesktop],
|
||||||
ProfileScope::Codex => &[AppType::Codex],
|
ProfileScope::Codex => &[AppType::Codex],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +76,8 @@ impl ProfileScope {
|
|||||||
/// 应用页 → 所属分组(Profile 不支持的应用返回 None)
|
/// 应用页 → 所属分组(Profile 不支持的应用返回 None)
|
||||||
pub fn for_app(app: &AppType) -> Option<Self> {
|
pub fn for_app(app: &AppType) -> Option<Self> {
|
||||||
match app {
|
match app {
|
||||||
AppType::Claude | AppType::ClaudeDesktop => Some(ProfileScope::Claude),
|
AppType::Claude => Some(ProfileScope::Claude),
|
||||||
|
AppType::ClaudeDesktop => Some(ProfileScope::ClaudeDesktop),
|
||||||
AppType::Codex => Some(ProfileScope::Codex),
|
AppType::Codex => Some(ProfileScope::Codex),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -554,7 +558,11 @@ mod tests {
|
|||||||
payload.merge_scope_from(&fresh, ProfileScope::Claude);
|
payload.merge_scope_from(&fresh, ProfileScope::Claude);
|
||||||
|
|
||||||
assert_eq!(payload.providers.claude, Some("p2".to_string()));
|
assert_eq!(payload.providers.claude, Some("p2".to_string()));
|
||||||
assert_eq!(payload.providers.claude_desktop, None);
|
assert_eq!(
|
||||||
|
payload.providers.claude_desktop,
|
||||||
|
Some("d1".to_string()),
|
||||||
|
"claude-desktop slot is in its own scope, untouched by claude merge"
|
||||||
|
);
|
||||||
assert_eq!(payload.mcp.claude, Some(ids(&["m2"])));
|
assert_eq!(payload.mcp.claude, Some(ids(&["m2"])));
|
||||||
// codex 侧完好:既没被覆盖也没被 fresh 的值污染
|
// codex 侧完好:既没被覆盖也没被 fresh 的值污染
|
||||||
assert_eq!(payload.providers.codex, Some("c1".to_string()));
|
assert_eq!(payload.providers.codex, Some("c1".to_string()));
|
||||||
@@ -565,17 +573,20 @@ mod tests {
|
|||||||
fn test_scope_captured_detects_per_scope_snapshot() {
|
fn test_scope_captured_detects_per_scope_snapshot() {
|
||||||
let mut payload = ProfilePayload::default();
|
let mut payload = ProfilePayload::default();
|
||||||
assert!(!payload.scope_captured(ProfileScope::Claude));
|
assert!(!payload.scope_captured(ProfileScope::Claude));
|
||||||
|
assert!(!payload.scope_captured(ProfileScope::ClaudeDesktop));
|
||||||
assert!(!payload.scope_captured(ProfileScope::Codex));
|
assert!(!payload.scope_captured(ProfileScope::Codex));
|
||||||
|
|
||||||
// 只拍过 claude 组(哪怕拍到的是空集)
|
// 只拍过 claude 组(哪怕拍到的是空集)
|
||||||
payload.mcp.claude = Some(vec![]);
|
payload.mcp.claude = Some(vec![]);
|
||||||
assert!(payload.scope_captured(ProfileScope::Claude));
|
assert!(payload.scope_captured(ProfileScope::Claude));
|
||||||
|
assert!(!payload.scope_captured(ProfileScope::ClaudeDesktop));
|
||||||
assert!(!payload.scope_captured(ProfileScope::Codex));
|
assert!(!payload.scope_captured(ProfileScope::Codex));
|
||||||
|
|
||||||
// Desktop 槽位属于 claude 组
|
// Desktop 槽位属于独立的 claude-desktop 组
|
||||||
let mut desktop_only = ProfilePayload::default();
|
let mut desktop_only = ProfilePayload::default();
|
||||||
desktop_only.providers.claude_desktop = Some("d1".into());
|
desktop_only.providers.claude_desktop = Some("d1".into());
|
||||||
assert!(desktop_only.scope_captured(ProfileScope::Claude));
|
assert!(desktop_only.scope_captured(ProfileScope::ClaudeDesktop));
|
||||||
|
assert!(!desktop_only.scope_captured(ProfileScope::Claude));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -603,10 +614,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_app_grouping() {
|
fn test_scope_app_grouping() {
|
||||||
// Desktop 并入 Claude 组;组内应用与 for_app 反向映射必须一致
|
// Claude Code 与 Claude Desktop 各自独立成组;
|
||||||
|
// 组内应用与 for_app 反向映射必须一致
|
||||||
|
assert_eq!(ProfileScope::Claude.apps(), &[AppType::Claude]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ProfileScope::Claude.apps(),
|
ProfileScope::ClaudeDesktop.apps(),
|
||||||
&[AppType::Claude, AppType::ClaudeDesktop]
|
&[AppType::ClaudeDesktop]
|
||||||
);
|
);
|
||||||
assert_eq!(ProfileScope::Codex.apps(), &[AppType::Codex]);
|
assert_eq!(ProfileScope::Codex.apps(), &[AppType::Codex]);
|
||||||
for scope in ProfileScope::ALL {
|
for scope in ProfileScope::ALL {
|
||||||
|
|||||||
@@ -738,6 +738,7 @@ pub fn create_tray_menu(
|
|||||||
// 分组标签用产品名,不进 i18n
|
// 分组标签用产品名,不进 i18n
|
||||||
let scope_label = match scope {
|
let scope_label = match scope {
|
||||||
ProfileScope::Claude => "Claude Code",
|
ProfileScope::Claude => "Claude Code",
|
||||||
|
ProfileScope::ClaudeDesktop => "Claude Desktop",
|
||||||
ProfileScope::Codex => "Codex",
|
ProfileScope::Codex => "Codex",
|
||||||
};
|
};
|
||||||
let mut scope_builder = SubmenuBuilder::with_id(
|
let mut scope_builder = SubmenuBuilder::with_id(
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
|
|||||||
.save_prompt(AppType::Claude.as_str(), &prompt("pr2", false))
|
.save_prompt(AppType::Claude.as_str(), &prompt("pr2", false))
|
||||||
.expect("save prompt pr2");
|
.expect("save prompt pr2");
|
||||||
|
|
||||||
// ---- 保存项目 A(在 Claude 页新建:只拍 Claude + Desktop 当前状态)----
|
// ---- 保存项目 A(在 Claude 页新建:只拍 Claude 当前状态)----
|
||||||
let profile_a = ProfileService::create(&state, "Project A", ProfileScope::Claude)
|
let profile_a = ProfileService::create(&state, "Project A", ProfileScope::Claude)
|
||||||
.expect("create profile A");
|
.expect("create profile A");
|
||||||
let payload: ProfilePayload =
|
let payload: ProfilePayload =
|
||||||
@@ -191,18 +191,14 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
|
|||||||
"codex side not captured when creating from the claude group"
|
"codex side not captured when creating from the claude group"
|
||||||
);
|
);
|
||||||
assert_eq!(payload.mcp.codex, None, "uncaptured side stays None");
|
assert_eq!(payload.mcp.codex, None, "uncaptured side stays None");
|
||||||
assert_eq!(payload.providers.claude_desktop.as_deref(), Some("d1"));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(payload.mcp.claude_desktop, payload.skills.claude_desktop),
|
payload.providers.claude_desktop, None,
|
||||||
(Some(vec![]), Some(vec![])),
|
"claude desktop has its own profile scope"
|
||||||
"desktop has no MCP/Skills dimension (captured as empty)"
|
|
||||||
);
|
);
|
||||||
assert_eq!(payload.prompts.claude_desktop, None);
|
|
||||||
|
|
||||||
// ---- 改动全部四类配置(走真实切换路径)----
|
// ---- 改动全部四类配置(走真实切换路径)----
|
||||||
ProviderService::switch(&state, AppType::Claude, "p2").expect("switch to p2");
|
ProviderService::switch(&state, AppType::Claude, "p2").expect("switch to p2");
|
||||||
// Desktop live 写入仅 macOS/Windows 可用;其他平台不动 d1,
|
// Desktop 现在有自己的项目分组;Claude 分组 apply 不应再影响 Desktop
|
||||||
// apply 时命中幂等跳过(这正是 Linux 上带 Desktop 槽的 payload 应有行为)
|
|
||||||
#[cfg(any(target_os = "macos", windows))]
|
#[cfg(any(target_os = "macos", windows))]
|
||||||
ProviderService::switch(&state, AppType::ClaudeDesktop, "d2").expect("switch desktop to d2");
|
ProviderService::switch(&state, AppType::ClaudeDesktop, "d2").expect("switch desktop to d2");
|
||||||
McpService::toggle_app(&state, "m1", AppType::Claude, false).expect("disable m1");
|
McpService::toggle_app(&state, "m1", AppType::Claude, false).expect("disable m1");
|
||||||
@@ -211,7 +207,7 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
|
|||||||
.expect("disable skill");
|
.expect("disable skill");
|
||||||
PromptService::enable_prompt(&state, AppType::Claude, "pr2").expect("enable pr2");
|
PromptService::enable_prompt(&state, AppType::Claude, "pr2").expect("enable pr2");
|
||||||
|
|
||||||
// ---- 应用项目 A(Claude 组):全部复原 ----
|
// ---- 应用项目 A(Claude 组):只复原 Claude 侧 ----
|
||||||
let (warnings, _) = ProfileService::apply(&state, &profile_a.id, ProfileScope::Claude)
|
let (warnings, _) = ProfileService::apply(&state, &profile_a.id, ProfileScope::Claude)
|
||||||
.expect("apply profile A");
|
.expect("apply profile A");
|
||||||
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
|
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
|
||||||
@@ -222,14 +218,15 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
|
|||||||
.expect("get current provider");
|
.expect("get current provider");
|
||||||
assert_eq!(current.as_deref(), Some("p1"), "provider restored to p1");
|
assert_eq!(current.as_deref(), Some("p1"), "provider restored to p1");
|
||||||
|
|
||||||
|
// Claude 分组不再管理 Desktop:Desktop 保持 d2 不变
|
||||||
let current_desktop = state
|
let current_desktop = state
|
||||||
.db
|
.db
|
||||||
.get_current_provider(AppType::ClaudeDesktop.as_str())
|
.get_current_provider(AppType::ClaudeDesktop.as_str())
|
||||||
.expect("get current desktop provider");
|
.expect("get current desktop provider");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
current_desktop.as_deref(),
|
current_desktop.as_deref(),
|
||||||
Some("d1"),
|
Some("d2"),
|
||||||
"desktop provider restored to d1"
|
"desktop provider stays at d2 after claude-scope apply"
|
||||||
);
|
);
|
||||||
|
|
||||||
let servers = state.db.get_all_mcp_servers().expect("get mcp servers");
|
let servers = state.db.get_all_mcp_servers().expect("get mcp servers");
|
||||||
@@ -744,3 +741,68 @@ fn profile_switch_auto_disables_takeover_before_apply() {
|
|||||||
"live config should point to real endpoint after auto-disable"
|
"live config should point to real endpoint after auto-disable"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", windows))]
|
||||||
|
#[test]
|
||||||
|
fn claude_desktop_profile_scope_is_independent() {
|
||||||
|
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||||
|
reset_test_fs();
|
||||||
|
let _home = ensure_test_home();
|
||||||
|
|
||||||
|
let state = create_test_state().expect("create test state");
|
||||||
|
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.save_provider(
|
||||||
|
AppType::ClaudeDesktop.as_str(),
|
||||||
|
&desktop_provider("d1", "dk-1"),
|
||||||
|
)
|
||||||
|
.expect("save desktop provider d1");
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.save_provider(
|
||||||
|
AppType::ClaudeDesktop.as_str(),
|
||||||
|
&desktop_provider("d2", "dk-2"),
|
||||||
|
)
|
||||||
|
.expect("save desktop provider d2");
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.set_current_provider(AppType::ClaudeDesktop.as_str(), "d1")
|
||||||
|
.expect("set current desktop provider d1");
|
||||||
|
|
||||||
|
// 在 Desktop 页新建项目:只拍 Desktop 供应商
|
||||||
|
let project = ProfileService::create(&state, "Desktop Project", ProfileScope::ClaudeDesktop)
|
||||||
|
.expect("create desktop profile");
|
||||||
|
let payload: ProfilePayload =
|
||||||
|
serde_json::from_str(&project.payload).expect("parse desktop payload");
|
||||||
|
assert_eq!(payload.providers.claude_desktop.as_deref(), Some("d1"));
|
||||||
|
assert_eq!(payload.providers.claude, None, "claude slot untouched");
|
||||||
|
assert_eq!(payload.providers.codex, None, "codex slot untouched");
|
||||||
|
|
||||||
|
// 切到 d2
|
||||||
|
ProviderService::switch(&state, AppType::ClaudeDesktop, "d2").expect("switch desktop to d2");
|
||||||
|
|
||||||
|
// 应用 Desktop 项目:恢复 d1
|
||||||
|
let (warnings, _) = ProfileService::apply(&state, &project.id, ProfileScope::ClaudeDesktop)
|
||||||
|
.expect("apply desktop profile");
|
||||||
|
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.get_current_provider(AppType::ClaudeDesktop.as_str())
|
||||||
|
.expect("get current desktop provider")
|
||||||
|
.as_deref(),
|
||||||
|
Some("d1"),
|
||||||
|
"desktop provider restored by desktop-scope apply"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.get_current_profile_id(ProfileScope::ClaudeDesktop.as_str())
|
||||||
|
.expect("get desktop current profile id")
|
||||||
|
.as_deref(),
|
||||||
|
Some(project.id.as_str()),
|
||||||
|
"desktop scope marker set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -390,6 +390,9 @@ function App() {
|
|||||||
await queryClient.invalidateQueries({ queryKey: ["skills"] });
|
await queryClient.invalidateQueries({ queryKey: ["skills"] });
|
||||||
await queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
await queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["providers", "claude-desktop"],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
useTauriEvent<SyncStatusUpdatedPayload | null | undefined>(
|
useTauriEvent<SyncStatusUpdatedPayload | null | undefined>(
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ import type { PerApp, Profile, ProfileScope } from "@/lib/api/profiles";
|
|||||||
*/
|
*/
|
||||||
export const APP_PROFILE_SCOPE: Partial<Record<AppId, ProfileScope>> = {
|
export const APP_PROFILE_SCOPE: Partial<Record<AppId, ProfileScope>> = {
|
||||||
claude: "claude",
|
claude: "claude",
|
||||||
"claude-desktop": "claude",
|
"claude-desktop": "claude-desktop",
|
||||||
codex: "codex",
|
codex: "codex",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 分组显示名(产品名,不进 i18n;与后端托盘子菜单标签一致) */
|
/** 分组显示名(产品名,不进 i18n;与后端托盘子菜单标签一致) */
|
||||||
export const PROFILE_SCOPE_LABELS: Record<ProfileScope, string> = {
|
export const PROFILE_SCOPE_LABELS: Record<ProfileScope, string> = {
|
||||||
claude: "Claude Code",
|
claude: "Claude Code",
|
||||||
|
"claude-desktop": "Claude Desktop",
|
||||||
codex: "Codex",
|
codex: "Codex",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 分组内的 payload 槽位 key(后端 ProfileScope::apps 的前端镜像) */
|
/** 分组内的 payload 槽位 key(后端 ProfileScope::apps 的前端镜像) */
|
||||||
const SCOPE_SLOT_KEYS: Record<ProfileScope, (keyof PerApp<unknown>)[]> = {
|
const SCOPE_SLOT_KEYS: Record<ProfileScope, (keyof PerApp<unknown>)[]> = {
|
||||||
claude: ["claude", "claude-desktop"],
|
claude: ["claude"],
|
||||||
|
"claude-desktop": ["claude-desktop"],
|
||||||
codex: ["codex"],
|
codex: ["codex"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1868,7 +1868,8 @@
|
|||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"switcherTooltip": {
|
"switcherTooltip": {
|
||||||
"claude": "Projects: switch the Claude Code provider / MCP / Skills / memory files, plus the Claude Desktop provider, in one click",
|
"claude": "Projects: switch the Claude Code provider / MCP / Skills / memory files in one click",
|
||||||
|
"claude-desktop": "Projects: switch the Claude Desktop provider in one click",
|
||||||
"codex": "Projects: switch the Codex provider / MCP / Skills / memory files in one click"
|
"codex": "Projects: switch the Codex provider / MCP / Skills / memory files in one click"
|
||||||
},
|
},
|
||||||
"none": "No project",
|
"none": "No project",
|
||||||
@@ -1876,7 +1877,8 @@
|
|||||||
"empty": "No projects yet",
|
"empty": "No projects yet",
|
||||||
"createFromCurrent": "New project",
|
"createFromCurrent": "New project",
|
||||||
"createDescription": {
|
"createDescription": {
|
||||||
"claude": "Save the current Claude Code provider, MCP, Skills and memory file (plus the Claude Desktop provider) as a project you can switch to later.",
|
"claude": "Save the current Claude Code provider, MCP, Skills and memory file as a project you can switch to later.",
|
||||||
|
"claude-desktop": "Save the current Claude Desktop provider as a project you can switch to later.",
|
||||||
"codex": "Save the current Codex provider, MCP, Skills and memory file configuration as a project you can switch to later."
|
"codex": "Save the current Codex provider, MCP, Skills and memory file configuration as a project you can switch to later."
|
||||||
},
|
},
|
||||||
"manage": "Manage projects…",
|
"manage": "Manage projects…",
|
||||||
|
|||||||
@@ -1868,7 +1868,8 @@
|
|||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"switcherTooltip": {
|
"switcherTooltip": {
|
||||||
"claude": "プロジェクト:Claude Code のプロバイダー / MCP / Skills / メモリファイル、および Claude Desktop のプロバイダーをワンクリックで切り替え",
|
"claude": "プロジェクト:Claude Code のプロバイダー / MCP / Skills / メモリファイルをワンクリックで切り替え",
|
||||||
|
"claude-desktop": "プロジェクト:Claude Desktop のプロバイダーをワンクリックで切り替え",
|
||||||
"codex": "プロジェクト:Codex のプロバイダー / MCP / Skills / メモリファイルをワンクリックで切り替え"
|
"codex": "プロジェクト:Codex のプロバイダー / MCP / Skills / メモリファイルをワンクリックで切り替え"
|
||||||
},
|
},
|
||||||
"none": "プロジェクトを使用しない",
|
"none": "プロジェクトを使用しない",
|
||||||
@@ -1876,7 +1877,8 @@
|
|||||||
"empty": "プロジェクトはまだありません",
|
"empty": "プロジェクトはまだありません",
|
||||||
"createFromCurrent": "新規プロジェクト",
|
"createFromCurrent": "新規プロジェクト",
|
||||||
"createDescription": {
|
"createDescription": {
|
||||||
"claude": "Claude Code の現在のプロバイダー、MCP、Skills、メモリファイル(および Claude Desktop のプロバイダー)をプロジェクトとして保存し、後からワンクリックで切り替えられます。",
|
"claude": "Claude Code の現在のプロバイダー、MCP、Skills、メモリファイルをプロジェクトとして保存し、後からワンクリックで切り替えられます。",
|
||||||
|
"claude-desktop": "Claude Desktop の現在のプロバイダーをプロジェクトとして保存し、後からワンクリックで切り替えられます。",
|
||||||
"codex": "Codex の現在のプロバイダー、MCP、Skills、メモリファイルの設定をプロジェクトとして保存し、後からワンクリックで切り替えられます。"
|
"codex": "Codex の現在のプロバイダー、MCP、Skills、メモリファイルの設定をプロジェクトとして保存し、後からワンクリックで切り替えられます。"
|
||||||
},
|
},
|
||||||
"manage": "プロジェクトを管理…",
|
"manage": "プロジェクトを管理…",
|
||||||
|
|||||||
@@ -1840,7 +1840,8 @@
|
|||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"switcherTooltip": {
|
"switcherTooltip": {
|
||||||
"claude": "專案:一鍵切換 Claude Code 的供應商 / MCP / Skills / 記憶檔案,及 Claude Desktop 的供應商",
|
"claude": "專案:一鍵切換 Claude Code 的供應商 / MCP / Skills / 記憶檔案",
|
||||||
|
"claude-desktop": "專案:一鍵切換 Claude Desktop 的供應商",
|
||||||
"codex": "專案:一鍵切換 Codex 的供應商 / MCP / Skills / 記憶檔案"
|
"codex": "專案:一鍵切換 Codex 的供應商 / MCP / Skills / 記憶檔案"
|
||||||
},
|
},
|
||||||
"none": "不使用專案",
|
"none": "不使用專案",
|
||||||
@@ -1848,7 +1849,8 @@
|
|||||||
"empty": "尚無專案",
|
"empty": "尚無專案",
|
||||||
"createFromCurrent": "新建專案",
|
"createFromCurrent": "新建專案",
|
||||||
"createDescription": {
|
"createDescription": {
|
||||||
"claude": "把 Claude Code 目前的供應商、MCP、Skills、記憶檔案(及 Claude Desktop 的供應商)儲存為一個專案,之後可一鍵切換。",
|
"claude": "把 Claude Code 目前的供應商、MCP、Skills、記憶檔案儲存為一個專案,之後可一鍵切換。",
|
||||||
|
"claude-desktop": "把 Claude Desktop 目前的供應商儲存為一個專案,之後可一鍵切換。",
|
||||||
"codex": "把 Codex 目前的供應商、MCP、Skills 和記憶檔案設定儲存為一個專案,之後可一鍵切換。"
|
"codex": "把 Codex 目前的供應商、MCP、Skills 和記憶檔案設定儲存為一個專案,之後可一鍵切換。"
|
||||||
},
|
},
|
||||||
"manage": "管理專案…",
|
"manage": "管理專案…",
|
||||||
|
|||||||
@@ -1868,7 +1868,8 @@
|
|||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"switcherTooltip": {
|
"switcherTooltip": {
|
||||||
"claude": "项目:一键切换 Claude Code 的供应商 / MCP / Skills / 记忆文件,及 Claude Desktop 的供应商",
|
"claude": "项目:一键切换 Claude Code 的供应商 / MCP / Skills / 记忆文件",
|
||||||
|
"claude-desktop": "项目:一键切换 Claude Desktop 的供应商",
|
||||||
"codex": "项目:一键切换 Codex 的供应商 / MCP / Skills / 记忆文件"
|
"codex": "项目:一键切换 Codex 的供应商 / MCP / Skills / 记忆文件"
|
||||||
},
|
},
|
||||||
"none": "不使用项目",
|
"none": "不使用项目",
|
||||||
@@ -1876,7 +1877,8 @@
|
|||||||
"empty": "暂无项目",
|
"empty": "暂无项目",
|
||||||
"createFromCurrent": "新建项目",
|
"createFromCurrent": "新建项目",
|
||||||
"createDescription": {
|
"createDescription": {
|
||||||
"claude": "把 Claude Code 当前的供应商、MCP、Skills、记忆文件(及 Claude Desktop 的供应商)保存为一个项目,之后可一键切换。",
|
"claude": "把 Claude Code 当前的供应商、MCP、Skills、记忆文件保存为一个项目,之后可一键切换。",
|
||||||
|
"claude-desktop": "把 Claude Desktop 当前的供应商保存为一个项目,之后可一键切换。",
|
||||||
"codex": "把 Codex 当前的供应商、MCP、Skills 和记忆文件配置保存为一个项目,之后可一键切换。"
|
"codex": "把 Codex 当前的供应商、MCP、Skills 和记忆文件配置保存为一个项目,之后可一键切换。"
|
||||||
},
|
},
|
||||||
"manage": "管理项目…",
|
"manage": "管理项目…",
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { invoke } from "@tauri-apps/api/core";
|
|||||||
/**
|
/**
|
||||||
* Profile 操作的应用分组(与后端 services/profile.rs 的 ProfileScope 严格对应)
|
* Profile 操作的应用分组(与后端 services/profile.rs 的 ProfileScope 严格对应)
|
||||||
*
|
*
|
||||||
* 项目实体全应用共享,但快照/应用/当前指针按组进行;Claude Desktop 并入
|
* 项目实体全应用共享,但快照/应用/当前指针按组进行;Claude Code 与
|
||||||
* claude 组(它只有聊天侧供应商一个受管维度,Code 标签页天然跟随 Claude Code)。
|
* Claude Desktop 的供应商独立切换,因此各自有独立分组。
|
||||||
*/
|
*/
|
||||||
export type ProfileScope = "claude" | "codex";
|
export type ProfileScope = "claude" | "claude-desktop" | "codex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按 app 分槽的载荷容器(与后端 services/profile.rs 的 PerApp<T> 严格对应)
|
* 按 app 分槽的载荷容器(与后端 services/profile.rs 的 PerApp<T> 严格对应)
|
||||||
|
|||||||
Reference in New Issue
Block a user