merge: resolve i18n conflicts between smart-url-path-detection and main

This commit is contained in:
YoVinchen
2026-02-09 12:58:01 +08:00
57 changed files with 4945 additions and 929 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
// 配置相关 API
import { invoke } from "@tauri-apps/api/core";
export type AppType = "claude" | "codex" | "gemini";
export type AppType = "claude" | "codex" | "gemini" | "omo";
/**
* 获取 Claude 通用配置片段(已废弃,使用 getCommonConfigSnippet
@@ -63,7 +63,7 @@ export type ExtractCommonConfigSnippetOptions = {
};
export async function extractCommonConfigSnippet(
appType: AppType,
appType: Exclude<AppType, "omo">,
options?: ExtractCommonConfigSnippetOptions,
): Promise<string> {
const args: Record<string, unknown> = { appType };
+10
View File
@@ -0,0 +1,10 @@
import { invoke } from "@tauri-apps/api/core";
import type { OmoLocalFileData } from "@/types/omo";
export const omoApi = {
readLocalFile: (): Promise<OmoLocalFileData> => invoke("read_omo_local_file"),
getCurrentOmoProviderId: (): Promise<string> =>
invoke("get_current_omo_provider_id"),
getOmoProviderCount: (): Promise<number> => invoke("get_omo_provider_count"),
disableCurrentOmo: (): Promise<void> => invoke("disable_current_omo"),
};
+28 -14
View File
@@ -17,13 +17,15 @@ export const useAddProviderMutation = (appId: AppId) => {
let id: string;
if (appId === "opencode") {
// OpenCode: use user-provided providerKey as ID
if (!providerInput.providerKey) {
throw new Error("Provider key is required for OpenCode");
if (providerInput.category === "omo") {
id = `omo-${generateUUID()}`;
} else {
if (!providerInput.providerKey) {
throw new Error("Provider key is required for OpenCode");
}
id = providerInput.providerKey;
}
id = providerInput.providerKey;
} else {
// Other apps: use random UUID
id = generateUUID();
}
@@ -32,7 +34,6 @@ export const useAddProviderMutation = (appId: AppId) => {
id,
createdAt: Date.now(),
};
// Remove providerKey from the provider object before saving
delete (newProvider as any).providerKey;
await providersApi.add(newProvider, appId);
@@ -41,7 +42,15 @@ export const useAddProviderMutation = (appId: AppId) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
// 更新托盘菜单(失败不影响主操作)
if (appId === "opencode") {
await queryClient.invalidateQueries({
queryKey: ["omo", "current-provider-id"],
});
await queryClient.invalidateQueries({
queryKey: ["omo", "provider-count"],
});
}
try {
await providersApi.updateTrayMenu();
} catch (trayError) {
@@ -115,7 +124,15 @@ export const useDeleteProviderMutation = (appId: AppId) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
// 更新托盘菜单(失败不影响主操作)
if (appId === "opencode") {
await queryClient.invalidateQueries({
queryKey: ["omo", "current-provider-id"],
});
await queryClient.invalidateQueries({
queryKey: ["omo", "provider-count"],
});
}
try {
await providersApi.updateTrayMenu();
} catch (trayError) {
@@ -157,14 +174,15 @@ export const useSwitchProviderMutation = (appId: AppId) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
// OpenCode: also invalidate live provider IDs cache to update button state
if (appId === "opencode") {
await queryClient.invalidateQueries({
queryKey: ["opencodeLiveProviderIds"],
});
await queryClient.invalidateQueries({
queryKey: ["omo", "current-provider-id"],
});
}
// 更新托盘菜单(失败不影响主操作)
try {
await providersApi.updateTrayMenu();
} catch (trayError) {
@@ -173,14 +191,10 @@ export const useSwitchProviderMutation = (appId: AppId) => {
trayError,
);
}
// Note: Success toast is handled by useProviderActions.switchProvider
// to allow customization based on provider properties (e.g., apiFormat)
},
onError: (error: Error) => {
const detail = extractErrorMessage(error) || t("common.unknown");
// 标题与详情分离,便于扫描 + 一键复制
toast.error(
t("notifications.switchFailedTitle", { defaultValue: "切换失败" }),
{
+95
View File
@@ -0,0 +1,95 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { omoApi } from "@/lib/api/omo";
import * as configApi from "@/lib/api/config";
import type { OmoGlobalConfig } from "@/types/omo";
export const omoKeys = {
all: ["omo"] as const,
globalConfig: () => [...omoKeys.all, "global-config"] as const,
currentProviderId: () => [...omoKeys.all, "current-provider-id"] as const,
providerCount: () => [...omoKeys.all, "provider-count"] as const,
};
function invalidateOmoQueries(queryClient: ReturnType<typeof useQueryClient>) {
queryClient.invalidateQueries({ queryKey: omoKeys.globalConfig() });
queryClient.invalidateQueries({ queryKey: ["providers"] });
queryClient.invalidateQueries({ queryKey: omoKeys.currentProviderId() });
queryClient.invalidateQueries({ queryKey: omoKeys.providerCount() });
}
export function useOmoGlobalConfig(enabled = true) {
return useQuery({
queryKey: omoKeys.globalConfig(),
enabled,
queryFn: async (): Promise<OmoGlobalConfig> => {
const raw = await configApi.getCommonConfigSnippet("omo");
if (!raw) {
return {
id: "global",
disabledAgents: [],
disabledMcps: [],
disabledHooks: [],
disabledSkills: [],
updatedAt: new Date().toISOString(),
};
}
try {
return JSON.parse(raw) as OmoGlobalConfig;
} catch (error) {
console.warn(
"[omo] invalid global config json, fallback to defaults",
error,
);
return {
id: "global",
disabledAgents: [],
disabledMcps: [],
disabledHooks: [],
disabledSkills: [],
updatedAt: new Date().toISOString(),
};
}
},
});
}
export function useCurrentOmoProviderId(enabled = true) {
return useQuery({
queryKey: omoKeys.currentProviderId(),
queryFn: omoApi.getCurrentOmoProviderId,
enabled,
});
}
export function useOmoProviderCount(enabled = true) {
return useQuery({
queryKey: omoKeys.providerCount(),
queryFn: omoApi.getOmoProviderCount,
enabled,
});
}
export function useSaveOmoGlobalConfig() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: OmoGlobalConfig) => {
const jsonStr = JSON.stringify(input);
await configApi.setCommonConfigSnippet("omo", jsonStr);
},
onSuccess: () => invalidateOmoQueries(queryClient),
});
}
export function useReadOmoLocalFile() {
return useMutation({
mutationFn: () => omoApi.readLocalFile(),
});
}
export function useDisableCurrentOmo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => omoApi.disableCurrentOmo(),
onSuccess: () => invalidateOmoQueries(queryClient),
});
}
+92 -23
View File
@@ -2,6 +2,35 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { usageApi } from "@/lib/api/usage";
import type { LogFilters } from "@/types/usage";
const DEFAULT_REFETCH_INTERVAL_MS = 30000;
type UsageQueryOptions = {
refetchInterval?: number | false;
refetchIntervalInBackground?: boolean;
};
type RequestLogsTimeMode = "rolling" | "fixed";
type RequestLogsQueryArgs = {
filters: LogFilters;
timeMode: RequestLogsTimeMode;
page?: number;
pageSize?: number;
rollingWindowSeconds?: number;
options?: UsageQueryOptions;
};
type RequestLogsKey = {
timeMode: RequestLogsTimeMode;
rollingWindowSeconds?: number;
appType?: string;
providerName?: string;
model?: string;
statusCode?: number;
startDate?: number;
endDate?: number;
};
// Query keys
export const usageKeys = {
all: ["usage"] as const,
@@ -9,8 +38,21 @@ export const usageKeys = {
trends: (days: number) => [...usageKeys.all, "trends", days] as const,
providerStats: () => [...usageKeys.all, "provider-stats"] as const,
modelStats: () => [...usageKeys.all, "model-stats"] as const,
logs: (filters: LogFilters, page: number, pageSize: number) =>
[...usageKeys.all, "logs", filters, page, pageSize] as const,
logs: (key: RequestLogsKey, page: number, pageSize: number) =>
[
...usageKeys.all,
"logs",
key.timeMode,
key.rollingWindowSeconds ?? 0,
key.appType ?? "",
key.providerName ?? "",
key.model ?? "",
key.statusCode ?? -1,
key.startDate ?? 0,
key.endDate ?? 0,
page,
pageSize,
] as const,
detail: (requestId: string) =>
[...usageKeys.all, "detail", requestId] as const,
pricing: () => [...usageKeys.all, "pricing"] as const,
@@ -25,58 +67,85 @@ const getWindow = (days: number) => {
};
// Hooks
export function useUsageSummary(days: number) {
export function useUsageSummary(days: number, options?: UsageQueryOptions) {
return useQuery({
queryKey: usageKeys.summary(days),
queryFn: () => {
const { startDate, endDate } = getWindow(days);
return usageApi.getUsageSummary(startDate, endDate);
},
refetchInterval: 30000, // 每30秒自动刷新
refetchIntervalInBackground: false, // 后台不刷新
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS, // 每30秒自动刷新
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false, // 后台不刷新
});
}
export function useUsageTrends(days: number) {
export function useUsageTrends(days: number, options?: UsageQueryOptions) {
return useQuery({
queryKey: usageKeys.trends(days),
queryFn: () => {
const { startDate, endDate } = getWindow(days);
return usageApi.getUsageTrends(startDate, endDate);
},
refetchInterval: 30000, // 每30秒自动刷新
refetchIntervalInBackground: false,
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS, // 每30秒自动刷新
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false,
});
}
export function useProviderStats() {
export function useProviderStats(options?: UsageQueryOptions) {
return useQuery({
queryKey: usageKeys.providerStats(),
queryFn: usageApi.getProviderStats,
refetchInterval: 30000, // 每30秒自动刷新
refetchIntervalInBackground: false,
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS, // 每30秒自动刷新
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false,
});
}
export function useModelStats() {
export function useModelStats(options?: UsageQueryOptions) {
return useQuery({
queryKey: usageKeys.modelStats(),
queryFn: usageApi.getModelStats,
refetchInterval: 30000, // 每30秒自动刷新
refetchIntervalInBackground: false,
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS, // 每30秒自动刷新
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false,
});
}
export function useRequestLogs(
filters: LogFilters,
page: number = 0,
pageSize: number = 20,
) {
const getRollingRange = (windowSeconds: number) => {
const endDate = Math.floor(Date.now() / 1000);
const startDate = endDate - windowSeconds;
return { startDate, endDate };
};
export function useRequestLogs({
filters,
timeMode,
page = 0,
pageSize = 20,
rollingWindowSeconds = 24 * 60 * 60,
options,
}: RequestLogsQueryArgs) {
const key: RequestLogsKey = {
timeMode,
rollingWindowSeconds:
timeMode === "rolling" ? rollingWindowSeconds : undefined,
appType: filters.appType,
providerName: filters.providerName,
model: filters.model,
statusCode: filters.statusCode,
startDate: timeMode === "fixed" ? filters.startDate : undefined,
endDate: timeMode === "fixed" ? filters.endDate : undefined,
};
return useQuery({
queryKey: usageKeys.logs(filters, page, pageSize),
queryFn: () => usageApi.getRequestLogs(filters, page, pageSize),
refetchInterval: 30000, // 每30秒自动刷新
refetchIntervalInBackground: false,
queryKey: usageKeys.logs(key, page, pageSize),
queryFn: () => {
const effectiveFilters =
timeMode === "rolling"
? { ...filters, ...getRollingRange(rollingWindowSeconds) }
: filters;
return usageApi.getRequestLogs(effectiveFilters, page, pageSize);
},
refetchInterval: options?.refetchInterval ?? DEFAULT_REFETCH_INTERVAL_MS, // 每30秒自动刷新
refetchIntervalInBackground: options?.refetchIntervalInBackground ?? false,
});
}