diff --git a/BitFun-Installer/src/data/modelProviders.ts b/BitFun-Installer/src/data/modelProviders.ts index c09070c6..86cff759 100644 --- a/BitFun-Installer/src/data/modelProviders.ts +++ b/BitFun-Installer/src/data/modelProviders.ts @@ -25,6 +25,8 @@ export const PROVIDER_DISPLAY_ORDER: string[] = [ 'deepseek', 'volcengine', 'siliconflow', + 'nvidia', + 'openrouter', 'minimax', 'moonshot', 'anthropic', @@ -161,6 +163,24 @@ export const PROVIDER_TEMPLATES: Record = { }, ], }, + nvidia: { + id: 'nvidia', + nameKey: 'model.providers.nvidia.name', + descriptionKey: 'model.providers.nvidia.description', + baseUrl: 'https://integrate.api.nvidia.com/v1', + format: 'openai', + models: [], + helpUrl: 'https://build.nvidia.com/settings/api-keys', + }, + openrouter: { + id: 'openrouter', + nameKey: 'model.providers.openrouter.name', + descriptionKey: 'model.providers.openrouter.description', + baseUrl: 'https://openrouter.ai/api/v1', + format: 'openai', + models: [], + helpUrl: 'https://openrouter.ai/keys', + }, }; export function getOrderedProviders(): ProviderTemplate[] { diff --git a/BitFun-Installer/src/i18n/locales/en.json b/BitFun-Installer/src/i18n/locales/en.json index a47b5acc..4d1427e7 100644 --- a/BitFun-Installer/src/i18n/locales/en.json +++ b/BitFun-Installer/src/i18n/locales/en.json @@ -116,6 +116,14 @@ "default": "OpenAI Format - Default", "anthropic": "Anthropic Format" } + }, + "nvidia": { + "name": "NVIDIA", + "description": "NVIDIA NIM Model Platform" + }, + "openrouter": { + "name": "OpenRouter", + "description": "OpenRouter Model Platform" } }, "modelNameSelectPlaceholder": "Select a model...", diff --git a/BitFun-Installer/src/i18n/locales/zh.json b/BitFun-Installer/src/i18n/locales/zh.json index 22b638de..6b54c7ca 100644 --- a/BitFun-Installer/src/i18n/locales/zh.json +++ b/BitFun-Installer/src/i18n/locales/zh.json @@ -116,6 +116,14 @@ "default": "OpenAI格式-默认", "anthropic": "Anthropic格式" } + }, + "nvidia": { + "name": "NVIDIA", + "description": "NVIDIA NIM 大模型平台" + }, + "openrouter": { + "name": "OpenRouter", + "description": "OpenRouter 大模型平台" } }, "modelNameSelectPlaceholder": "选择模型...", diff --git a/src/crates/core/src/agentic/image_analysis/types.rs b/src/crates/core/src/agentic/image_analysis/types.rs index 0b57de22..056d5d5e 100644 --- a/src/crates/core/src/agentic/image_analysis/types.rs +++ b/src/crates/core/src/agentic/image_analysis/types.rs @@ -120,7 +120,7 @@ impl ImageLimits { /// Get limits based on model provider pub fn for_provider(provider: &str) -> Self { match provider.to_lowercase().as_str() { - "openai" | "response" | "responses" => Self { + "openai" | "response" | "responses" | "nvidia" | "openrouter" => Self { max_size: 20 * 1024 * 1024, // 20MB max_width: 2048, max_height: 2048, diff --git a/src/crates/core/src/infrastructure/ai/client.rs b/src/crates/core/src/infrastructure/ai/client.rs index dd5faa33..3409fcb3 100644 --- a/src/crates/core/src/infrastructure/ai/client.rs +++ b/src/crates/core/src/infrastructure/ai/client.rs @@ -158,7 +158,10 @@ impl AIClient { fn build_test_connection_extra_body(&self) -> Option { let provider = self.config.format.to_ascii_lowercase(); - if !matches!(provider.as_str(), "openai" | "response" | "responses") { + if !matches!( + provider.as_str(), + "openai" | "response" | "responses" | "nvidia" | "openrouter" + ) { return self.config.custom_request_body.clone(); } diff --git a/src/crates/core/src/util/types/config.rs b/src/crates/core/src/util/types/config.rs index 0e634efe..b90c5393 100644 --- a/src/crates/core/src/util/types/config.rs +++ b/src/crates/core/src/util/types/config.rs @@ -57,7 +57,7 @@ fn resolve_request_url(base_url: &str, provider: &str, model_name: &str) -> Stri } match provider.trim().to_ascii_lowercase().as_str() { - "openai" => append_endpoint(&trimmed, "chat/completions"), + "openai" | "nvidia" | "openrouter" => append_endpoint(&trimmed, "chat/completions"), "response" | "responses" => append_endpoint(&trimmed, "responses"), "anthropic" => append_endpoint(&trimmed, "v1/messages"), "gemini" | "google" => resolve_gemini_request_url(&trimmed, model_name), @@ -151,6 +151,22 @@ mod tests { "https://api.openbitfun.com/v1beta/models/gemini-2.5-pro:streamGenerateContent?alt=sse" ); } + + #[test] + fn resolves_nvidia_request_url() { + assert_eq!( + resolve_request_url("https://integrate.api.nvidia.com/v1", "nvidia", ""), + "https://integrate.api.nvidia.com/v1/chat/completions" + ); + } + + #[test] + fn resolves_openrouter_request_url() { + assert_eq!( + resolve_request_url("https://openrouter.ai/api/v1", "openrouter", ""), + "https://openrouter.ai/api/v1/chat/completions" + ); + } } impl TryFrom for AIConfig { diff --git a/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx b/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx index 5250674e..1413aefa 100644 --- a/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx +++ b/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx @@ -149,21 +149,16 @@ export const VirtualMessageList = forwardRef((_, ref) => scrollToBottom, }), [scrollToTurn, scrollToIndex, scrollToBottom]); - // ── Core scroll policy: processing → auto-scroll to bottom ──────────── + // ── Initial scroll to bottom when processing starts ────────────────── + // Note: followOutput handles continuous auto-scroll, so we only need + // an initial scroll here. The 300ms interval was removed because it + // conflicted with followOutput and caused visual jitter. useEffect(() => { if (!isProcessing) return; if (virtuosoRef.current) { - virtuosoRef.current.scrollTo({ top: 999999999, behavior: 'smooth' }); + virtuosoRef.current.scrollTo({ top: 999999999, behavior: 'auto' }); } - - const intervalId = setInterval(() => { - if (virtuosoRef.current) { - virtuosoRef.current.scrollTo({ top: 999999999, behavior: 'smooth' }); - } - }, 300); - - return () => clearInterval(intervalId); }, [isProcessing]); const handleFollowOutput = useCallback(() => { diff --git a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts index 73cbb3dc..601f92f1 100644 --- a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts +++ b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts @@ -181,6 +181,28 @@ export const PROVIDER_TEMPLATES: Record = { { url: 'https://api.siliconflow.cn/v1', format: 'openai', note: 'default' }, { url: 'https://api.siliconflow.cn/v1/messages', format: 'anthropic', note: 'Anthropic' }, ] + }, + + nvidia: { + id: 'nvidia', + name: t('settings/ai-model:providers.nvidia.name'), + baseUrl: 'https://integrate.api.nvidia.com/v1', + format: 'openai', + models: [], + requiresApiKey: true, + description: t('settings/ai-model:providers.nvidia.description'), + helpUrl: 'https://build.nvidia.com/settings/api-keys' + }, + + openrouter: { + id: 'openrouter', + name: t('settings/ai-model:providers.openrouter.name'), + baseUrl: 'https://openrouter.ai/api/v1', + format: 'openai', + models: [], + requiresApiKey: true, + description: t('settings/ai-model:providers.openrouter.description'), + helpUrl: 'https://openrouter.ai/keys' } }; diff --git a/src/web-ui/src/infrastructure/config/services/providerCatalog.ts b/src/web-ui/src/infrastructure/config/services/providerCatalog.ts index 57c02d3e..45d6a151 100644 --- a/src/web-ui/src/infrastructure/config/services/providerCatalog.ts +++ b/src/web-ui/src/infrastructure/config/services/providerCatalog.ts @@ -63,6 +63,14 @@ export const PROVIDER_URL_CATALOG: ProviderUrlCatalogItem[] = [ 'https://api.siliconflow.cn/v1/messages', ], }, + { + id: 'nvidia', + baseUrl: 'https://integrate.api.nvidia.com/v1', + }, + { + id: 'openrouter', + baseUrl: 'https://openrouter.ai/api/v1', + }, ]; export function normalizeProviderBaseUrl(url: string): string { diff --git a/src/web-ui/src/locales/en-US/settings/ai-model.json b/src/web-ui/src/locales/en-US/settings/ai-model.json index 3073618e..06201f55 100644 --- a/src/web-ui/src/locales/en-US/settings/ai-model.json +++ b/src/web-ui/src/locales/en-US/settings/ai-model.json @@ -86,6 +86,14 @@ "default": "OpenAI Format - Default", "anthropic": "Anthropic Format" } + }, + "nvidia": { + "name": "NVIDIA", + "description": "NVIDIA NIM Model Platform" + }, + "openrouter": { + "name": "OpenRouter", + "description": "OpenRouter Model Platform" } }, "tabs": { diff --git a/src/web-ui/src/locales/zh-CN/settings/ai-model.json b/src/web-ui/src/locales/zh-CN/settings/ai-model.json index 093bf171..d8caf209 100644 --- a/src/web-ui/src/locales/zh-CN/settings/ai-model.json +++ b/src/web-ui/src/locales/zh-CN/settings/ai-model.json @@ -86,6 +86,14 @@ "default": "OpenAI格式-默认", "anthropic": "Anthropic格式" } + }, + "nvidia": { + "name": "NVIDIA", + "description": "NVIDIA NIM 大模型平台" + }, + "openrouter": { + "name": "OpenRouter", + "description": "OpenRouter 大模型平台" } }, "tabs": {