@lobehub/chat 0.103.1 → 0.105.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/.github/workflows/issue-auto-comments.yml +2 -2
- package/CHANGELOG.md +50 -0
- package/README.md +6 -5
- package/README.zh-CN.md +6 -5
- package/package.json +1 -1
- package/src/app/api/config.test.ts +42 -0
- package/src/app/api/config.ts +15 -0
- package/src/app/api/openai/chat/route.ts +2 -0
- package/src/app/api/openai/stt/route.ts +2 -0
- package/src/app/api/openai/tts/route.ts +2 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +13 -5
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.test.ts +75 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.ts +2 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/listenToPlugin.test.ts +104 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings.test.ts +53 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState.test.ts +43 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/postMessage.test.ts +64 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/postMessage.ts +13 -2
- package/src/config/__tests__/server.test.ts +6 -0
- package/src/config/server.ts +7 -0
|
@@ -59,8 +59,8 @@ jobs:
|
|
|
59
59
|
comment: |
|
|
60
60
|
❤️ Great PR @${{ github.event.pull_request.user.login }} ❤️
|
|
61
61
|
|
|
62
|
-
The growth of project is inseparable from user feedback and contribution, thanks for your contribution
|
|
63
|
-
项目的成长离不开用户反馈和贡献,感谢您的贡献!
|
|
62
|
+
The growth of project is inseparable from user feedback and contribution, thanks for your contribution! If you are interesting with the lobehub developer community, please join our [discord](https://discord.com/invite/AYFPHvv2jT) and then dm @arvinxx or @canisminor1990. They will invite you to our private developer channel. We are talking about the lobe-chat development or sharing ai newsletter around the world.\
|
|
63
|
+
项目的成长离不开用户反馈和贡献,感谢您的贡献! 如果您对 LobeHub 开发者社区感兴趣,请加入我们的 [discord](https://discord.com/invite/AYFPHvv2jT),然后私信 @arvinxx 或 @canisminor1990。他们会邀请您加入我们的私密开发者频道。我们将会讨论关于 Lobe Chat 的开发,分享和讨论全球范围内的 AI 消息。
|
|
64
64
|
emoji: 'hooray'
|
|
65
65
|
pr-emoji: '+1, heart'
|
|
66
66
|
- name: Remove inactive
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.105.0](https://github.com/lobehub/lobe-chat/compare/v0.104.0...v0.105.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-11-22**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Standalone pluginn can get more arguments on init.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Standalone pluginn can get more arguments on init, closes [#498](https://github.com/lobehub/lobe-chat/issues/498) ([a7624f5](https://github.com/lobehub/lobe-chat/commit/a7624f5))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.104.0](https://github.com/lobehub/lobe-chat/compare/v0.103.1...v0.104.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-11-21**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Support using env variable to set regions for OpenAI Edge Functions..
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Support using env variable to set regions for OpenAI Edge Functions., closes [#473](https://github.com/lobehub/lobe-chat/issues/473) ([de6b79e](https://github.com/lobehub/lobe-chat/commit/de6b79e))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.103.1](https://github.com/lobehub/lobe-chat/compare/v0.103.0...v0.103.1)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2023-11-21**</sup>
|
package/README.md
CHANGED
|
@@ -390,11 +390,12 @@ $ docker run -d -p 3210:3210 \
|
|
|
390
390
|
|
|
391
391
|
This project provides some additional configuration items set with environment variables:
|
|
392
392
|
|
|
393
|
-
| Environment Variable
|
|
394
|
-
|
|
|
395
|
-
| `OPENAI_API_KEY`
|
|
396
|
-
| `OPENAI_PROXY_URL`
|
|
397
|
-
| `
|
|
393
|
+
| Environment Variable | Required | Description | Example |
|
|
394
|
+
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
395
|
+
| `OPENAI_API_KEY` | Yes | This is the API key you apply on the OpenAI account page | `sk-xxxxxx...xxxxxx` |
|
|
396
|
+
| `OPENAI_PROXY_URL` | No | If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL | `https://api.chatanywhere.cn/v1`<br/>The default value is<br/>`https://api.openai.com/v1` |
|
|
397
|
+
| `OPENAI_FUNCTION_REGIONS` | No | When you deploy Lobe-Chat using Vercel and need to specify the region for the Edge Function that handles requests to the OpenAI API, you can use this configuration. The value should be a comma-separated array of strings. | `iad1,sfo1` |
|
|
398
|
+
| `ACCESS_CODE` | No | Add a password to access this service; the password should be a 6-digit number or letter | `awCT74` or `e3@09!` |
|
|
398
399
|
|
|
399
400
|
> \[!NOTE]
|
|
400
401
|
>
|
package/README.zh-CN.md
CHANGED
|
@@ -364,11 +364,12 @@ $ docker run -d -p 3210:3210 \
|
|
|
364
364
|
|
|
365
365
|
本项目提供了一些额外的配置项,使用环境变量进行设置:
|
|
366
366
|
|
|
367
|
-
| 环境变量
|
|
368
|
-
|
|
|
369
|
-
| `OPENAI_API_KEY`
|
|
370
|
-
| `OPENAI_PROXY_URL`
|
|
371
|
-
| `
|
|
367
|
+
| 环境变量 | 类型 | 描述 | 示例 |
|
|
368
|
+
| ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
369
|
+
| `OPENAI_API_KEY` | 必选 | 这是你在 OpenAI 账户页面申请的 API 密钥 | `sk-xxxxxx...xxxxxx` |
|
|
370
|
+
| `OPENAI_PROXY_URL` | 可选 | 如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL | `https://api.chatanywhere.cn/v1`<br/>默认值:<br/>`https://api.openai.com/v1` |
|
|
371
|
+
| `OPENAI_FUNCTION_REGIONS` | 可选 | 当你使用 Vercel 部署 Lobe-Chat,而且有需求指定响应调用 OpenAI 接口的请求的 Edge Function 的 Region 时,可以使用该配置进行配置,该值的类型为逗号分隔的字符串数组 | `iad1,sfo1` |
|
|
372
|
+
| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,密码应为 6 位数字或字母 | `awCT74` 或 `e3@09!` |
|
|
372
373
|
|
|
373
374
|
> \[!NOTE]
|
|
374
375
|
>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.105.0",
|
|
4
4
|
"description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getPreferredRegion } from './config';
|
|
4
|
+
|
|
5
|
+
// Stub the global process object to safely mock environment variables
|
|
6
|
+
vi.stubGlobal('process', {
|
|
7
|
+
...process, // Preserve the original process object
|
|
8
|
+
env: { ...process.env }, // Clone the environment variables object for modification
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('getPreferredRegion', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Reset environment variables before each test case
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns default value when get config error', () => {
|
|
18
|
+
const originalProcess = global.process;
|
|
19
|
+
const originalError = console.error;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
global.process = undefined;
|
|
22
|
+
console.error = () => {};
|
|
23
|
+
|
|
24
|
+
const preferredRegion = getPreferredRegion();
|
|
25
|
+
expect(preferredRegion).toBe('auto');
|
|
26
|
+
|
|
27
|
+
global.process = originalProcess;
|
|
28
|
+
console.error = originalError;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('return default value when preferredRegion is empty', () => {
|
|
32
|
+
process.env.OPENAI_FUNCTION_REGIONS = '';
|
|
33
|
+
const preferredRegion = getPreferredRegion();
|
|
34
|
+
expect(preferredRegion).toBe('auto');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('return correct list values when preferredRegion is correctly passed', () => {
|
|
38
|
+
process.env.OPENAI_FUNCTION_REGIONS = 'ida1,sfo1';
|
|
39
|
+
const preferredRegion = getPreferredRegion();
|
|
40
|
+
expect(preferredRegion).toStrictEqual(['ida1', 'sfo1']);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getServerConfig } from '@/config/server';
|
|
2
|
+
|
|
3
|
+
export const getPreferredRegion = () => {
|
|
4
|
+
try {
|
|
5
|
+
const cfg = getServerConfig();
|
|
6
|
+
if (cfg.OPENAI_FUNCTION_REGIONS.length <= 0) {
|
|
7
|
+
return 'auto';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return cfg.OPENAI_FUNCTION_REGIONS;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error('get server config failed, error:', error);
|
|
13
|
+
return 'auto';
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
2
|
|
|
3
|
+
import { getPreferredRegion } from '../../config';
|
|
3
4
|
import { createBizOpenAI } from '../createBizOpenAI';
|
|
4
5
|
import { createChatCompletion } from './createChatCompletion';
|
|
5
6
|
|
|
6
7
|
export const runtime = 'edge';
|
|
8
|
+
export const preferredRegion = getPreferredRegion();
|
|
7
9
|
|
|
8
10
|
export const POST = async (req: Request) => {
|
|
9
11
|
const payload = (await req.json()) as OpenAIChatStreamPayload;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { OpenAISTTPayload } from '@lobehub/tts';
|
|
2
2
|
import { createOpenaiAudioTranscriptions } from '@lobehub/tts/server';
|
|
3
3
|
|
|
4
|
+
import { getPreferredRegion } from '../../config';
|
|
4
5
|
import { createBizOpenAI } from '../createBizOpenAI';
|
|
5
6
|
|
|
6
7
|
export const runtime = 'edge';
|
|
8
|
+
export const preferredRegion = getPreferredRegion();
|
|
7
9
|
|
|
8
10
|
export const POST = async (req: Request) => {
|
|
9
11
|
const formData = await req.formData();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { OpenAITTSPayload } from '@lobehub/tts';
|
|
2
2
|
import { createOpenaiAudioSpeech } from '@lobehub/tts/server';
|
|
3
3
|
|
|
4
|
+
import { getPreferredRegion } from '../../config';
|
|
4
5
|
import { createBizOpenAI } from '../createBizOpenAI';
|
|
5
6
|
|
|
6
7
|
export const runtime = 'edge';
|
|
8
|
+
export const preferredRegion = getPreferredRegion();
|
|
7
9
|
|
|
8
10
|
export const POST = async (req: Request) => {
|
|
9
11
|
const payload = (await req.json()) as OpenAITTSPayload;
|
package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx
CHANGED
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
sendPluginStateToPlugin,
|
|
24
24
|
} from '../utils/postMessage';
|
|
25
25
|
|
|
26
|
+
// just to simplify code a little, don't use this pattern everywhere
|
|
27
|
+
const getSettings = (identifier: string) =>
|
|
28
|
+
pluginSelectors.getPluginSettingsById(identifier)(usePluginStore.getState());
|
|
29
|
+
const getMessage = (id: string) => chatSelectors.getMessageById(id)(useSessionStore.getState());
|
|
30
|
+
|
|
26
31
|
interface IFrameRenderProps {
|
|
27
32
|
height?: number;
|
|
28
33
|
id: string;
|
|
@@ -39,8 +44,13 @@ const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, h
|
|
|
39
44
|
// when payload change,send content to plugin
|
|
40
45
|
useOnPluginReadyForInteraction(() => {
|
|
41
46
|
const iframeWin = iframeRef.current?.contentWindow;
|
|
47
|
+
|
|
42
48
|
if (iframeWin && payload) {
|
|
43
|
-
|
|
49
|
+
const settings = getSettings(payload.identifier);
|
|
50
|
+
const message = getMessage(id);
|
|
51
|
+
const state = message?.pluginState;
|
|
52
|
+
|
|
53
|
+
sendPayloadToPlugin(iframeWin, { payload, settings, state });
|
|
44
54
|
}
|
|
45
55
|
}, [payload]);
|
|
46
56
|
|
|
@@ -74,7 +84,7 @@ const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, h
|
|
|
74
84
|
const iframeWin = iframeRef.current?.contentWindow;
|
|
75
85
|
|
|
76
86
|
if (iframeWin) {
|
|
77
|
-
const message =
|
|
87
|
+
const message = getMessage(id);
|
|
78
88
|
if (!message) return;
|
|
79
89
|
|
|
80
90
|
sendPluginStateToPlugin(iframeWin, key, message.pluginState?.[key]);
|
|
@@ -94,9 +104,7 @@ const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, h
|
|
|
94
104
|
if (iframeWin) {
|
|
95
105
|
if (!payload?.identifier) return;
|
|
96
106
|
|
|
97
|
-
const settings =
|
|
98
|
-
usePluginStore.getState(),
|
|
99
|
-
);
|
|
107
|
+
const settings = getSettings(payload.identifier);
|
|
100
108
|
|
|
101
109
|
sendPluginSettingsToPlugin(iframeWin, settings);
|
|
102
110
|
}
|
package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.test.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { useOnPluginReadyForInteraction } from './iframeOnReady';
|
|
6
|
+
|
|
7
|
+
describe('useOnPluginReadyForInteraction', () => {
|
|
8
|
+
const mockOnReady = vi.fn();
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
mockOnReady.mockReset();
|
|
12
|
+
window.removeEventListener('message', expect.any(Function));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('sets readyForRender to true when a PluginChannel.pluginReadyForRender message is received', async () => {
|
|
16
|
+
const { result } = renderHook(() => useOnPluginReadyForInteraction(mockOnReady));
|
|
17
|
+
|
|
18
|
+
expect(result.current).toBe(false); // Initially, readyForRender should be false
|
|
19
|
+
|
|
20
|
+
const event = new MessageEvent('message', {
|
|
21
|
+
data: { type: PluginChannel.pluginReadyForRender },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
act(() => {
|
|
25
|
+
window.dispatchEvent(event);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(result.current).toBe(true); // After the event, readyForRender should be true
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('sets readyForRender to true when a PluginChannel.pluginReadyForRender message is received', async () => {
|
|
32
|
+
const { result } = renderHook(() => useOnPluginReadyForInteraction(mockOnReady));
|
|
33
|
+
|
|
34
|
+
expect(result.current).toBe(false); // Initially, readyForRender should be false
|
|
35
|
+
|
|
36
|
+
const event = new MessageEvent('message', {
|
|
37
|
+
data: { type: PluginChannel.pluginReadyForRender },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
act(() => {
|
|
41
|
+
window.dispatchEvent(event);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(result.current).toBe(true); // After the event, readyForRender should be true
|
|
45
|
+
expect(mockOnReady).toHaveBeenCalledTimes(1); // onReady should have been called once
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does not call onReady for non-pluginReadyForRender messages', async () => {
|
|
49
|
+
renderHook(() => useOnPluginReadyForInteraction(mockOnReady));
|
|
50
|
+
|
|
51
|
+
const event = new MessageEvent('message', {
|
|
52
|
+
data: { type: 'nonPluginReadyMessage' },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
window.dispatchEvent(event);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(mockOnReady).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('cleans up message event listener on unmount', () => {
|
|
63
|
+
const { unmount } = renderHook(() => useOnPluginReadyForInteraction(mockOnReady));
|
|
64
|
+
|
|
65
|
+
unmount();
|
|
66
|
+
|
|
67
|
+
const event = new MessageEvent('message', {
|
|
68
|
+
data: { type: PluginChannel.pluginReadyForRender },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
window.dispatchEvent(event);
|
|
72
|
+
|
|
73
|
+
expect(mockOnReady).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
});
|
package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/listenToPlugin.test.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
useOnPluginFetchMessage,
|
|
7
|
+
useOnPluginFetchPluginSettings,
|
|
8
|
+
useOnPluginFetchPluginState,
|
|
9
|
+
useOnPluginFillContent,
|
|
10
|
+
} from './listenToPlugin';
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('useOnPluginFetchMessage', () => {
|
|
17
|
+
it('calls onRequest when a fetchPluginMessage is received', () => {
|
|
18
|
+
const mockOnRequest = vi.fn();
|
|
19
|
+
renderHook(() => useOnPluginFetchMessage(mockOnRequest));
|
|
20
|
+
|
|
21
|
+
const testData = { key: 'testData', type: PluginChannel.fetchPluginMessage };
|
|
22
|
+
const event = new MessageEvent('message', {
|
|
23
|
+
data: testData,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
window.dispatchEvent(event);
|
|
27
|
+
|
|
28
|
+
expect(mockOnRequest).toHaveBeenCalledWith(testData);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('does not call onRequest for other message types', () => {
|
|
32
|
+
const mockOnRequest = vi.fn();
|
|
33
|
+
renderHook(() => useOnPluginFetchMessage(mockOnRequest));
|
|
34
|
+
|
|
35
|
+
const event = new MessageEvent('message', {
|
|
36
|
+
data: { type: 'otherMessageType' },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
window.dispatchEvent(event);
|
|
40
|
+
|
|
41
|
+
expect(mockOnRequest).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('useOnPluginFetchPluginState', () => {
|
|
46
|
+
it('calls onRequest with the key when a fetchPluginState message is received', () => {
|
|
47
|
+
const mockOnRequest = vi.fn();
|
|
48
|
+
renderHook(() => useOnPluginFetchPluginState(mockOnRequest));
|
|
49
|
+
|
|
50
|
+
const testKey = 'testKey';
|
|
51
|
+
const event = new MessageEvent('message', {
|
|
52
|
+
data: { type: PluginChannel.fetchPluginState, key: testKey },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
window.dispatchEvent(event);
|
|
56
|
+
|
|
57
|
+
expect(mockOnRequest).toHaveBeenCalledWith(testKey);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('useOnPluginFillContent', () => {
|
|
62
|
+
it('calls callback with content when a fillStandalonePluginContent message is received', () => {
|
|
63
|
+
const mockCallback = vi.fn();
|
|
64
|
+
renderHook(() => useOnPluginFillContent(mockCallback));
|
|
65
|
+
|
|
66
|
+
const testContent = 'testContent';
|
|
67
|
+
const event = new MessageEvent('message', {
|
|
68
|
+
data: { type: PluginChannel.fillStandalonePluginContent, content: testContent },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
window.dispatchEvent(event);
|
|
72
|
+
|
|
73
|
+
expect(mockCallback).toHaveBeenCalledWith(testContent);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('calls callback with JSON stringified content if content is not a string', () => {
|
|
77
|
+
const mockCallback = vi.fn();
|
|
78
|
+
renderHook(() => useOnPluginFillContent(mockCallback));
|
|
79
|
+
|
|
80
|
+
const testContent = { some: 'data' };
|
|
81
|
+
const event = new MessageEvent('message', {
|
|
82
|
+
data: { type: PluginChannel.fillStandalonePluginContent, content: testContent },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
window.dispatchEvent(event);
|
|
86
|
+
|
|
87
|
+
expect(mockCallback).toHaveBeenCalledWith(JSON.stringify(testContent));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('useOnPluginFetchPluginSettings', () => {
|
|
92
|
+
it('calls onRequest when a fetchPluginSettings message is received', () => {
|
|
93
|
+
const mockOnRequest = vi.fn();
|
|
94
|
+
renderHook(() => useOnPluginFetchPluginSettings(mockOnRequest));
|
|
95
|
+
|
|
96
|
+
const event = new MessageEvent('message', {
|
|
97
|
+
data: { type: PluginChannel.fetchPluginSettings },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
window.dispatchEvent(event);
|
|
101
|
+
|
|
102
|
+
expect(mockOnRequest).toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
});
|
package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings.test.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { useOnPluginSettingsUpdate } from './pluginSettings';
|
|
6
|
+
|
|
7
|
+
describe('useOnPluginSettingsUpdate', () => {
|
|
8
|
+
const mockCallback = vi.fn();
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
mockCallback.mockReset();
|
|
12
|
+
window.removeEventListener('message', expect.any(Function));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('calls the callback when a PluginChannel updatePluginSettings message is received', () => {
|
|
16
|
+
renderHook(() => useOnPluginSettingsUpdate(mockCallback));
|
|
17
|
+
|
|
18
|
+
const testSettings = { theme: 'dark', notifications: true };
|
|
19
|
+
const event = new MessageEvent('message', {
|
|
20
|
+
data: { type: PluginChannel.updatePluginSettings, value: testSettings },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
window.dispatchEvent(event);
|
|
24
|
+
|
|
25
|
+
expect(mockCallback).toHaveBeenCalledWith(testSettings);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('does not call the callback for non-updatePluginSettings messages', () => {
|
|
29
|
+
renderHook(() => useOnPluginSettingsUpdate(mockCallback));
|
|
30
|
+
|
|
31
|
+
const event = new MessageEvent('message', {
|
|
32
|
+
data: { type: 'nonPluginSettingsUpdate', value: { irrelevant: true } },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
window.dispatchEvent(event);
|
|
36
|
+
|
|
37
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('cleans up message event listener on unmount', () => {
|
|
41
|
+
const { unmount } = renderHook(() => useOnPluginSettingsUpdate(mockCallback));
|
|
42
|
+
|
|
43
|
+
unmount();
|
|
44
|
+
|
|
45
|
+
const event = new MessageEvent('message', {
|
|
46
|
+
data: { type: PluginChannel.updatePluginSettings, value: {} },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
window.dispatchEvent(event);
|
|
50
|
+
|
|
51
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { useOnPluginStateUpdate } from './pluginState';
|
|
6
|
+
|
|
7
|
+
describe('useOnPluginStateUpdate', () => {
|
|
8
|
+
// Mock for the callback function to be used in tests
|
|
9
|
+
const mockCallback = vi.fn();
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Reset the mock callback after each test
|
|
13
|
+
mockCallback.mockReset();
|
|
14
|
+
// Ensure no event listeners are left hanging after each test
|
|
15
|
+
window.removeEventListener('message', expect.any(Function));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('calls the callback when a PluginChannel update message is received', () => {
|
|
19
|
+
renderHook(() => useOnPluginStateUpdate(mockCallback));
|
|
20
|
+
|
|
21
|
+
const testKey = 'testKey';
|
|
22
|
+
const testValue = 'testValue';
|
|
23
|
+
const event = new MessageEvent('message', {
|
|
24
|
+
data: { type: PluginChannel.updatePluginState, key: testKey, value: testValue },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
window.dispatchEvent(event);
|
|
28
|
+
|
|
29
|
+
expect(mockCallback).toHaveBeenCalledWith(testKey, testValue);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not call the callback for non-PluginChannel messages', () => {
|
|
33
|
+
renderHook(() => useOnPluginStateUpdate(mockCallback));
|
|
34
|
+
|
|
35
|
+
const event = new MessageEvent('message', {
|
|
36
|
+
data: { type: 'nonPluginMessage', key: 'key', value: 'value' },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
window.dispatchEvent(event);
|
|
40
|
+
|
|
41
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
sendMessageContentToPlugin,
|
|
6
|
+
sendPayloadToPlugin,
|
|
7
|
+
sendPluginSettingsToPlugin,
|
|
8
|
+
sendPluginStateToPlugin,
|
|
9
|
+
} from './postMessage';
|
|
10
|
+
|
|
11
|
+
// Mock window object with a postMessage spy
|
|
12
|
+
const mockWindow = {
|
|
13
|
+
postMessage: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('plugin communication functions', () => {
|
|
17
|
+
it('sendMessageContentToPlugin should call window.postMessage with correct arguments', () => {
|
|
18
|
+
const props = { some: 'data' };
|
|
19
|
+
sendMessageContentToPlugin(mockWindow as unknown as Window, props);
|
|
20
|
+
expect(mockWindow.postMessage).toHaveBeenCalledWith(
|
|
21
|
+
{ props, type: PluginChannel.renderPlugin },
|
|
22
|
+
'*',
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('sendPayloadToPlugin should call window.postMessage with correct arguments', () => {
|
|
27
|
+
const props = { payload: 'payload', settings: 'settings', state: 'state' };
|
|
28
|
+
sendPayloadToPlugin(mockWindow as unknown as Window, props);
|
|
29
|
+
expect(mockWindow.postMessage).toHaveBeenCalledWith(
|
|
30
|
+
{
|
|
31
|
+
type: PluginChannel.initStandalonePlugin,
|
|
32
|
+
payload: props.payload,
|
|
33
|
+
settings: props.settings,
|
|
34
|
+
state: props.state,
|
|
35
|
+
props: props.payload, // Note: This is due to the TODO in your code
|
|
36
|
+
},
|
|
37
|
+
'*',
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('sendPluginStateToPlugin should call window.postMessage with correct arguments', () => {
|
|
42
|
+
const key = 'key';
|
|
43
|
+
const value = 'value';
|
|
44
|
+
sendPluginStateToPlugin(mockWindow as unknown as Window, key, value);
|
|
45
|
+
expect(mockWindow.postMessage).toHaveBeenCalledWith(
|
|
46
|
+
{ key, type: PluginChannel.renderPluginState, value },
|
|
47
|
+
'*',
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('sendPluginSettingsToPlugin should call window.postMessage with correct arguments', () => {
|
|
52
|
+
const settings = { setting1: 'value1' };
|
|
53
|
+
sendPluginSettingsToPlugin(mockWindow as unknown as Window, settings);
|
|
54
|
+
expect(mockWindow.postMessage).toHaveBeenCalledWith(
|
|
55
|
+
{ type: PluginChannel.renderPluginSettings, value: settings },
|
|
56
|
+
'*',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Reset the mock after each test
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
vi.resetAllMocks();
|
|
64
|
+
});
|
|
@@ -4,8 +4,19 @@ export const sendMessageContentToPlugin = (window: Window, props: any) => {
|
|
|
4
4
|
window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
export const sendPayloadToPlugin = (
|
|
8
|
-
window
|
|
7
|
+
export const sendPayloadToPlugin = (
|
|
8
|
+
window: Window,
|
|
9
|
+
props: { payload: any; settings: any; state?: any },
|
|
10
|
+
) => {
|
|
11
|
+
window.postMessage(
|
|
12
|
+
{
|
|
13
|
+
type: PluginChannel.initStandalonePlugin,
|
|
14
|
+
...props,
|
|
15
|
+
// TODO: props need to deprecated
|
|
16
|
+
props: props.payload,
|
|
17
|
+
},
|
|
18
|
+
'*',
|
|
19
|
+
);
|
|
9
20
|
};
|
|
10
21
|
|
|
11
22
|
export const sendPluginStateToPlugin = (window: Window, key: string, value: any) => {
|
|
@@ -39,6 +39,12 @@ describe('getServerConfig', () => {
|
|
|
39
39
|
expect(config.USE_AZURE_OPENAI).toBe(false);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it('correctly handles values for OPENAI_FUNCTION_REGIONS', () => {
|
|
43
|
+
process.env.OPENAI_FUNCTION_REGIONS = 'iad1,sfo1';
|
|
44
|
+
const config = getServerConfig();
|
|
45
|
+
expect(config.OPENAI_FUNCTION_REGIONS).toStrictEqual(['iad1', 'sfo1']);
|
|
46
|
+
});
|
|
47
|
+
|
|
42
48
|
it('returns default IMGUR_CLIENT_ID when no environment variable is set', () => {
|
|
43
49
|
const config = getServerConfig();
|
|
44
50
|
expect(config.IMGUR_CLIENT_ID).toBe('e415f320d6e24f9');
|
package/src/config/server.ts
CHANGED
|
@@ -27,11 +27,18 @@ export const getServerConfig = () => {
|
|
|
27
27
|
throw new Error('[Server Config] you are importing a nodejs-only module outside of nodejs');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// region format: iad1,sfo1
|
|
31
|
+
let regions: string[] = [];
|
|
32
|
+
if (process.env.OPENAI_FUNCTION_REGIONS) {
|
|
33
|
+
regions = process.env.OPENAI_FUNCTION_REGIONS.split(',');
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
return {
|
|
31
37
|
ACCESS_CODE: process.env.ACCESS_CODE,
|
|
32
38
|
|
|
33
39
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
34
40
|
OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
|
|
41
|
+
OPENAI_FUNCTION_REGIONS: regions,
|
|
35
42
|
|
|
36
43
|
AZURE_API_KEY: process.env.AZURE_API_KEY,
|
|
37
44
|
AZURE_API_VERSION: process.env.AZURE_API_VERSION,
|