@lobehub/chat 0.116.4 → 0.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -37,9 +37,16 @@ OPENAI_API_KEY=sk-xxxxxxxxx
37
37
  ############ Market Service ############
38
38
  ########################################
39
39
 
40
- # The LobeChat plugins market index url
41
- # PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com
42
-
43
40
  # The LobeChat agents market index url
44
41
  # AGENTS_INDEX_URL=https://chat-agents.lobehub.com
45
42
 
43
+ ########################################
44
+ ############ Plugin Service ############
45
+ ########################################
46
+
47
+ # The LobeChat plugins store index url
48
+ # PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com
49
+
50
+ # set the plugin settings
51
+ # the format is `plugin-identifier:key1=value1;key2=value2`, multiple settings fields are separated by semicolons `;`, multiple plugin settings are separated by commas `,`.
52
+ # PLUGIN_SETTINGS=search-engine:SERPAPI_API_KEY=xxxxx
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.117.0](https://github.com/lobehub/lobe-chat/compare/v0.116.5...v0.117.0)
6
+
7
+ <sup>Released on **2023-12-27**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Support plugin settings env.
12
+
13
+ #### 🐛 Bug Fixes
14
+
15
+ - **misc**: Improve topic search experience.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's improved
23
+
24
+ - **misc**: Support plugin settings env, closes [#821](https://github.com/lobehub/lobe-chat/issues/821) ([efd9dc9](https://github.com/lobehub/lobe-chat/commit/efd9dc9))
25
+
26
+ #### What's fixed
27
+
28
+ - **misc**: Improve topic search experience, closes [#828](https://github.com/lobehub/lobe-chat/issues/828) ([ad55f1c](https://github.com/lobehub/lobe-chat/commit/ad55f1c))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ### [Version 0.116.5](https://github.com/lobehub/lobe-chat/compare/v0.116.4...v0.116.5)
39
+
40
+ <sup>Released on **2023-12-27**</sup>
41
+
42
+ #### 🐛 Bug Fixes
43
+
44
+ - **misc**: Fix input box losing focus after sending a message on the desktop.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### What's fixed
52
+
53
+ - **misc**: Fix input box losing focus after sending a message on the desktop, closes [#830](https://github.com/lobehub/lobe-chat/issues/830) ([d491af9](https://github.com/lobehub/lobe-chat/commit/d491af9))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ### [Version 0.116.4](https://github.com/lobehub/lobe-chat/compare/v0.116.3...v0.116.4)
6
64
 
7
65
  <sup>Released on **2023-12-26**</sup>
@@ -93,7 +93,26 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
93
93
  - 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
94
94
  - Default: `https://chat-plugins.lobehub.com`
95
95
 
96
- <br/>
96
+ ### `PLUGINS_SETTINGS`
97
+
98
+ - Type: Optional
99
+ - 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 `,`.
100
+ - Default: `-`
101
+ - Example:`search-engine:SERPAPI_API_KEY=xxxxx,plugin-2:key1=value1;key2=value2`
102
+
103
+ 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:
104
+
105
+ ```json
106
+ {
107
+ "plugin-2": {
108
+ "key1": "value1",
109
+ "key2": "value2"
110
+ },
111
+ "search-engine": {
112
+ "SERPAPI_API_KEY": "xxxxx"
113
+ }
114
+ }
115
+ ```
97
116
 
98
117
  ## Agent Service
99
118
 
@@ -91,6 +91,27 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
91
91
  - 描述:LobeChat 插件市场的索引地址,如果你自行部署了插件市场的服务,可以使用该变量来覆盖默认的插件市场地址
92
92
  - 默认值:`https://chat-plugins.lobehub.com`
93
93
 
94
+ ### `PLUGINS_SETTINGS`
95
+
96
+ - 类型:可选
97
+ - 描述:用于配置插件的设置,使用 `插件名:设置字段=设置值` 的格式来配置插件的设置,多个设置字段用英文分号 `;` 隔开,多个插件设置使用英文逗号`,`隔开。
98
+ - 默认值:`-`
99
+ - 示例:`search-engine:SERPAPI_API_KEY=xxxxx,plugin-2:key1=value1;key2=value2`
100
+
101
+ 上述示例表示设置 `search-engine` 插件的 `SERPAPI_API_KEY` 为 `xxxxx`,设置 `plugin-2` 的 `key1` 为 `value1`,`key2` 为 `value2`。生成的插件设置配置如下:
102
+
103
+ ```json
104
+ {
105
+ "plugin-2": {
106
+ "key1": "value1",
107
+ "key2": "value2"
108
+ },
109
+ "search-engine": {
110
+ "SERPAPI_API_KEY": "xxxxx"
111
+ }
112
+ }
113
+ ```
114
+
94
115
  ## 角色服务
95
116
 
96
117
  ### `AGENTS_INDEX_URL`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.116.4",
3
+ "version": "0.117.0",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,7 +1,13 @@
1
1
  import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
2
2
 
3
+ import { parserPluginSettings } from '@/app/api/plugin/gateway/settings';
3
4
  import { getServerConfig } from '@/config/server';
4
5
 
5
- const pluginsIndexUrl = getServerConfig().PLUGINS_INDEX_URL;
6
+ const { PLUGINS_INDEX_URL: pluginsIndexUrl, PLUGIN_SETTINGS } = getServerConfig();
6
7
 
7
- export const POST = createGatewayOnEdgeRuntime({ pluginsIndexUrl });
8
+ const defaultPluginSettings = parserPluginSettings(PLUGIN_SETTINGS);
9
+
10
+ export const POST = createGatewayOnEdgeRuntime({
11
+ defaultPluginSettings,
12
+ pluginsIndexUrl,
13
+ });
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { parserPluginSettings } from './settings';
4
+
5
+ describe('parserPluginSettings', () => {
6
+ it('should return an empty object when input is undefined', () => {
7
+ expect(parserPluginSettings()).toEqual({});
8
+ });
9
+
10
+ it('should return an empty object when input is an empty string', () => {
11
+ expect(parserPluginSettings('')).toEqual({});
12
+ });
13
+
14
+ it('should parse plugin settings from a well-formed string', () => {
15
+ const input = 'plugin1:key1=value1;key2=value2,plugin2:key3=value3';
16
+ const expected = {
17
+ plugin1: { key1: 'value1', key2: 'value2' },
18
+ plugin2: { key3: 'value3' },
19
+ };
20
+ expect(parserPluginSettings(input)).toEqual(expected);
21
+ });
22
+
23
+ it('should handle strings with Chinese commas', () => {
24
+ const input = 'plugin1:key1=value1;key2=value2,plugin2:key3=value3';
25
+ const expected = {
26
+ plugin1: { key1: 'value1', key2: 'value2' },
27
+ plugin2: { key3: 'value3' },
28
+ };
29
+ expect(parserPluginSettings(input)).toEqual(expected);
30
+ });
31
+
32
+ it('should ignore empty segments', () => {
33
+ const input = 'plugin1:key1=value1;key2=value2,,,plugin2:key3=value3';
34
+ const expected = {
35
+ plugin1: { key1: 'value1', key2: 'value2' },
36
+ plugin2: { key3: 'value3' },
37
+ };
38
+ expect(parserPluginSettings(input)).toEqual(expected);
39
+ });
40
+
41
+ it('should merge settings for the same pluginId', () => {
42
+ const input = 'plugin1:key1=value1;key2=value2,plugin1:key3=value3;key4=value4';
43
+ const expected = {
44
+ plugin1: { key1: 'value1', key2: 'value2', key3: 'value3', key4: 'value4' },
45
+ };
46
+ expect(parserPluginSettings(input)).toEqual(expected);
47
+ });
48
+
49
+ it('should override previous values if the same key appears again for the same pluginId', () => {
50
+ const input = 'plugin1:key1=value1;key2=value2,plugin1:key2=newValue2;key3=value3';
51
+ const expected = {
52
+ plugin1: { key1: 'value1', key2: 'newValue2', key3: 'value3' },
53
+ };
54
+ expect(parserPluginSettings(input)).toEqual(expected);
55
+ });
56
+
57
+ describe('error senses', () => {
58
+ it('should ignore settings with incorrect key-value format', () => {
59
+ const input = 'plugin1:key1=value1;incorrectFormat,plugin2:key2=value2';
60
+ const expected = {
61
+ plugin1: { key1: 'value1' },
62
+ plugin2: { key2: 'value2' },
63
+ };
64
+ expect(parserPluginSettings(input)).toEqual(expected);
65
+ });
66
+
67
+ it('should handle extra separators gracefully', () => {
68
+ const input = 'plugin1:key1=value1==value1.1;key2=value2;,plugin2:key3=value3';
69
+ const expected = {
70
+ plugin1: { key1: 'value1', key2: 'value2' },
71
+ plugin2: { key3: 'value3' },
72
+ };
73
+ expect(parserPluginSettings(input)).toEqual(expected);
74
+ });
75
+
76
+ it('should ignore settings with empty keys or values', () => {
77
+ const input = 'plugin1:=value1;key2=,plugin2:key3=value3';
78
+ const expected = {
79
+ plugin2: { key3: 'value3' },
80
+ };
81
+
82
+ expect(parserPluginSettings(input)).toEqual(expected);
83
+ });
84
+
85
+ it('should ignore leading and trailing whitespace in keys and values', () => {
86
+ const input = ' plugin1 : key1 = value1 ; key2 = value2 , plugin2 : key3=value3 ';
87
+ const expected = {
88
+ plugin1: { key1: 'value1', key2: 'value2' },
89
+ plugin2: { key3: 'value3' },
90
+ };
91
+ expect(parserPluginSettings(input)).toEqual(expected);
92
+ });
93
+
94
+ it('should handle special characters in keys and values', () => {
95
+ const input = 'plugin1:key1=value1+special;key2=value2#special,plugin2:key3=value3/special';
96
+ const expected = {
97
+ plugin1: { key1: 'value1+special', key2: 'value2#special' },
98
+ plugin2: { key3: 'value3/special' },
99
+ };
100
+ expect(parserPluginSettings(input)).toEqual(expected);
101
+ });
102
+
103
+ // ... (additional test cases as needed)
104
+ });
105
+ });
@@ -0,0 +1,29 @@
1
+ export const parserPluginSettings = (
2
+ settingsStr?: string,
3
+ ): Record<string, Record<string, string>> => {
4
+ if (!settingsStr) return {};
5
+
6
+ const settings = new Map();
7
+
8
+ const array = settingsStr.split(/[,,]/).filter(Boolean);
9
+
10
+ for (const item of array) {
11
+ const [id, pluginSettingsStr] = item.split(':');
12
+ if (!id) continue;
13
+
14
+ const pluginSettingItems = pluginSettingsStr.split(';');
15
+
16
+ const cleanId = id.trim();
17
+
18
+ for (const item of pluginSettingItems) {
19
+ const [key, value] = item.split('=');
20
+ if (!key || !value) continue;
21
+ const cleanKey = key.trim();
22
+ const cleanValue = value.trim();
23
+
24
+ settings.set(cleanId, { ...settings.get(cleanId), [cleanKey]: cleanValue });
25
+ }
26
+ }
27
+
28
+ return Object.fromEntries(settings.entries());
29
+ };
@@ -8,7 +8,7 @@ import { useChatStore } from '@/store/chat';
8
8
  const TopicSearchBar = memo<{ onClear?: () => void }>(({ onClear }) => {
9
9
  const { t } = useTranslation('chat');
10
10
 
11
- const [keywords, setKeywords] = useState<string | undefined>(undefined);
11
+ const [keywords, setKeywords] = useState('');
12
12
  const { mobile } = useResponsive();
13
13
 
14
14
  const useSearchTopics = useChatStore((s) => s.useSearchTopics);
@@ -17,7 +17,9 @@ declare global {
17
17
  IMGUR_CLIENT_ID?: string;
18
18
 
19
19
  AGENTS_INDEX_URL?: string;
20
+
20
21
  PLUGINS_INDEX_URL?: string;
22
+ PLUGIN_SETTINGS?: string;
21
23
  }
22
24
  }
23
25
  }
@@ -62,5 +64,7 @@ export const getServerConfig = () => {
62
64
  PLUGINS_INDEX_URL: !!process.env.PLUGINS_INDEX_URL
63
65
  ? process.env.PLUGINS_INDEX_URL
64
66
  : 'https://chat-plugins.lobehub.com',
67
+
68
+ PLUGIN_SETTINGS: process.env.PLUGIN_SETTINGS,
65
69
  };
66
70
  };
@@ -1,6 +1,7 @@
1
1
  import { TextAreaRef } from 'antd/es/input/TextArea';
2
2
  import { useCallback, useRef, useState } from 'react';
3
3
 
4
+ import { useIsMobile } from '@/hooks/useIsMobile';
4
5
  import { useChatStore } from '@/store/chat';
5
6
  import { useGlobalStore } from '@/store/global';
6
7
  import { useSessionStore } from '@/store/session';
@@ -23,12 +24,15 @@ export const useChatInput = () => {
23
24
  s.updateInputMessage,
24
25
  s.stopGenerateMessage,
25
26
  ]);
27
+ const mobile = useIsMobile();
26
28
 
27
29
  const handleSend = useCallback(() => {
28
30
  setExpand(false);
29
- ref?.current?.blur();
31
+ if (mobile) {
32
+ ref?.current?.blur();
33
+ }
30
34
  onSend();
31
- }, [onSend]);
35
+ }, [onSend, mobile]);
32
36
 
33
37
  return {
34
38
  canUpload,