mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
✨ feat: support plugin settings env (#821)
* ✨ feat: support plugin settings env * 📝 docs: add plugin settings document
This commit is contained in:
parent
ad55f1c4a8
commit
efd9dc9df2
7 changed files with 197 additions and 6 deletions
13
.env.example
13
.env.example
|
|
@ -37,9 +37,16 @@ OPENAI_API_KEY=sk-xxxxxxxxx
|
|||
############ Market Service ############
|
||||
########################################
|
||||
|
||||
# The LobeChat plugins market index url
|
||||
# PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com
|
||||
|
||||
# The LobeChat agents market index url
|
||||
# AGENTS_INDEX_URL=https://chat-agents.lobehub.com
|
||||
|
||||
########################################
|
||||
############ Plugin Service ############
|
||||
########################################
|
||||
|
||||
# The LobeChat plugins store index url
|
||||
# PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com
|
||||
|
||||
# set the plugin settings
|
||||
# the format is `plugin-identifier:key1=value1;key2=value2`, multiple settings fields are separated by semicolons `;`, multiple plugin settings are separated by commas `,`.
|
||||
# PLUGIN_SETTINGS=search-engine:SERPAPI_API_KEY=xxxxx
|
||||
|
|
|
|||
|
|
@ -93,7 +93,26 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
|
|||
- Description: The index address of the LobeChat plugin market. If you have deployed the plugin market service yourself, you can use this variable to override the default plugin market address
|
||||
- Default: `https://chat-plugins.lobehub.com`
|
||||
|
||||
<br/>
|
||||
### `PLUGINS_SETTINGS`
|
||||
|
||||
- Type: Optional
|
||||
- Description: Used to set the plugin settings, the format is `plugin-identifier:key1=value1;key2=value2`, multiple settings fields are separated by semicolons `;`, multiple plugin settings are separated by commas `,`.
|
||||
- Default: `-`
|
||||
- Example:`search-engine:SERPAPI_API_KEY=xxxxx,plugin-2:key1=value1;key2=value2`
|
||||
|
||||
The above example adds `search-engine` plugin settings, and sets the `SERPAPI_API_KEY` of the `search-engine` plugin to `xxxxx`, and sets the `key1` of the `plugin-2` plugin to `value1`, and `key2` to `value2`. The generated plugin settings configuration is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin-2": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
},
|
||||
"search-engine": {
|
||||
"SERPAPI_API_KEY": "xxxxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Service
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,27 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
|
|||
- 描述:LobeChat 插件市场的索引地址,如果你自行部署了插件市场的服务,可以使用该变量来覆盖默认的插件市场地址
|
||||
- 默认值:`https://chat-plugins.lobehub.com`
|
||||
|
||||
### `PLUGINS_SETTINGS`
|
||||
|
||||
- 类型:可选
|
||||
- 描述:用于配置插件的设置,使用 `插件名:设置字段=设置值` 的格式来配置插件的设置,多个设置字段用英文分号 `;` 隔开,多个插件设置使用英文逗号`,`隔开。
|
||||
- 默认值:`-`
|
||||
- 示例:`search-engine:SERPAPI_API_KEY=xxxxx,plugin-2:key1=value1;key2=value2`
|
||||
|
||||
上述示例表示设置 `search-engine` 插件的 `SERPAPI_API_KEY` 为 `xxxxx`,设置 `plugin-2` 的 `key1` 为 `value1`,`key2` 为 `value2`。生成的插件设置配置如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin-2": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
},
|
||||
"search-engine": {
|
||||
"SERPAPI_API_KEY": "xxxxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 角色服务
|
||||
|
||||
### `AGENTS_INDEX_URL`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
|
||||
|
||||
import { parserPluginSettings } from '@/app/api/plugin/gateway/settings';
|
||||
import { getServerConfig } from '@/config/server';
|
||||
|
||||
const pluginsIndexUrl = getServerConfig().PLUGINS_INDEX_URL;
|
||||
const { PLUGINS_INDEX_URL: pluginsIndexUrl, PLUGIN_SETTINGS } = getServerConfig();
|
||||
|
||||
export const POST = createGatewayOnEdgeRuntime({ pluginsIndexUrl });
|
||||
const defaultPluginSettings = parserPluginSettings(PLUGIN_SETTINGS);
|
||||
|
||||
export const POST = createGatewayOnEdgeRuntime({
|
||||
defaultPluginSettings,
|
||||
pluginsIndexUrl,
|
||||
});
|
||||
|
|
|
|||
105
src/app/api/plugin/gateway/settings.test.ts
Normal file
105
src/app/api/plugin/gateway/settings.test.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { parserPluginSettings } from './settings';
|
||||
|
||||
describe('parserPluginSettings', () => {
|
||||
it('should return an empty object when input is undefined', () => {
|
||||
expect(parserPluginSettings()).toEqual({});
|
||||
});
|
||||
|
||||
it('should return an empty object when input is an empty string', () => {
|
||||
expect(parserPluginSettings('')).toEqual({});
|
||||
});
|
||||
|
||||
it('should parse plugin settings from a well-formed string', () => {
|
||||
const input = 'plugin1:key1=value1;key2=value2,plugin2:key3=value3';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2' },
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle strings with Chinese commas', () => {
|
||||
const input = 'plugin1:key1=value1;key2=value2,plugin2:key3=value3';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2' },
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should ignore empty segments', () => {
|
||||
const input = 'plugin1:key1=value1;key2=value2,,,plugin2:key3=value3';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2' },
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should merge settings for the same pluginId', () => {
|
||||
const input = 'plugin1:key1=value1;key2=value2,plugin1:key3=value3;key4=value4';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2', key3: 'value3', key4: 'value4' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should override previous values if the same key appears again for the same pluginId', () => {
|
||||
const input = 'plugin1:key1=value1;key2=value2,plugin1:key2=newValue2;key3=value3';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'newValue2', key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
describe('error senses', () => {
|
||||
it('should ignore settings with incorrect key-value format', () => {
|
||||
const input = 'plugin1:key1=value1;incorrectFormat,plugin2:key2=value2';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1' },
|
||||
plugin2: { key2: 'value2' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle extra separators gracefully', () => {
|
||||
const input = 'plugin1:key1=value1==value1.1;key2=value2;,plugin2:key3=value3';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2' },
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should ignore settings with empty keys or values', () => {
|
||||
const input = 'plugin1:=value1;key2=,plugin2:key3=value3';
|
||||
const expected = {
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should ignore leading and trailing whitespace in keys and values', () => {
|
||||
const input = ' plugin1 : key1 = value1 ; key2 = value2 , plugin2 : key3=value3 ';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1', key2: 'value2' },
|
||||
plugin2: { key3: 'value3' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle special characters in keys and values', () => {
|
||||
const input = 'plugin1:key1=value1+special;key2=value2#special,plugin2:key3=value3/special';
|
||||
const expected = {
|
||||
plugin1: { key1: 'value1+special', key2: 'value2#special' },
|
||||
plugin2: { key3: 'value3/special' },
|
||||
};
|
||||
expect(parserPluginSettings(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
// ... (additional test cases as needed)
|
||||
});
|
||||
});
|
||||
29
src/app/api/plugin/gateway/settings.ts
Normal file
29
src/app/api/plugin/gateway/settings.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export const parserPluginSettings = (
|
||||
settingsStr?: string,
|
||||
): Record<string, Record<string, string>> => {
|
||||
if (!settingsStr) return {};
|
||||
|
||||
const settings = new Map();
|
||||
|
||||
const array = settingsStr.split(/[,,]/).filter(Boolean);
|
||||
|
||||
for (const item of array) {
|
||||
const [id, pluginSettingsStr] = item.split(':');
|
||||
if (!id) continue;
|
||||
|
||||
const pluginSettingItems = pluginSettingsStr.split(';');
|
||||
|
||||
const cleanId = id.trim();
|
||||
|
||||
for (const item of pluginSettingItems) {
|
||||
const [key, value] = item.split('=');
|
||||
if (!key || !value) continue;
|
||||
const cleanKey = key.trim();
|
||||
const cleanValue = value.trim();
|
||||
|
||||
settings.set(cleanId, { ...settings.get(cleanId), [cleanKey]: cleanValue });
|
||||
}
|
||||
}
|
||||
|
||||
return Object.fromEntries(settings.entries());
|
||||
};
|
||||
|
|
@ -17,7 +17,9 @@ declare global {
|
|||
IMGUR_CLIENT_ID?: string;
|
||||
|
||||
AGENTS_INDEX_URL?: string;
|
||||
|
||||
PLUGINS_INDEX_URL?: string;
|
||||
PLUGIN_SETTINGS?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,5 +64,7 @@ export const getServerConfig = () => {
|
|||
PLUGINS_INDEX_URL: !!process.env.PLUGINS_INDEX_URL
|
||||
? process.env.PLUGINS_INDEX_URL
|
||||
: 'https://chat-plugins.lobehub.com',
|
||||
|
||||
PLUGIN_SETTINGS: process.env.PLUGIN_SETTINGS,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue