diff --git a/src/renderer/packages/model-setting-utils/index.ts b/src/renderer/packages/model-setting-utils/index.ts index 3f4f25fbe..102159cd7 100644 --- a/src/renderer/packages/model-setting-utils/index.ts +++ b/src/renderer/packages/model-setting-utils/index.ts @@ -11,6 +11,7 @@ import GroqSettingUtil from './groq-setting-util' import { ModelSettingUtil } from './interface' import LMStudioSettingUtil from './lmstudio-setting-util' import MistralAISettingUtil from './mistral-ai-setting-util' +import NovitaAISettingUtil from './novita-ai-setting-util' import OllamaSettingUtil from './ollama-setting-util' import OpenAISettingUtil from './openai-setting-util' import PerplexitySettingUtil from './perplexity-setting-util' @@ -33,6 +34,7 @@ export function getModelSettingUtil(aiProvider: ModelProvider): ModelSettingUtil [ModelProviderEnum.VolcEngine]: VolcEngineSettingUtil, [ModelProviderEnum.MistralAI]: MistralAISettingUtil, [ModelProviderEnum.LMStudio]: LMStudioSettingUtil, + [ModelProviderEnum.NovitaAI]: NovitaAISettingUtil, [ModelProviderEnum.Perplexity]: PerplexitySettingUtil, [ModelProviderEnum.XAI]: XAISettingUtil, [ModelProviderEnum.Custom]: CustomModelSettingUtil, diff --git a/src/renderer/packages/model-setting-utils/novita-ai-setting-util.ts b/src/renderer/packages/model-setting-utils/novita-ai-setting-util.ts new file mode 100644 index 000000000..a7ec5bf53 --- /dev/null +++ b/src/renderer/packages/model-setting-utils/novita-ai-setting-util.ts @@ -0,0 +1,67 @@ +import OpenAI from 'src/shared/models/openai' +import { type ModelProvider, ModelProviderEnum, type ProviderSettings } from 'src/shared/types' +import { createModelDependencies } from '@/adapters' +import BaseConfig from './base-config' +import type { ModelSettingUtil } from './interface' + +export default class NovitaAISettingUtil extends BaseConfig implements ModelSettingUtil { + public provider: ModelProvider = ModelProviderEnum.NovitaAI + + async getCurrentModelDisplayName( + model: string, + ): Promise { + return `Novita AI (${model})` + } + + protected async listProviderModels(settings: ProviderSettings): Promise { + const model = settings.models?.[0] || { modelId: 'deepseek/deepseek-v3-0324' } + const dependencies = await createModelDependencies() + + const openai = new OpenAI( + { + apiHost: settings.apiHost!, + apiKey: settings.apiKey!, + model, + temperature: 0, + dalleStyle: 'vivid', + injectDefaultMetadata: false, + useProxy: settings.useProxy || false, + }, + dependencies + ) + + try { + const [chatModels, embeddingModels] = await Promise.all([ + openai.listModels(), + this.fetchEmbeddingModels(settings.apiHost!, settings.apiKey!), + ]) + + return [...chatModels, ...embeddingModels] + } catch (error) { + console.error('Failed to fetch Novita AI models:', error) + return openai.listModels() + } + } + + private async fetchEmbeddingModels(apiHost: string, apiKey: string): Promise { + try { + const response = await fetch(`${apiHost}/models?model_type=embedding`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + return data.data?.map((model: any) => model.id) || [] + } catch (error) { + console.error('Failed to fetch embedding models:', error) + return [] + } + } +} \ No newline at end of file diff --git a/src/renderer/static/icons/providers/novita-ai.png b/src/renderer/static/icons/providers/novita-ai.png new file mode 100644 index 000000000..8598b349a Binary files /dev/null and b/src/renderer/static/icons/providers/novita-ai.png differ diff --git a/src/shared/defaults.ts b/src/shared/defaults.ts index 24f5c2c91..5b050d7c6 100644 --- a/src/shared/defaults.ts +++ b/src/shared/defaults.ts @@ -695,4 +695,89 @@ export const SystemProviders: ProviderBaseInfo[] = [ ], }, }, + { + id: ModelProviderEnum.NovitaAI, + name: 'Novita AI', + type: ModelProviderType.OpenAI, + urls: { + website: 'https://novita.ai', + }, + defaultSettings: { + apiHost: 'https://api.novita.ai/v3/openai', + apiPath: '/chat/completions', + models: [ + { + modelId: 'deepseek/deepseek-v3-0324', + contextWindow: 163840, + maxOutput: 163840, + capabilities: ['tool_use'], + }, + { + modelId: 'deepseek/deepseek-r1-0528', + contextWindow: 163840, + maxOutput: 163840, + capabilities: ['tool_use'], + }, + { + modelId: 'moonshotai/kimi-k2-instruct', + contextWindow: 131072, + maxOutput: 131072, + capabilities: ['tool_use'], + }, + { + modelId: 'zai-org/glm-4.5', + contextWindow: 131072, + maxOutput: 131072, + capabilities: ['tool_use'], + }, + { + modelId: 'baidu/ernie-4.5-vl-424b-a47b', + contextWindow: 123000, + maxOutput: 16000, + capabilities: ['tool_use', 'vision'], + }, + { + modelId: 'qwen/qwen3-235b-a22b-thinking-2507', + contextWindow: 131072, + maxOutput: 131072, + capabilities: ['tool_use'], + }, + { + modelId: 'qwen/qwen3-235b-a22b-instruct-2507', + contextWindow: 262144, + maxOutput: 262144, + capabilities: ['tool_use'], + }, + { + modelId: 'qwen/qwen3-30b-a3b-fp8', + contextWindow: 262144, + maxOutput: 262144, + }, + { + modelId: 'qwen/qwen2.5-vl-72b-instruct', + contextWindow: 32768, + maxOutput: 32768, + capabilities: ['vision'], + }, + { + modelId: 'google/gemma-3-27b-it', + contextWindow: 32000, + maxOutput: 32000, + capabilities: ['tool_use', 'vision'], + }, + { + modelId: 'qwen/qwen3-embedding-8b', + contextWindow: 32768, + maxOutput: 4096, + type: 'embedding', + }, + { + modelId: 'baai/bge-m3', + contextWindow: 8192, + maxOutput: 96000, + type: 'embedding', + } + ], + }, + }, ] diff --git a/src/shared/models/index.ts b/src/shared/models/index.ts index 7de929387..ca1f10f5f 100644 --- a/src/shared/models/index.ts +++ b/src/shared/models/index.ts @@ -270,6 +270,23 @@ export function getModel(setting: Settings, config: Config, dependencies: ModelD }, dependencies ) + + case ModelProviderEnum.NovitaAI: + return new CustomOpenAI( + { + apiKey: providerSetting.apiKey || '', + apiHost: formattedApiHost, + apiPath: providerSetting.apiPath || '/chat/completions', + model, + temperature: setting.temperature, + topP: setting.topP, + maxTokens: setting.maxTokens, + stream: setting.stream, + useProxy: providerSetting.useProxy, + }, + dependencies + ) + default: if (providerBaseInfo.isCustom) { return new CustomOpenAI( @@ -308,6 +325,7 @@ export const aiProviderNameHash: Record = { [ModelProviderEnum.LMStudio]: 'LM Studio API', [ModelProviderEnum.Perplexity]: 'Perplexity API', [ModelProviderEnum.XAI]: 'xAI API', + [ModelProviderEnum.NovitaAI]: 'Novita AI API', [ModelProviderEnum.Custom]: 'Custom Provider', } @@ -383,6 +401,11 @@ export const AIModelProviderMenuOptionList = [ label: aiProviderNameHash[ModelProviderEnum.ChatGLM6B], disabled: false, }, + { + value: ModelProviderEnum.NovitaAI, + label: aiProviderNameHash[ModelProviderEnum.NovitaAI], + disabled: false, + }, // { // value: 'hunyuan', // label: '腾讯混元', diff --git a/src/shared/types.ts b/src/shared/types.ts index a83141c2b..e61f76e5a 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -255,6 +255,7 @@ export enum ModelProviderEnum { LMStudio = 'lm-studio', Perplexity = 'perplexity', XAI = 'xAI', + NovitaAI = 'novita-ai', Custom = 'custom', } export type ModelProvider = ModelProviderEnum | string diff --git a/src/shared/utils/llm_utils.ts b/src/shared/utils/llm_utils.ts index 1652e9029..f918f3d77 100644 --- a/src/shared/utils/llm_utils.ts +++ b/src/shared/utils/llm_utils.ts @@ -116,6 +116,7 @@ export function isOpenAICompatible(providerId: string, modelId: string) { ModelProviderEnum.Groq, ModelProviderEnum.DeepSeek, ModelProviderEnum.LMStudio, + ModelProviderEnum.NovitaAI, ].includes(providerId as ModelProviderEnum) || providerId.startsWith('custom-provider-') ) }