From f628662e48e40d7a4b0d39ee67ec2574215b1f46 Mon Sep 17 00:00:00 2001 From: gaoyiman Date: Wed, 15 Apr 2026 12:16:36 +0800 Subject: [PATCH] feat: add volcengine and byteplus provider presets --- README.md | 2 +- src/shared/db/settings.ts | 238 ++++++++++++++++++++++++++++++++------ 2 files changed, 203 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index d00963d..0649b71 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ WorkAny is a desktop AI agent application that executes tasks through natural la - **Artifact Preview** - Live preview for HTML/React/code files - **MCP Support** - Model Context Protocol server integration (stdio/SSE/HTTP) - **Skills Support** - Custom agent skills for extended capabilities -- **Multi-provider** - OpenRouter, Anthropic, OpenAI, and any compatible API endpoint +- **Multi-provider** - Built-in presets for OpenRouter, MiniMax, Z.ai, Volcengine, Volcengine Coding Plan, BytePlus, BytePlus Coding Plan, Kimi, and any compatible API endpoint ## Project Structure diff --git a/src/shared/db/settings.ts b/src/shared/db/settings.ts index b22a1f2..4488545 100644 --- a/src/shared/db/settings.ts +++ b/src/shared/db/settings.ts @@ -235,6 +235,26 @@ export interface Settings { // AI Provider Configuration // ============================================================================ +function cloneAIProvider(provider: AIProvider): AIProvider { + return { + ...provider, + models: [...provider.models], + }; +} + +const legacyVolcengineProvider: AIProvider = { + id: 'volcengine', + name: 'Volcengine', + apiKey: '', + baseUrl: 'https://ark.cn-beijing.volces.com/api/coding', + enabled: true, + models: ['ark-code-latest'], + apiType: 'openai-completions', + icon: 'V', + apiKeyUrl: 'https://volcengine.com/L/Sq5rSgyFu_E', + canDelete: true, +}; + // Default providers with full configuration export const defaultProviders: AIProvider[] = [ { @@ -278,12 +298,82 @@ export const defaultProviders: AIProvider[] = [ id: 'volcengine', name: 'Volcengine', apiKey: '', - baseUrl: 'https://ark.cn-beijing.volces.com/api/coding', + baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', enabled: true, - models: ['ark-code-latest'], + models: [ + 'doubao-seed-2-0-pro-260215', + 'doubao-seed-2-0-lite-260215', + 'doubao-seed-2-0-mini-260215', + 'doubao-seed-2-0-code-preview-260215', + 'kimi-k2-5-260127', + 'glm-4-7-251222', + 'deepseek-v3-2-251201', + ], + defaultModel: 'doubao-seed-2-0-pro-260215', apiType: 'openai-completions', icon: 'V', - apiKeyUrl: 'https://volcengine.com/L/Sq5rSgyFu_E', + apiKeyUrl: 'https://www.volcengine.com/product/ark', + canDelete: true, + }, + { + id: 'volcengine-coding-plan', + name: 'Volcengine Coding Plan', + apiKey: '', + baseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3', + enabled: true, + models: [ + 'doubao-seed-2.0-code', + 'doubao-seed-2.0-pro', + 'doubao-seed-2.0-lite', + 'doubao-seed-code', + 'minimax-m2.5', + 'glm-4.7', + 'deepseek-v3.2', + 'kimi-k2.5', + ], + defaultModel: 'doubao-seed-2.0-code', + apiType: 'openai-completions', + icon: 'V', + apiKeyUrl: 'https://www.volcengine.com/activity/codingplan', + canDelete: true, + }, + { + id: 'byteplus', + name: 'BytePlus', + apiKey: '', + baseUrl: 'https://ark.ap-southeast.bytepluses.com/api/v3', + enabled: true, + models: [ + 'seed-2-0-pro-260328', + 'seed-2-0-lite-260228', + 'seed-2-0-mini-260215', + 'kimi-k2-5-260127', + 'glm-4-7-251222', + ], + defaultModel: 'seed-2-0-pro-260328', + apiType: 'openai-completions', + icon: 'B', + apiKeyUrl: 'https://www.byteplus.com/en/product/modelark', + canDelete: true, + }, + { + id: 'byteplus-coding-plan', + name: 'BytePlus Coding Plan', + apiKey: '', + baseUrl: 'https://ark.ap-southeast.bytepluses.com/api/coding/v3', + enabled: true, + models: [ + 'dola-seed-2.0-pro', + 'dola-seed-2.0-lite', + 'bytedance-seed-code', + 'glm-4.7', + 'kimi-k2.5', + 'gpt-oss-120b', + ], + defaultModel: 'dola-seed-2.0-pro', + apiType: 'openai-completions', + icon: 'B', + apiKeyUrl: 'https://www.byteplus.com/en/activity/codingplan', canDelete: true, }, { @@ -355,14 +445,24 @@ export const providerDefaultModels: Record = { // Model suggestions for custom providers (matched by name pattern) export const customProviderModels: Record = { 火山: [ - 'doubao-1-5-pro-256k-250115', - 'doubao-1-5-lite-32k-250115', - 'deepseek-v3-250324', + 'doubao-seed-2-0-pro-260215', + 'doubao-seed-2-0-lite-260215', + 'deepseek-v3-2-251201', ], volcengine: [ - 'doubao-1-5-pro-256k-250115', - 'doubao-1-5-lite-32k-250115', - 'deepseek-v3-250324', + 'doubao-seed-2-0-pro-260215', + 'doubao-seed-2-0-lite-260215', + 'doubao-seed-2-0-code-preview-260215', + 'kimi-k2-5-260127', + 'glm-4-7-251222', + 'deepseek-v3-2-251201', + ], + byteplus: [ + 'seed-2-0-pro-260328', + 'seed-2-0-lite-260228', + 'seed-2-0-mini-260215', + 'kimi-k2-5-260127', + 'glm-4-7-251222', ], deepseek: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner'], kimi: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'], @@ -398,7 +498,7 @@ export const defaultSettings: Settings = { nickname: 'Guest User', avatar: '', }, - providers: defaultProviders, + providers: defaultProviders.map(cloneAIProvider), defaultProvider: '', // Empty until user configures a provider defaultModel: '', mcpConfigPath: '', // Will be resolved to app data dir at init @@ -443,6 +543,95 @@ let db: Awaited< ReturnType > | null = null; +function arraysEqual(left: string[], right: string[]): boolean { + return ( + left.length === right.length && + left.every((value, index) => value === right[index]) + ); +} + +function shouldMigrateLegacyVolcengineProvider(provider: AIProvider): boolean { + return ( + provider.id === legacyVolcengineProvider.id && + provider.name === legacyVolcengineProvider.name && + provider.baseUrl === legacyVolcengineProvider.baseUrl && + provider.apiType === legacyVolcengineProvider.apiType && + provider.icon === legacyVolcengineProvider.icon && + provider.apiKeyUrl === legacyVolcengineProvider.apiKeyUrl && + arraysEqual(provider.models, legacyVolcengineProvider.models) + ); +} + +function migrateLegacyVolcengineProvider(provider: AIProvider): AIProvider { + const latestProvider = defaultProviders.find( + (item) => item.id === provider.id + ); + if (!latestProvider) { + return provider; + } + + const defaultModel = + provider.defaultModel && + provider.defaultModel !== legacyVolcengineProvider.models[0] && + latestProvider.models.includes(provider.defaultModel) + ? provider.defaultModel + : latestProvider.defaultModel || latestProvider.models[0] || ''; + + return { + ...cloneAIProvider(latestProvider), + apiKey: provider.apiKey, + enabled: provider.enabled, + defaultModel, + }; +} + +function normalizeProviders(providers: AIProvider[]): AIProvider[] { + const normalizedProviders = providers.map((provider) => + shouldMigrateLegacyVolcengineProvider(provider) + ? migrateLegacyVolcengineProvider(provider) + : provider + ); + + const existingIds = new Set( + normalizedProviders.map((provider) => provider.id) + ); + for (const defaultProvider of defaultProviders) { + if (!existingIds.has(defaultProvider.id)) { + normalizedProviders.push(cloneAIProvider(defaultProvider)); + } + } + + return normalizedProviders; +} + +function normalizeDefaultModelSelection(settings: Settings): Settings { + if (!settings.defaultProvider || settings.defaultProvider === 'default') { + return settings; + } + + const selectedProvider = settings.providers.find( + (provider) => provider.id === settings.defaultProvider + ); + + if (!selectedProvider) { + settings.defaultProvider = ''; + settings.defaultModel = ''; + return settings; + } + + if (!selectedProvider.models.includes(settings.defaultModel)) { + settings.defaultModel = + selectedProvider.defaultModel || selectedProvider.models[0] || ''; + } + + return settings; +} + +function normalizeSettings(settings: Settings): Settings { + settings.providers = normalizeProviders(settings.providers); + return normalizeDefaultModelSelection(settings); +} + // Initialize database connection (only in Tauri) async function getDatabase() { if (!isTauriSync()) { @@ -500,12 +689,7 @@ export async function getSettingsAsync(): Promise { // Skip invalid JSON values } } - // Migration: Add missing default providers - for (const defaultProvider of defaultProviders) { - if (!settings.providers.find((p) => p.id === defaultProvider.id)) { - settings.providers.push(defaultProvider); - } - } + normalizeSettings(settings); // Debug: Log loaded settings console.log('[Settings] Loaded from database:', { defaultProvider: settings.defaultProvider, @@ -530,16 +714,7 @@ export async function getSettingsAsync(): Promise { const stored = localStorage.getItem('workany_settings'); if (stored) { const loadedSettings = { ...defaultSettings, ...JSON.parse(stored) }; - // Migration: Add missing default providers - for (const defaultProvider of defaultProviders) { - if ( - !loadedSettings.providers.find( - (p: AIProvider) => p.id === defaultProvider.id - ) - ) { - loadedSettings.providers.push(defaultProvider); - } - } + normalizeSettings(loadedSettings); // Debug: Log loaded settings console.log('[Settings] Loaded from localStorage:', { defaultProvider: loadedSettings.defaultProvider, @@ -576,16 +751,7 @@ export function getSettings(): Settings { const stored = localStorage.getItem('workany_settings'); if (stored) { const loadedSettings = { ...defaultSettings, ...JSON.parse(stored) }; - // Migration: Add missing default providers - for (const defaultProvider of defaultProviders) { - if ( - !loadedSettings.providers.find( - (p: AIProvider) => p.id === defaultProvider.id - ) - ) { - loadedSettings.providers.push(defaultProvider); - } - } + normalizeSettings(loadedSettings); settingsCache = loadedSettings; console.log('[Settings] getSettings loaded from localStorage:', { defaultProvider: loadedSettings.defaultProvider,