Merge branch 'main' into feat/smart-url-path-detection

# Conflicts:
#	src/App.tsx
#	src/hooks/useProviderActions.ts
#	src/i18n/locales/en.json
#	src/i18n/locales/ja.json
#	src/i18n/locales/zh.json
This commit is contained in:
YoVinchen
2026-02-15 13:48:28 +08:00
112 changed files with 11858 additions and 585 deletions
+2
View File
@@ -7,7 +7,9 @@ export { skillsApi } from "./skills";
export { usageApi } from "./usage";
export { vscodeApi } from "./vscode";
export { proxyApi } from "./proxy";
export { openclawApi } from "./openclaw";
export { sessionsApi } from "./sessions";
export { workspaceApi } from "./workspace";
export * as configApi from "./config";
export type { ProviderSwitchEvent } from "./providers";
export type { Prompt } from "./prompts";
+105
View File
@@ -0,0 +1,105 @@
import { invoke } from "@tauri-apps/api/core";
import type {
OpenClawDefaultModel,
OpenClawModelCatalogEntry,
OpenClawAgentsDefaults,
OpenClawEnvConfig,
OpenClawToolsConfig,
} from "@/types";
/**
* OpenClaw configuration API
*
* Manages ~/.openclaw/openclaw.json sections:
* - agents.defaults (model, catalog)
* - env (environment variables)
* - tools (permissions)
*/
export const openclawApi = {
// ============================================================
// Agents Configuration
// ============================================================
/**
* Get default model configuration (agents.defaults.model)
*/
async getDefaultModel(): Promise<OpenClawDefaultModel | null> {
return await invoke("get_openclaw_default_model");
},
/**
* Set default model configuration (agents.defaults.model)
*/
async setDefaultModel(model: OpenClawDefaultModel): Promise<void> {
return await invoke("set_openclaw_default_model", { model });
},
/**
* Get model catalog/allowlist (agents.defaults.models)
*/
async getModelCatalog(): Promise<Record<
string,
OpenClawModelCatalogEntry
> | null> {
return await invoke("get_openclaw_model_catalog");
},
/**
* Set model catalog/allowlist (agents.defaults.models)
*/
async setModelCatalog(
catalog: Record<string, OpenClawModelCatalogEntry>,
): Promise<void> {
return await invoke("set_openclaw_model_catalog", { catalog });
},
/**
* Get full agents.defaults config (all fields)
*/
async getAgentsDefaults(): Promise<OpenClawAgentsDefaults | null> {
return await invoke("get_openclaw_agents_defaults");
},
/**
* Set full agents.defaults config (all fields)
*/
async setAgentsDefaults(defaults: OpenClawAgentsDefaults): Promise<void> {
return await invoke("set_openclaw_agents_defaults", { defaults });
},
// ============================================================
// Env Configuration
// ============================================================
/**
* Get env config (env section of openclaw.json)
*/
async getEnv(): Promise<OpenClawEnvConfig> {
return await invoke("get_openclaw_env");
},
/**
* Set env config (env section of openclaw.json)
*/
async setEnv(env: OpenClawEnvConfig): Promise<void> {
return await invoke("set_openclaw_env", { env });
},
// ============================================================
// Tools Configuration
// ============================================================
/**
* Get tools config (tools section of openclaw.json)
*/
async getTools(): Promise<OpenClawToolsConfig> {
return await invoke("get_openclaw_tools");
},
/**
* Set tools config (tools section of openclaw.json)
*/
async setTools(tools: OpenClawToolsConfig): Promise<void> {
return await invoke("set_openclaw_tools", { tools });
},
};
+8
View File
@@ -98,6 +98,14 @@ export const providersApi = {
async getOpenCodeLiveProviderIds(): Promise<string[]> {
return await invoke("get_opencode_live_provider_ids");
},
/**
* 获取 OpenClaw live 配置中的供应商 ID 列表
* 用于前端判断供应商是否已添加到 openclaw.json
*/
async getOpenClawLiveProviderIds(): Promise<string[]> {
return await invoke("get_openclaw_live_provider_ids");
},
};
// ============================================================================
+47 -1
View File
@@ -1,5 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import type { Settings } from "@/types";
import type { Settings, WebDavSyncSettings, RemoteSnapshotInfo } from "@/types";
import type { AppId } from "./types";
export interface ConfigTransferResult {
@@ -9,6 +9,15 @@ export interface ConfigTransferResult {
backupId?: string;
}
export interface WebDavTestResult {
success: boolean;
message?: string;
}
export interface WebDavSyncResult {
status: string;
}
export const settingsApi = {
async get(): Promise<Settings> {
return await invoke("get_settings");
@@ -93,6 +102,42 @@ export const settingsApi = {
return await invoke("import_config_from_file", { filePath });
},
// ─── WebDAV v2 sync ───────────────────────────────────────
async webdavTestConnection(
settings: WebDavSyncSettings,
preserveEmptyPassword = true,
): Promise<WebDavTestResult> {
return await invoke("webdav_test_connection", {
settings,
preserveEmptyPassword,
});
},
async webdavSyncUpload(): Promise<WebDavSyncResult> {
return await invoke("webdav_sync_upload");
},
async webdavSyncDownload(): Promise<WebDavSyncResult> {
return await invoke("webdav_sync_download");
},
async webdavSyncSaveSettings(
settings: WebDavSyncSettings,
passwordTouched = false,
): Promise<{ success: boolean }> {
return await invoke("webdav_sync_save_settings", {
settings,
passwordTouched,
});
},
async webdavSyncFetchRemoteInfo(): Promise<
RemoteSnapshotInfo | { empty: true }
> {
return await invoke("webdav_sync_fetch_remote_info");
},
async syncCurrentProvidersLive(): Promise<void> {
const result = (await invoke("sync_current_providers_live")) as {
success?: boolean;
@@ -155,6 +200,7 @@ export const settingsApi = {
export interface RectifierConfig {
enabled: boolean;
requestThinkingSignature: boolean;
requestThinkingBudget: boolean;
}
export interface LogConfig {
+2 -1
View File
@@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
import type { AppId } from "@/lib/api/types";
// ========== 类型定义 ==========
export type AppType = "claude" | "codex" | "gemini" | "opencode" | "openclaw";
/** Skill 应用启用状态 */
export interface SkillApps {
@@ -10,6 +10,7 @@ export interface SkillApps {
codex: boolean;
gemini: boolean;
opencode: boolean;
openclaw: boolean;
}
/** 已安装的 Skillv3.10.0+ 统一结构) */
+1 -1
View File
@@ -1,2 +1,2 @@
// 前端统一使用 AppId 作为应用标识(与后端命令参数 `app` 一致)
export type AppId = "claude" | "codex" | "gemini" | "opencode";
export type AppId = "claude" | "codex" | "gemini" | "opencode" | "openclaw";
+11
View File
@@ -0,0 +1,11 @@
import { invoke } from "@tauri-apps/api/core";
export const workspaceApi = {
async readFile(filename: string): Promise<string | null> {
return invoke<string | null>("read_workspace_file", { filename });
},
async writeFile(filename: string, content: string): Promise<void> {
return invoke<void>("write_workspace_file", { filename, content });
},
};
+15 -3
View File
@@ -5,6 +5,7 @@ import { providersApi, settingsApi, type AppId } from "@/lib/api";
import type { Provider, Settings } from "@/types";
import { extractErrorMessage } from "@/utils/errorUtils";
import { generateUUID } from "@/utils/uuid";
import { openclawKeys } from "@/hooks/useOpenClaw";
export const useAddProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient();
@@ -16,12 +17,12 @@ export const useAddProviderMutation = (appId: AppId) => {
) => {
let id: string;
if (appId === "opencode") {
if (appId === "opencode" || appId === "openclaw") {
if (providerInput.category === "omo") {
id = `omo-${generateUUID()}`;
} else {
if (!providerInput.providerKey) {
throw new Error("Provider key is required for OpenCode");
throw new Error(`Provider key is required for ${appId}`);
}
id = providerInput.providerKey;
}
@@ -29,8 +30,10 @@ export const useAddProviderMutation = (appId: AppId) => {
id = generateUUID();
}
const { providerKey: _providerKey, ...rest } = providerInput;
const newProvider: Provider = {
...providerInput,
...rest,
id,
createdAt: Date.now(),
};
@@ -174,6 +177,7 @@ export const useSwitchProviderMutation = (appId: AppId) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
// OpenCode/OpenClaw: also invalidate live provider IDs cache to update button state
if (appId === "opencode") {
await queryClient.invalidateQueries({
queryKey: ["opencodeLiveProviderIds"],
@@ -182,6 +186,14 @@ export const useSwitchProviderMutation = (appId: AppId) => {
queryKey: ["omo", "current-provider-id"],
});
}
if (appId === "openclaw") {
await queryClient.invalidateQueries({
queryKey: openclawKeys.liveProviderIds,
});
await queryClient.invalidateQueries({
queryKey: openclawKeys.defaultModel,
});
}
try {
await providersApi.updateTrayMenu();
+2 -2
View File
@@ -4,8 +4,8 @@ export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 5,
refetchOnWindowFocus: true,
staleTime: 0,
},
mutations: {
retry: false,
+21
View File
@@ -28,6 +28,27 @@ export const settingsSchema = z.object({
// Skill 同步设置
skillSyncMethod: z.enum(["auto", "symlink", "copy"]).optional(),
// WebDAV v2 同步设置(通过专用命令保存,schema 仅用于读取)
webdavSync: z
.object({
enabled: z.boolean().optional(),
baseUrl: z.string().trim().optional().or(z.literal("")),
username: z.string().trim().optional().or(z.literal("")),
password: z.string().optional(),
remoteRoot: z.string().trim().optional().or(z.literal("")),
profile: z.string().trim().optional().or(z.literal("")),
status: z
.object({
lastSyncAt: z.number().nullable().optional(),
lastError: z.string().nullable().optional(),
lastRemoteEtag: z.string().nullable().optional(),
lastLocalManifestHash: z.string().nullable().optional(),
lastRemoteManifestHash: z.string().nullable().optional(),
})
.optional(),
})
.optional(),
});
export type SettingsFormData = z.infer<typeof settingsSchema>;