@lobehub/chat 0.92.0 → 0.93.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/CHANGELOG.md +33 -0
- package/package.json +1 -1
- package/src/app/chat/features/Conversation/ChatList/Actions/customAction.ts +6 -4
- package/src/app/chat/features/Conversation/ChatList/index.tsx +21 -14
- package/src/chains/langDetect.ts +31 -0
- package/src/chains/pickEmoji.ts +34 -0
- package/src/chains/{agent.ts → summaryAgentName.ts} +5 -46
- package/src/chains/summaryDescription.ts +15 -0
- package/src/chains/summaryTitle.ts +32 -0
- package/src/chains/translate.ts +17 -0
- package/src/features/AgentSetting/store/action.ts +6 -4
- package/src/store/session/slices/chat/actions/topic.ts +1 -1
- package/src/store/session/slices/chat/actions/translate.ts +2 -1
- package/src/chains/chat.ts +0 -77
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.93.0](https://github.com/lobehub/lobe-chat/compare/v0.92.0...v0.93.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-10-18**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor chain.
|
|
12
|
+
|
|
13
|
+
#### ✨ Features
|
|
14
|
+
|
|
15
|
+
- **misc**: Support multi-language translate.
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
21
|
+
|
|
22
|
+
#### Code refactoring
|
|
23
|
+
|
|
24
|
+
- **misc**: Refactor chain ([49c4863](https://github.com/lobehub/lobe-chat/commit/49c4863))
|
|
25
|
+
|
|
26
|
+
#### What's improved
|
|
27
|
+
|
|
28
|
+
- **misc**: Support multi-language translate ([548bc5d](https://github.com/lobehub/lobe-chat/commit/548bc5d))
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<div align="right">
|
|
33
|
+
|
|
34
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
5
38
|
## [Version 0.92.0](https://github.com/lobehub/lobe-chat/compare/v0.91.0...v0.92.0)
|
|
6
39
|
|
|
7
40
|
<sup>Released on **2023-10-18**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.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",
|
|
@@ -2,14 +2,16 @@ import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
|
|
|
2
2
|
import { LanguagesIcon } from 'lucide-react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
|
|
5
|
+
import { localeOptions } from '@/locales/options';
|
|
6
|
+
|
|
5
7
|
export const useCustomActions = () => {
|
|
6
8
|
const { t } = useTranslation('chat');
|
|
7
9
|
|
|
8
10
|
const translate = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
children: localeOptions.map((i) => ({
|
|
12
|
+
key: i.value,
|
|
13
|
+
label: t(`lang.${i.value}`, { ns: 'common' }),
|
|
14
|
+
})),
|
|
13
15
|
icon: LanguagesIcon,
|
|
14
16
|
key: 'translate',
|
|
15
17
|
label: t('translateTo'),
|
|
@@ -5,7 +5,6 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { PREFIX_KEY, REGENERATE_KEY } from '@/const/hotkeys';
|
|
8
|
-
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
9
8
|
import { useSessionChatInit, useSessionStore } from '@/store/session';
|
|
10
9
|
import { agentSelectors, chatSelectors } from '@/store/session/selectors';
|
|
11
10
|
|
|
@@ -43,14 +42,11 @@ const List = memo(() => {
|
|
|
43
42
|
];
|
|
44
43
|
});
|
|
45
44
|
|
|
46
|
-
const targetLang = useGlobalStore(settingsSelectors.currentLanguage);
|
|
47
|
-
|
|
48
45
|
const hotkeys = [PREFIX_KEY, REGENERATE_KEY].join('+');
|
|
49
46
|
|
|
50
47
|
useHotkeys(
|
|
51
48
|
hotkeys,
|
|
52
49
|
() => {
|
|
53
|
-
console.log(111);
|
|
54
50
|
const lastMessage = data.at(-1);
|
|
55
51
|
if (!lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system') return;
|
|
56
52
|
resendMessage(lastMessage.id);
|
|
@@ -68,17 +64,28 @@ const List = memo(() => {
|
|
|
68
64
|
enableHistoryCount={enableHistoryCount}
|
|
69
65
|
historyCount={historyCount}
|
|
70
66
|
loadingId={chatLoadingId}
|
|
71
|
-
onActionsClick={{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
onActionsClick={(action, { id, error }) => {
|
|
68
|
+
switch (action.key) {
|
|
69
|
+
case 'del': {
|
|
70
|
+
deleteMessage(id);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'regenerate': {
|
|
74
|
+
resendMessage(id);
|
|
75
|
+
|
|
76
|
+
// if this message is an error message, we need to delete it
|
|
77
|
+
if (error) deleteMessage(id);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
// click the menu item with translate item, the result is:
|
|
83
|
+
// key: 'en-US'
|
|
84
|
+
// keyPath: ['en-US','translate']
|
|
85
|
+
if (action.keyPath.at(-1) === 'translate') {
|
|
86
|
+
const lang = action.keyPath[0];
|
|
87
|
+
translateMessage(id, lang);
|
|
88
|
+
}
|
|
82
89
|
}}
|
|
83
90
|
onMessageChange={(id, content) =>
|
|
84
91
|
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content })
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
|
+
|
|
3
|
+
export const chainLangDetect = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
4
|
+
messages: [
|
|
5
|
+
{
|
|
6
|
+
content:
|
|
7
|
+
'你是一名精通全世界语言的语言专家,你需要识别用户输入的内容,以国际标准 locale 进行输出',
|
|
8
|
+
role: 'system',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
content: '{你好}',
|
|
12
|
+
role: 'user',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
content: 'zh-CN',
|
|
16
|
+
role: 'assistant',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
content: '{hello}',
|
|
20
|
+
role: 'user',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
content: 'en-US',
|
|
24
|
+
role: 'assistant',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
content: `{${content}}`,
|
|
28
|
+
role: 'user',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pick emoji for user prompt
|
|
5
|
+
* @param content
|
|
6
|
+
*/
|
|
7
|
+
export const chainPickEmoji = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
8
|
+
messages: [
|
|
9
|
+
{
|
|
10
|
+
content: '你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。',
|
|
11
|
+
role: 'system',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
content: `输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。`,
|
|
15
|
+
role: 'user',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
content: `💅`,
|
|
19
|
+
role: 'assistant',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
content: `输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。`,
|
|
23
|
+
role: 'user',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
content: `🧪`,
|
|
27
|
+
role: 'assistant',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
content: `输入:${content}`,
|
|
31
|
+
role: 'user',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* summary agent name for user prompt
|
|
5
|
+
* @param content
|
|
6
|
+
*/
|
|
7
|
+
export const chainSummaryAgentName = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
5
8
|
messages: [
|
|
6
9
|
{
|
|
7
10
|
content: `你是一名擅长起名的起名大师,你需要将用户的描述总结为 20 个字以内的角色,格式要求如下:
|
|
@@ -30,47 +33,3 @@ export const promptSummaryAgentName = (content: string): Partial<OpenAIChatStrea
|
|
|
30
33
|
{ content: `输入: {${content}}`, role: 'user' },
|
|
31
34
|
],
|
|
32
35
|
});
|
|
33
|
-
|
|
34
|
-
// 自动挑选 emoji 和背景色
|
|
35
|
-
export const promptPickEmoji = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
36
|
-
messages: [
|
|
37
|
-
{
|
|
38
|
-
content: '你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。',
|
|
39
|
-
role: 'system',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
content: `输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。`,
|
|
43
|
-
role: 'user',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
content: `💅`,
|
|
47
|
-
role: 'assistant',
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
content: `输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。`,
|
|
51
|
-
role: 'user',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
content: `🧪`,
|
|
55
|
-
role: 'assistant',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
content: `输入:${content}`,
|
|
59
|
-
role: 'user',
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const promptSummaryDescription = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
65
|
-
messages: [
|
|
66
|
-
{
|
|
67
|
-
content:
|
|
68
|
-
'你是一名擅长会话的助理,你需要将用户的输入的内容总结为一个专家的简介,不超过 20 个字',
|
|
69
|
-
role: 'system',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
content: `${content}`,
|
|
73
|
-
role: 'user',
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
|
+
|
|
3
|
+
export const chainSummaryDescription = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
4
|
+
messages: [
|
|
5
|
+
{
|
|
6
|
+
content:
|
|
7
|
+
'你是一名擅长会话的助理,你需要将用户的输入的内容总结为一个专家的简介,不超过 20 个字',
|
|
8
|
+
role: 'system',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
content: `${content}`,
|
|
12
|
+
role: 'user',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { chatHelpers } from '@/store/session/slices/chat/helpers';
|
|
2
|
+
import { LanguageModel } from '@/types/llm';
|
|
3
|
+
import { OpenAIChatMessage, OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
4
|
+
|
|
5
|
+
export const chainSummaryTitle = async (
|
|
6
|
+
messages: OpenAIChatMessage[],
|
|
7
|
+
): Promise<Partial<OpenAIChatStreamPayload>> => {
|
|
8
|
+
const finalMessages: OpenAIChatMessage[] = [
|
|
9
|
+
{
|
|
10
|
+
content:
|
|
11
|
+
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不需要包含标点符号',
|
|
12
|
+
role: 'system',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
content: `${messages.map((message) => `${message.role}: ${message.content}`).join('\n')}
|
|
16
|
+
|
|
17
|
+
请总结上述对话为10个字以内的标题,不需要包含标点符号`,
|
|
18
|
+
role: 'user',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
// 如果超过 4k,则使用 GPT3.5 16K 模型
|
|
22
|
+
const tokens = await chatHelpers.getMessagesTokenCount(finalMessages);
|
|
23
|
+
let model: LanguageModel | undefined = undefined;
|
|
24
|
+
if (tokens > 4000) {
|
|
25
|
+
model = LanguageModel.GPT3_5_16K;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
messages: finalMessages,
|
|
30
|
+
model,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
2
|
+
|
|
3
|
+
export const chainTranslate = (
|
|
4
|
+
content: string,
|
|
5
|
+
targetLang: string,
|
|
6
|
+
): Partial<OpenAIChatStreamPayload> => ({
|
|
7
|
+
messages: [
|
|
8
|
+
{
|
|
9
|
+
content: '你是一名擅长翻译的助理,你需要将输入的语言翻译为目标语言',
|
|
10
|
+
role: 'system',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
content: `请将以下内容 ${content},翻译为 ${targetLang} `,
|
|
14
|
+
role: 'user',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { chainPickEmoji } from '@/chains/pickEmoji';
|
|
4
|
+
import { chainSummaryAgentName } from '@/chains/summaryAgentName';
|
|
5
|
+
import { chainSummaryDescription } from '@/chains/summaryDescription';
|
|
4
6
|
import { MetaData } from '@/types/meta';
|
|
5
7
|
import { LobeAgentConfig } from '@/types/session';
|
|
6
8
|
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
@@ -73,7 +75,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
73
75
|
onLoadingChange: (loading) => {
|
|
74
76
|
get().updateLoadingState('avatar', loading);
|
|
75
77
|
},
|
|
76
|
-
params:
|
|
78
|
+
params: chainPickEmoji(systemRole),
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
if (emoji) {
|
|
@@ -100,7 +102,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
100
102
|
updateLoadingState('description', loading);
|
|
101
103
|
},
|
|
102
104
|
onMessageHandle: streamUpdateMeta('description'),
|
|
103
|
-
params:
|
|
105
|
+
params: chainSummaryDescription(systemRole),
|
|
104
106
|
});
|
|
105
107
|
},
|
|
106
108
|
autocompleteAgentTitle: async () => {
|
|
@@ -123,7 +125,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
123
125
|
updateLoadingState('title', loading);
|
|
124
126
|
},
|
|
125
127
|
onMessageHandle: streamUpdateMeta('title'),
|
|
126
|
-
params:
|
|
128
|
+
params: chainSummaryAgentName(systemRole),
|
|
127
129
|
});
|
|
128
130
|
},
|
|
129
131
|
autocompleteAllMeta: (replace) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
2
|
|
|
3
|
-
import { chainSummaryTitle } from '@/chains/
|
|
3
|
+
import { chainSummaryTitle } from '@/chains/summaryTitle';
|
|
4
4
|
import { LOADING_FLAT } from '@/const/message';
|
|
5
5
|
import { SessionStore } from '@/store/session';
|
|
6
6
|
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { produce } from 'immer';
|
|
2
2
|
import { StateCreator } from 'zustand/vanilla';
|
|
3
3
|
|
|
4
|
-
import { chainLangDetect
|
|
4
|
+
import { chainLangDetect } from '@/chains/langDetect';
|
|
5
|
+
import { chainTranslate } from '@/chains/translate';
|
|
5
6
|
import { supportLocales } from '@/locales/options';
|
|
6
7
|
import { SessionStore } from '@/store/session';
|
|
7
8
|
import { fetchPresetTaskResult } from '@/utils/fetch';
|
package/src/chains/chat.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { chatHelpers } from '@/store/session/helpers';
|
|
2
|
-
import { LanguageModel } from '@/types/llm';
|
|
3
|
-
import { OpenAIChatMessage, OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
4
|
-
|
|
5
|
-
export const chainSummaryTitle = async (
|
|
6
|
-
messages: OpenAIChatMessage[],
|
|
7
|
-
): Promise<Partial<OpenAIChatStreamPayload>> => {
|
|
8
|
-
const finalMessages: OpenAIChatMessage[] = [
|
|
9
|
-
{
|
|
10
|
-
content:
|
|
11
|
-
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不需要包含标点符号',
|
|
12
|
-
role: 'system',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
content: `${messages.map((message) => `${message.role}: ${message.content}`).join('\n')}
|
|
16
|
-
|
|
17
|
-
请总结上述对话为10个字以内的标题,不需要包含标点符号`,
|
|
18
|
-
role: 'user',
|
|
19
|
-
},
|
|
20
|
-
];
|
|
21
|
-
// 如果超过 4k,则使用 GPT3.5 16K 模型
|
|
22
|
-
const tokens = await chatHelpers.getMessagesTokenCount(finalMessages);
|
|
23
|
-
let model: LanguageModel | undefined = undefined;
|
|
24
|
-
if (tokens > 4000) {
|
|
25
|
-
model = LanguageModel.GPT3_5_16K;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
messages: finalMessages,
|
|
30
|
-
model,
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const chainTranslate = (
|
|
35
|
-
content: string,
|
|
36
|
-
targetLang: string,
|
|
37
|
-
): Partial<OpenAIChatStreamPayload> => ({
|
|
38
|
-
messages: [
|
|
39
|
-
{
|
|
40
|
-
content: '你是一名擅长翻译的助理,你需要将输入的语言翻译为目标语言',
|
|
41
|
-
role: 'system',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
content: `请将以下内容 ${content},翻译为 ${targetLang} `,
|
|
45
|
-
role: 'user',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
export const chainLangDetect = (content: string): Partial<OpenAIChatStreamPayload> => ({
|
|
50
|
-
messages: [
|
|
51
|
-
{
|
|
52
|
-
content:
|
|
53
|
-
'你是一名精通全世界语言的语言专家,你需要识别用户输入的内容,以国际标准 locale 进行输出',
|
|
54
|
-
role: 'system',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
content: '{你好}',
|
|
58
|
-
role: 'user',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
content: 'zh-CN',
|
|
62
|
-
role: 'assistant',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
content: '{hello}',
|
|
66
|
-
role: 'user',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
content: 'en-US',
|
|
70
|
-
role: 'assistant',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
content: `{${content}}`,
|
|
74
|
-
role: 'user',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
});
|