Compare commits

...

7 Commits

Author SHA1 Message Date
semantic-release-bot 3e64ee659e 🔖 chore(release): v1.132.17 [skip ci]
### [Version 1.132.17](https://github.com/lobehub/lobe-chat/compare/v1.132.16...v1.132.17)
<sup>Released on **2025-09-27**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix input empty group name.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's fixed

* **misc**: Fix input empty group name, closes [#9441](https://github.com/lobehub/lobe-chat/issues/9441) ([f653ce1](https://github.com/lobehub/lobe-chat/commit/f653ce1))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-09-27 05:57:02 +00:00
huangkairan f653ce1737 🐛 fix: fix input empty group name (#9441)
fix: cant input empty group name
2025-09-27 07:47:21 +02:00
Arvin Xu eeabb69088 ️ perf: fix battery usage (#9444) 2025-09-27 07:46:18 +02:00
sxjeru 356cf029dd feat: Add new provider Ollama Cloud (#9435) 2025-09-27 07:45:47 +02:00
lobehubbot 6e7b420347 📝 docs(bot): Auto sync agents & plugin to readme 2025-09-26 15:43:27 +00:00
semantic-release-bot ee464838ac 🔖 chore(release): v1.132.16 [skip ci]
### [Version&nbsp;1.132.16](https://github.com/lobehub/lobe-chat/compare/v1.132.15...v1.132.16)
<sup>Released on **2025-09-26**</sup>

#### 🐛 Bug Fixes

- **misc**: Resolve qwen-image-edit imageUrls conversion issue.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's fixed

* **misc**: Resolve qwen-image-edit imageUrls conversion issue, closes [#9414](https://github.com/lobehub/lobe-chat/issues/9414) ([ec5af1b](https://github.com/lobehub/lobe-chat/commit/ec5af1b))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-09-26 15:42:22 +00:00
Maple Gao ec5af1b4c7 🐛 fix: resolve qwen-image-edit imageUrls conversion issue (#9414)
* 🔧 fix: resolve missing imageurls convertion

*  test: add comprehensive tests for qwen-image-edit imageUrls conversion

- Add tests for imageUrls array to imageUrl conversion
- Add tests for multiple elements using first element
- Add tests for empty array error handling
- Add tests for imageUrl priority over imageUrls
- Add tests for missing parameters error handling
- All 20 test cases pass (5 new + 15 existing)

---------

Co-authored-by: Arvin Xu <arvinx@foxmail.com>
2025-09-26 17:32:18 +02:00
23 changed files with 396 additions and 15 deletions
+50
View File
@@ -2,6 +2,56 @@
# Changelog
### [Version 1.132.17](https://github.com/lobehub/lobe-chat/compare/v1.132.16...v1.132.17)
<sup>Released on **2025-09-27**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix input empty group name.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix input empty group name, closes [#9441](https://github.com/lobehub/lobe-chat/issues/9441) ([f653ce1](https://github.com/lobehub/lobe-chat/commit/f653ce1))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
### [Version 1.132.16](https://github.com/lobehub/lobe-chat/compare/v1.132.15...v1.132.16)
<sup>Released on **2025-09-26**</sup>
#### 🐛 Bug Fixes
- **misc**: Resolve qwen-image-edit imageUrls conversion issue.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Resolve qwen-image-edit imageUrls conversion issue, closes [#9414](https://github.com/lobehub/lobe-chat/issues/9414) ([ec5af1b](https://github.com/lobehub/lobe-chat/commit/ec5af1b))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
### [Version 1.132.15](https://github.com/lobehub/lobe-chat/compare/v1.132.14...v1.132.15)
<sup>Released on **2025-09-25**</sup>
@@ -336,7 +336,6 @@ export default class Browser {
vibrancy: 'sidebar',
visualEffectState: 'active',
webPreferences: {
backgroundThrottling: false,
contextIsolation: true,
preload: join(preloadDir, 'index.js'),
},
+7
View File
@@ -1,4 +1,11 @@
[
{
"children": {
"fixes": ["Resolve qwen-image-edit imageUrls conversion issue."]
},
"date": "2025-09-26",
"version": "1.132.16"
},
{
"children": {
"fixes": ["Add proxyUrl configuration for NEW API provider."]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@lobehub/chat",
"version": "1.132.15",
"version": "1.132.17",
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
"keywords": [
"framework",
+1
View File
@@ -42,6 +42,7 @@
"./novita": "./src/aiModels/novita.ts",
"./nvidia": "./src/aiModels/nvidia.ts",
"./ollama": "./src/aiModels/ollama.ts",
"./ollamacloud": "./src/aiModels/ollamacloud.ts",
"./openai": "./src/aiModels/openai.ts",
"./openrouter": "./src/aiModels/openrouter.ts",
"./perplexity": "./src/aiModels/perplexity.ts",
@@ -36,6 +36,7 @@ import { default as newapi } from './newapi';
import { default as novita } from './novita';
import { default as nvidia } from './nvidia';
import { default as ollama } from './ollama';
import { default as ollamacloud } from './ollamacloud';
import { default as openai } from './openai';
import { default as openrouter } from './openrouter';
import { default as perplexity } from './perplexity';
@@ -120,6 +121,7 @@ export const LOBE_DEFAULT_MODEL_LIST = buildDefaultModelList({
novita,
nvidia,
ollama,
ollamacloud,
openai,
openrouter,
perplexity,
@@ -186,6 +188,7 @@ export { default as newapi } from './newapi';
export { default as novita } from './novita';
export { default as nvidia } from './nvidia';
export { default as ollama } from './ollama';
export { default as ollamacloud } from './ollamacloud';
export { gptImage1ParamsSchema, default as openai, openaiChatModels } from './openai';
export { default as openrouter } from './openrouter';
export { default as perplexity } from './perplexity';
@@ -10,7 +10,6 @@ const ollamaChatModels: AIChatModelCard[] = [
description:
'DeepSeek V3.1:下一代推理模型,提升了复杂推理与链路思考能力,适合需要深入分析的任务。',
displayName: 'DeepSeek V3.1',
enabled: true,
id: 'deepseek-v3.1:671b',
type: 'chat',
},
@@ -23,7 +22,6 @@ const ollamaChatModels: AIChatModelCard[] = [
description:
'GPT-OSS 20B 是 OpenAI 发布的开源大语言模型,采用 MXFP4 量化技术,适合在高端消费级GPU或Apple Silicon Mac上运行。该模型在对话生成、代码编写和推理任务方面表现出色,支持函数调用和工具使用。',
displayName: 'GPT-OSS 20B',
enabled: true,
id: 'gpt-oss:20b',
releasedAt: '2025-08-05',
type: 'chat',
@@ -0,0 +1,70 @@
import { AIChatModelCard } from '../types/aiModel';
const ollamaCloudModels: AIChatModelCard[] = [
{
abilities: {
functionCall: true,
reasoning: true,
},
contextWindowTokens: 163_840,
description:
'DeepSeek V3.1:下一代推理模型,提升了复杂推理与链路思考能力,适合需要深入分析的任务。',
displayName: 'DeepSeek V3.1',
enabled: true,
id: 'deepseek-v3.1:671b',
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
},
contextWindowTokens: 131_072,
description:
'GPT-OSS 20B 是 OpenAI 发布的开源大语言模型,采用 MXFP4 量化技术,适合在高端消费级GPU或Apple Silicon Mac上运行。该模型在对话生成、代码编写和推理任务方面表现出色,支持函数调用和工具使用。',
displayName: 'GPT-OSS 20B',
id: 'gpt-oss:20b',
releasedAt: '2025-08-05',
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
},
contextWindowTokens: 131_072,
description:
'GPT-OSS 120B 是 OpenAI 发布的大型开源语言模型,采用 MXFP4 量化技术,为旗舰级模型。需要多GPU或高性能工作站环境运行,在复杂推理、代码生成和多语言处理方面具备卓越性能,支持高级函数调用和工具集成。',
displayName: 'GPT-OSS 120B',
id: 'gpt-oss:120b',
releasedAt: '2025-08-05',
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
},
contextWindowTokens: 131_072,
description:
'Kimi K2 是由月之暗面 AI 开发的大规模混合专家 (MoE) 语言模型,具有 1 万亿总参数和每次前向传递 320 亿激活参数。它针对代理能力进行了优化,包括高级工具使用、推理和代码合成。',
displayName: 'Kimi K2',
id: 'kimi-k2:1t',
type: 'chat',
},
{
abilities: {
functionCall: true,
},
contextWindowTokens: 262_144,
description:
'阿里巴巴针对代理和编码任务的高性能长上下文模型。',
displayName: 'Qwen3 Coder 480B',
id: 'qwen3-coder:480b',
type: 'chat',
},
];
export const allModels = [...ollamaCloudModels];
export default allModels;
@@ -37,6 +37,7 @@ export enum ModelProvider {
Novita = 'novita',
Nvidia = 'nvidia',
Ollama = 'ollama',
OllamaCloud = 'ollamacloud',
OpenAI = 'openai',
OpenRouter = 'openrouter',
PPIO = 'ppio',
+1
View File
@@ -19,6 +19,7 @@ export { LobeMoonshotAI } from './providers/moonshot';
export { LobeNebiusAI } from './providers/nebius';
export { LobeNewAPIAI } from './providers/newapi';
export { LobeOllamaAI } from './providers/ollama';
export { LobeOllamaCloudAI } from './providers/ollamacloud';
export { LobeOpenAI } from './providers/openai';
export { LobeOpenRouterAI } from './providers/openrouter';
export { LobePerplexityAI } from './providers/perplexity';
@@ -0,0 +1,40 @@
import { ModelProvider } from 'model-bank';
import { createOpenAICompatibleRuntime } from '../../core/openaiCompatibleFactory';
import { processMultiProviderModelList } from '../../utils/modelParse';
export const LobeOllamaCloudAI = createOpenAICompatibleRuntime({
baseURL: 'https://api.ollama.com/v1',
chatCompletion: {
handlePayload: (payload) => {
const { model, ...rest } = payload;
return {
...rest,
model,
} as any;
},
},
debug: {
chatCompletion: () => process.env.DEBUG_OLLAMA_CLOUD_CHAT_COMPLETION === '1',
},
models: async ({ client }) => {
try {
const modelsPage = (await client.models.list()) as any;
const modelList = Array.isArray(modelsPage?.data)
? modelsPage.data
: Array.isArray(modelsPage)
? modelsPage
: [];
return await processMultiProviderModelList(modelList, 'ollamacloud');
} catch (error) {
console.warn(
'Failed to fetch Ollama Cloud models. Please ensure your Ollama Cloud API key is valid:',
error,
);
return [];
}
},
provider: ModelProvider.OllamaCloud,
});
@@ -700,5 +700,167 @@ describe('createQwenImage', () => {
}),
);
});
it('should convert imageUrls array to imageUrl for qwen-image-edit', async () => {
const mockImageUrl =
'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/imageUrls-converted.jpg';
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
output: {
choices: [
{
message: {
content: [{ image: mockImageUrl }],
},
},
],
},
request_id: 'req-imageUrls-123',
}),
});
const payload: CreateImagePayload = {
model: 'qwen-image-edit',
params: {
prompt: 'Edit this image to add a dog',
imageUrls: [
'https://example.com/source-image-1.jpg',
'https://example.com/source-image-2.jpg',
],
},
};
const result = await createQwenImage(payload, mockOptions);
expect(result).toEqual({
imageUrl: mockImageUrl,
});
const [url, options] = (fetch as any).mock.calls[0];
const body = JSON.parse(options.body);
// Verify that the first imageUrl from imageUrls array was used
expect(body.input.messages[0].content[0].image).toBe(
'https://example.com/source-image-1.jpg',
);
});
it('should use first imageUrl when imageUrls has multiple elements', async () => {
const mockImageUrl = 'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/first-element.jpg';
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
output: {
choices: [
{
message: {
content: [{ image: mockImageUrl }],
},
},
],
},
request_id: 'req-first-element',
}),
});
const payload: CreateImagePayload = {
model: 'qwen-image-edit',
params: {
prompt: 'Use the first image only',
imageUrls: [
'https://example.com/first-image.jpg',
'https://example.com/second-image.jpg',
'https://example.com/third-image.jpg',
],
},
};
await createQwenImage(payload, mockOptions);
const [url, options] = (fetch as any).mock.calls[0];
const body = JSON.parse(options.body);
// Should use only the first image from the array
expect(body.input.messages[0].content[0].image).toBe('https://example.com/first-image.jpg');
});
it('should throw error when imageUrls is empty array', async () => {
const payload: CreateImagePayload = {
model: 'qwen-image-edit',
params: {
prompt: 'Edit this image',
imageUrls: [], // Empty array
},
};
await expect(createQwenImage(payload, mockOptions)).rejects.toEqual(
expect.objectContaining({
errorType: 'ProviderBizError',
provider: 'qwen',
}),
);
});
it('should prioritize imageUrl over imageUrls when both are provided', async () => {
const mockImageUrl = 'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/priority-test.jpg';
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
output: {
choices: [
{
message: {
content: [{ image: mockImageUrl }],
},
},
],
},
request_id: 'req-priority-test',
}),
});
const payload: CreateImagePayload = {
model: 'qwen-image-edit',
params: {
prompt: 'Test priority between imageUrl and imageUrls',
imageUrl: 'https://example.com/priority-image.jpg',
imageUrls: [
'https://example.com/should-not-use-1.jpg',
'https://example.com/should-not-use-2.jpg',
],
},
};
await createQwenImage(payload, mockOptions);
const [url, options] = (fetch as any).mock.calls[0];
const body = JSON.parse(options.body);
// Should use imageUrl, not imageUrls
expect(body.input.messages[0].content[0].image).toBe(
'https://example.com/priority-image.jpg',
);
});
it('should throw error when neither imageUrl nor imageUrls are provided', async () => {
const payload: CreateImagePayload = {
model: 'qwen-image-edit',
params: {
prompt: 'Edit this image',
// Neither imageUrl nor imageUrls provided
},
};
await expect(createQwenImage(payload, mockOptions)).rejects.toEqual(
expect.objectContaining({
errorType: 'ProviderBizError',
provider: 'qwen',
}),
);
});
});
});
@@ -98,8 +98,15 @@ async function createImageEdit(
const endpoint = `https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation`;
log('Creating image edit with model: %s, endpoint: %s', model, endpoint);
if (!params.imageUrl) {
throw new Error('imageUrl is required for qwen-image-edit model');
// Handle imageUrls to imageUrl conversion
let imageUrl = params.imageUrl;
if (!imageUrl && params.imageUrls && params.imageUrls.length > 0) {
imageUrl = params.imageUrls[0];
log('Converting imageUrls to imageUrl: using first image %s', imageUrl);
}
if (!imageUrl) {
throw new Error('imageUrl or imageUrls is required for qwen-image-edit model');
}
const response = await fetch(endpoint, {
@@ -107,7 +114,7 @@ async function createImageEdit(
input: {
messages: [
{
content: [{ image: params.imageUrl }, { text: params.prompt }],
content: [{ image: imageUrl }, { text: params.prompt }],
role: 'user',
},
],
@@ -152,10 +159,10 @@ async function createImageEdit(
throw new Error('No image found in response content');
}
const imageUrl = imageContent.image;
log('Image edit generated successfully: %s', imageUrl);
const resultImageUrl = imageContent.image;
log('Image edit generated successfully: %s', resultImageUrl);
return { imageUrl };
return { imageUrl: resultImageUrl };
}
/**
@@ -224,11 +231,11 @@ export async function createQwenImage(
};
}
const imageUrl = taskStatus.output.results[0].url;
log('Image generated successfully: %s', imageUrl);
const generatedImageUrl = taskStatus.output.results[0].url;
log('Image generated successfully: %s', generatedImageUrl);
return {
data: { imageUrl },
data: { imageUrl: generatedImageUrl },
status: 'success',
};
}
+2
View File
@@ -35,6 +35,7 @@ import { LobeNewAPIAI } from './providers/newapi';
import { LobeNovitaAI } from './providers/novita';
import { LobeNvidiaAI } from './providers/nvidia';
import { LobeOllamaAI } from './providers/ollama';
import { LobeOllamaCloudAI } from './providers/ollamacloud';
import { LobeOpenAI } from './providers/openai';
import { LobeOpenRouterAI } from './providers/openrouter';
import { LobePerplexityAI } from './providers/perplexity';
@@ -99,6 +100,7 @@ export const providerRuntimeMap = {
novita: LobeNovitaAI,
nvidia: LobeNvidiaAI,
ollama: LobeOllamaAI,
ollamacloud: LobeOllamaCloudAI,
openai: LobeOpenAI,
openrouter: LobeOpenRouterAI,
perplexity: LobePerplexityAI,
@@ -75,6 +75,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
novita?: OpenAICompatibleKeyVault;
nvidia?: OpenAICompatibleKeyVault;
ollama?: OpenAICompatibleKeyVault;
ollamacloud?: OpenAICompatibleKeyVault;
openai?: OpenAICompatibleKeyVault;
openrouter?: OpenAICompatibleKeyVault;
password?: string;
@@ -64,7 +64,7 @@ const GroupItem = memo<SessionGroupItem>(({ id, name }) => {
onChangeEnd={async (input) => {
if (name !== input) {
if (!input) return;
if (input.length === 0 || input.length > 20)
if (input.length === 0 || input.length > 20 || input.trim() === '')
return message.warning(t('sessionGroup.tooLong'));
await updateSessionGroupName(id, input);
@@ -35,7 +35,7 @@ const CreateGroupModal = memo<CreateGroupModalProps>(
onCancel?.(e);
}}
onOk={async (e: MouseEvent<HTMLButtonElement>) => {
if (input.length === 0 || input.length > 20)
if (input.length === 0 || input.length > 20 || input.trim() === '')
return message.warning(t('sessionGroup.tooLong'));
setLoading(true);
@@ -23,6 +23,7 @@ import {
MoonshotProviderCard,
NovitaProviderCard,
NvidiaProviderCard,
OllamaCloudProviderCard,
OpenRouterProviderCard,
PPIOProviderCard,
PerplexityProviderCard,
@@ -116,6 +117,7 @@ export const useProviderList = (): ProviderItem[] => {
InfiniAIProviderCard,
AkashChatProviderCard,
Ai302ProviderCard,
OllamaCloudProviderCard,
],
[
AzureProvider,
+3
View File
@@ -37,6 +37,7 @@ import NewAPIProvider from './newapi';
import NovitaProvider from './novita';
import NvidiaProvider from './nvidia';
import OllamaProvider from './ollama';
import OllamaCloudProvider from './ollamacloud';
import OpenAIProvider from './openai';
import OpenRouterProvider from './openrouter';
import PerplexityProvider from './perplexity';
@@ -125,6 +126,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
{ ...AzureProvider, chatModels: [] },
AzureAIProvider,
OllamaProvider,
OllamaCloudProvider,
VLLMProvider,
XinferenceProvider,
AnthropicProvider,
@@ -233,6 +235,7 @@ export { default as NewAPIProviderCard } from './newapi';
export { default as NovitaProviderCard } from './novita';
export { default as NvidiaProviderCard } from './nvidia';
export { default as OllamaProviderCard } from './ollama';
export { default as OllamaCloudProviderCard } from './ollamacloud';
export { default as OpenAIProviderCard } from './openai';
export { default as OpenRouterProviderCard } from './openrouter';
export { default as PerplexityProviderCard } from './perplexity';
+18
View File
@@ -0,0 +1,18 @@
import { ModelProviderCard } from '@/types/llm';
const OllamaCloud: ModelProviderCard = {
chatModels: [],
checkModel: 'gpt-oss:20b',
description:
'Ollama Cloud 提供官方托管的推理服务,开箱即用地访问 Ollama 模型库,并支持 OpenAI 兼容接口。',
id: 'ollamacloud',
modelsUrl: 'https://ollama.com/library',
name: 'Ollama Cloud',
settings: {
sdkType: 'openai',
showModelFetcher: true,
},
url: 'https://ollama.com/cloud',
};
export default OllamaCloud;
+4
View File
@@ -71,6 +71,8 @@ export const getLLMConfig = () => {
WENXIN_API_KEY: z.string().optional(),
ENABLED_OLLAMA: z.boolean(),
ENABLED_OLLAMA_CLOUD: z.boolean(),
OLLAMA_CLOUD_API_KEY: z.string().optional(),
ENABLED_VLLM: z.boolean(),
VLLM_API_KEY: z.string().optional(),
@@ -267,6 +269,8 @@ export const getLLMConfig = () => {
WENXIN_API_KEY: process.env.WENXIN_API_KEY,
ENABLED_OLLAMA: process.env.ENABLED_OLLAMA !== '0',
ENABLED_OLLAMA_CLOUD: !!process.env.OLLAMA_CLOUD_API_KEY,
OLLAMA_CLOUD_API_KEY: process.env.OLLAMA_CLOUD_API_KEY,
ENABLED_VLLM: !!process.env.VLLM_API_KEY,
VLLM_API_KEY: process.env.VLLM_API_KEY,
+4
View File
@@ -12,6 +12,7 @@ import { genServerLLMConfig } from './_deprecated';
import { genServerAiProvidersConfig } from './genServerAiProviderConfig';
import { parseAgentConfig } from './parseDefaultAgent';
import { parseFilesConfig } from './parseFilesConfig';
import { ollamacloud } from 'packages/model-bank/src';
export const getServerGlobalConfig = async () => {
const { ACCESS_CODES, DEFAULT_AGENT_CONFIG } = getAppConfig();
@@ -40,6 +41,9 @@ export const getServerGlobalConfig = async () => {
enabled: isDesktop ? true : undefined,
fetchOnClient: isDesktop ? false : !process.env.OLLAMA_PROXY_URL,
},
ollamacloud: {
enabledKey: 'ENABLED_OLLAMA_CLOUD',
},
qwen: {
withDeploymentName: true,
},
+8
View File
@@ -98,6 +98,14 @@ const getParamsFromPayload = (provider: string, payload: ClientSecretPayload) =>
return { apiKey };
}
case ModelProvider.OllamaCloud: {
const { OLLAMA_CLOUD_API_KEY } = llmConfig;
const apiKey = apiKeyManager.pick(payload?.apiKey || OLLAMA_CLOUD_API_KEY);
return { apiKey };
}
case ModelProvider.TencentCloud: {
const { TENCENT_CLOUD_API_KEY } = llmConfig;