refactor(profiles): shared project entity with per-scope switching

Projects are now global shared entities; Claude and Codex groups switch
independently via scoped current pointers and scoped payload slots.

- Remove scope column from profiles; keep current_profile_id_<scope>
- Use Option<Vec<String>> for mcp/skills to distinguish 'never captured'
  from 'captured empty', preventing cross-side accidental disable
- Update/apply operations scoped to the active group via merge_scope_from
- Tray menu nests same shared list under Claude Code/Codex groups
- Add i18n for per-scope tooltips and 'not saved for this side' hint

Refs: profile P1 shared-entity redesign
This commit is contained in:
Jason
2026-07-04 21:34:30 +08:00
parent 65a5464fcc
commit dbb5999d1e
17 changed files with 831 additions and 190 deletions
+32 -15
View File
@@ -1,5 +1,13 @@
import { invoke } from "@tauri-apps/api/core";
/**
* Profile 操作的应用分组(与后端 services/profile.rs 的 ProfileScope 严格对应)
*
* 项目实体全应用共享,但快照/应用/当前指针按组进行;Claude Desktop 并入
* claude 组(它只有聊天侧供应商一个受管维度,Code 标签页天然跟随 Claude Code)。
*/
export type ProfileScope = "claude" | "codex";
/**
* 按 app 分槽的载荷容器(与后端 services/profile.rs 的 PerApp<T> 严格对应)
*/
@@ -11,11 +19,14 @@ export interface PerApp<T> {
/**
* 项目 Profile 的配置快照(与后端 ProfilePayload 严格对应)
*
* 所有槽位 null = 该侧从未拍过快照(应用时不动),与"拍到的就是空集"
* (空数组,应用时清空启用)严格区分。
*/
export interface ProfilePayload {
providers: PerApp<string | null>;
mcp: PerApp<string[]>;
skills: PerApp<string[]>;
mcp: PerApp<string[] | null>;
skills: PerApp<string[] | null>;
prompts: PerApp<string | null>;
}
@@ -27,37 +38,42 @@ export interface Profile {
updatedAt?: number;
}
/** 每个分组当前激活的项目 id(未使用项目时为 null) */
export type CurrentProfileIds = Record<ProfileScope, string | null>;
export interface ProfilesResponse {
profiles: Profile[];
currentId: string | null;
currentIds: CurrentProfileIds;
}
export const profilesApi = {
/**
* 获取所有项目及当前激活项目 id
* 获取所有项目及各分组当前激活项目 id
*/
async list(): Promise<ProfilesResponse> {
return await invoke("list_profiles");
},
/**
* 以当前配置状态创建新项目
* 创建新项目(只拍发起页所属分组的当前状态,其余分组槽位留空)
*/
async create(name: string): Promise<Profile> {
return await invoke("create_profile", { name });
async create(name: string, scope: ProfileScope): Promise<Profile> {
return await invoke("create_profile", { name, scope });
},
/**
* 更新项目重命名和/或以当前状态重拍快照
* 更新项目重命名(作用于共享实体)和/或以当前状态重拍快照
* resnapshot 只覆盖 scope 分组的槽位,其余分组原样保留)
*/
async update(
id: string,
options: { name?: string; resnapshot?: boolean },
options: { name?: string; resnapshot?: boolean; scope?: ProfileScope },
): Promise<Profile> {
return await invoke("update_profile", {
id,
name: options.name,
resnapshot: options.resnapshot,
scope: options.scope,
});
},
@@ -69,16 +85,17 @@ export const profilesApi = {
},
/**
* 应用项目快照,返回 warningsbest-effort,部分失败不中断)
* 应用项目快照(只作用于发起页所属分组内的应用),返回 warnings
* best-effort,部分失败不中断)
*/
async apply(id: string): Promise<string[]> {
return await invoke("apply_profile", { id });
async apply(id: string, scope: ProfileScope): Promise<string[]> {
return await invoke("apply_profile", { id, scope });
},
/**
* 不使用项目:仅清除激活标记,不改动任何配置
* 不使用项目:仅清除某分组的激活标记,不改动任何配置
*/
async clearCurrent(): Promise<void> {
return await invoke("clear_current_profile");
async clearCurrent(scope: ProfileScope): Promise<void> {
return await invoke("clear_current_profile", { scope });
},
};