* feat(gemini): add Gemini provider integration - Add gemini_config.rs module for .env file parsing - Extend AppType enum to support Gemini - Implement GeminiConfigEditor and GeminiFormFields components - Add GeminiIcon with standardized 1024x1024 viewBox - Add Gemini provider presets configuration - Update i18n translations for Gemini support - Extend ProviderService and McpService for Gemini * fix(gemini): resolve TypeScript errors, add i18n support, and fix MCP logic **Critical Fixes:** - Fix TS2741 errors in tests/msw/state.ts by adding missing Gemini type definitions - Fix ProviderCard.extractApiUrl to support GOOGLE_GEMINI_BASE_URL display - Add missing apps.gemini i18n keys (zh/en) for proper app name display - Fix MCP service Gemini cross-app duplication logic to prevent self-copy **Technical Details:** - tests/msw/state.ts: Add gemini default providers, current ID, and MCP config - ProviderCard.tsx: Check both ANTHROPIC_BASE_URL and GOOGLE_GEMINI_BASE_URL - services/mcp.rs: Skip Gemini in sync_other_side logic with unreachable!() guards - Run pnpm format to auto-fix code style issues **Verification:** - ✅ pnpm typecheck passes - ✅ pnpm format completed * feat(gemini): enhance authentication and config parsing - Add strict and lenient .env parsing modes - Implement PackyCode partner authentication detection - Support Google OAuth official authentication - Auto-configure security.auth.selectedType for PackyCode - Add comprehensive test coverage for all auth types - Update i18n for OAuth hints and Gemini config --------- Co-authored-by: Jason <farion1231@gmail.com>
255 lines
6.3 KiB
TypeScript
255 lines
6.3 KiB
TypeScript
import type { AppId } from "@/lib/api/types";
|
|
import type { McpServer, Provider, Settings } from "@/types";
|
|
|
|
type ProvidersByApp = Record<AppId, Record<string, Provider>>;
|
|
type CurrentProviderState = Record<AppId, string>;
|
|
type McpConfigState = Record<AppId, Record<string, McpServer>>;
|
|
|
|
const createDefaultProviders = (): ProvidersByApp => ({
|
|
claude: {
|
|
"claude-1": {
|
|
id: "claude-1",
|
|
name: "Claude Default",
|
|
settingsConfig: {},
|
|
category: "official",
|
|
sortIndex: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
"claude-2": {
|
|
id: "claude-2",
|
|
name: "Claude Custom",
|
|
settingsConfig: {},
|
|
category: "custom",
|
|
sortIndex: 1,
|
|
createdAt: Date.now() + 1,
|
|
},
|
|
},
|
|
codex: {
|
|
"codex-1": {
|
|
id: "codex-1",
|
|
name: "Codex Default",
|
|
settingsConfig: {},
|
|
category: "official",
|
|
sortIndex: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
"codex-2": {
|
|
id: "codex-2",
|
|
name: "Codex Secondary",
|
|
settingsConfig: {},
|
|
category: "custom",
|
|
sortIndex: 1,
|
|
createdAt: Date.now() + 1,
|
|
},
|
|
},
|
|
gemini: {
|
|
"gemini-1": {
|
|
id: "gemini-1",
|
|
name: "Gemini Default",
|
|
settingsConfig: {
|
|
env: {
|
|
GEMINI_API_KEY: "test-key",
|
|
GOOGLE_GEMINI_BASE_URL: "https://generativelanguage.googleapis.com",
|
|
},
|
|
},
|
|
category: "official",
|
|
sortIndex: 0,
|
|
createdAt: Date.now(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const createDefaultCurrent = (): CurrentProviderState => ({
|
|
claude: "claude-1",
|
|
codex: "codex-1",
|
|
gemini: "gemini-1",
|
|
});
|
|
|
|
let providers = createDefaultProviders();
|
|
let current = createDefaultCurrent();
|
|
let settingsState: Settings = {
|
|
showInTray: true,
|
|
minimizeToTrayOnClose: true,
|
|
enableClaudePluginIntegration: false,
|
|
claudeConfigDir: "/default/claude",
|
|
codexConfigDir: "/default/codex",
|
|
language: "zh",
|
|
};
|
|
let appConfigDirOverride: string | null = null;
|
|
let mcpConfigs: McpConfigState = {
|
|
claude: {
|
|
sample: {
|
|
id: "sample",
|
|
name: "Sample Claude Server",
|
|
enabled: true,
|
|
server: {
|
|
type: "stdio",
|
|
command: "claude-server",
|
|
},
|
|
},
|
|
},
|
|
codex: {
|
|
httpServer: {
|
|
id: "httpServer",
|
|
name: "HTTP Codex Server",
|
|
enabled: false,
|
|
server: {
|
|
type: "http",
|
|
url: "http://localhost:3000",
|
|
},
|
|
},
|
|
},
|
|
gemini: {},
|
|
};
|
|
|
|
const cloneProviders = (value: ProvidersByApp) =>
|
|
JSON.parse(JSON.stringify(value)) as ProvidersByApp;
|
|
|
|
export const resetProviderState = () => {
|
|
providers = createDefaultProviders();
|
|
current = createDefaultCurrent();
|
|
settingsState = {
|
|
showInTray: true,
|
|
minimizeToTrayOnClose: true,
|
|
enableClaudePluginIntegration: false,
|
|
claudeConfigDir: "/default/claude",
|
|
codexConfigDir: "/default/codex",
|
|
language: "zh",
|
|
};
|
|
appConfigDirOverride = null;
|
|
mcpConfigs = {
|
|
claude: {
|
|
sample: {
|
|
id: "sample",
|
|
name: "Sample Claude Server",
|
|
enabled: true,
|
|
server: {
|
|
type: "stdio",
|
|
command: "claude-server",
|
|
},
|
|
},
|
|
},
|
|
codex: {
|
|
httpServer: {
|
|
id: "httpServer",
|
|
name: "HTTP Codex Server",
|
|
enabled: false,
|
|
server: {
|
|
type: "http",
|
|
url: "http://localhost:3000",
|
|
},
|
|
},
|
|
},
|
|
gemini: {},
|
|
};
|
|
};
|
|
|
|
export const getProviders = (appType: AppId) =>
|
|
cloneProviders(providers)[appType] ?? {};
|
|
|
|
export const getCurrentProviderId = (appType: AppId) => current[appType] ?? "";
|
|
|
|
export const setCurrentProviderId = (appType: AppId, providerId: string) => {
|
|
current[appType] = providerId;
|
|
};
|
|
|
|
export const updateProviders = (appType: AppId, data: Record<string, Provider>) => {
|
|
providers[appType] = cloneProviders({ [appType]: data } as ProvidersByApp)[appType];
|
|
};
|
|
|
|
export const setProviders = (appType: AppId, data: Record<string, Provider>) => {
|
|
providers[appType] = JSON.parse(JSON.stringify(data)) as Record<string, Provider>;
|
|
};
|
|
|
|
export const addProvider = (appType: AppId, provider: Provider) => {
|
|
providers[appType] = providers[appType] ?? {};
|
|
providers[appType][provider.id] = provider;
|
|
};
|
|
|
|
export const updateProvider = (appType: AppId, provider: Provider) => {
|
|
if (!providers[appType]) return;
|
|
providers[appType][provider.id] = {
|
|
...providers[appType][provider.id],
|
|
...provider,
|
|
};
|
|
};
|
|
|
|
export const deleteProvider = (appType: AppId, providerId: string) => {
|
|
if (!providers[appType]) return;
|
|
delete providers[appType][providerId];
|
|
if (current[appType] === providerId) {
|
|
const fallback = Object.keys(providers[appType])[0] ?? "";
|
|
current[appType] = fallback;
|
|
}
|
|
};
|
|
|
|
export const updateSortOrder = (
|
|
appType: AppId,
|
|
updates: { id: string; sortIndex: number }[],
|
|
) => {
|
|
if (!providers[appType]) return;
|
|
updates.forEach(({ id, sortIndex }) => {
|
|
const provider = providers[appType][id];
|
|
if (provider) {
|
|
providers[appType][id] = { ...provider, sortIndex };
|
|
}
|
|
});
|
|
};
|
|
|
|
export const listProviders = (appType: AppId) =>
|
|
JSON.parse(JSON.stringify(providers[appType] ?? {})) as Record<string, Provider>;
|
|
|
|
export const getSettings = () => JSON.parse(JSON.stringify(settingsState)) as Settings;
|
|
|
|
export const setSettings = (data: Partial<Settings>) => {
|
|
settingsState = { ...settingsState, ...data };
|
|
};
|
|
|
|
export const getAppConfigDirOverride = () => appConfigDirOverride;
|
|
|
|
export const setAppConfigDirOverrideState = (value: string | null) => {
|
|
appConfigDirOverride = value;
|
|
};
|
|
|
|
export const getMcpConfig = (appType: AppId) => {
|
|
const servers = JSON.parse(
|
|
JSON.stringify(mcpConfigs[appType] ?? {}),
|
|
) as Record<string, McpServer>;
|
|
return {
|
|
configPath: `/mock/${appType}.mcp.json`,
|
|
servers,
|
|
};
|
|
};
|
|
|
|
export const setMcpConfig = (appType: AppId, value: Record<string, McpServer>) => {
|
|
mcpConfigs[appType] = JSON.parse(JSON.stringify(value)) as Record<string, McpServer>;
|
|
};
|
|
|
|
export const setMcpServerEnabled = (
|
|
appType: AppId,
|
|
id: string,
|
|
enabled: boolean,
|
|
) => {
|
|
if (!mcpConfigs[appType]?.[id]) return;
|
|
mcpConfigs[appType][id] = {
|
|
...mcpConfigs[appType][id],
|
|
enabled,
|
|
};
|
|
};
|
|
|
|
export const upsertMcpServer = (
|
|
appType: AppId,
|
|
id: string,
|
|
server: McpServer,
|
|
) => {
|
|
if (!mcpConfigs[appType]) {
|
|
mcpConfigs[appType] = {};
|
|
}
|
|
mcpConfigs[appType][id] = JSON.parse(JSON.stringify(server)) as McpServer;
|
|
};
|
|
|
|
export const deleteMcpServer = (appType: AppId, id: string) => {
|
|
if (!mcpConfigs[appType]) return;
|
|
delete mcpConfigs[appType][id];
|
|
};
|