Skip to content

Commit

Permalink
✨ feat: support plugin settings env (lobehub#821)
Browse files Browse the repository at this point in the history
* ✨ feat: support plugin settings env

* 📝 docs: add plugin settings document
  • Loading branch information
arvinxx authored Dec 27, 2023
1 parent ad55f1c commit efd9dc9
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 6 deletions.
13 changes: 10 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 20 additions & 1 deletion docs/Deployment/Environment-Variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions docs/Deployment/Environment-Variable.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
10 changes: 8 additions & 2 deletions src/app/api/plugin/gateway/route.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 105 additions & 0 deletions src/app/api/plugin/gateway/settings.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 29 additions & 0 deletions src/app/api/plugin/gateway/settings.ts
Original file line number Diff line number Diff line change
@@ -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());
};
4 changes: 4 additions & 0 deletions src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ declare global {
IMGUR_CLIENT_ID?: string;

AGENTS_INDEX_URL?: string;

PLUGINS_INDEX_URL?: string;
PLUGIN_SETTINGS?: string;
}
}
}
Expand Down Expand Up @@ -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,
};
};

0 comments on commit efd9dc9

Please sign in to comment.