@lobehub/lobehub 2.0.0-next.274 → 2.0.0-next.276
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 +42 -0
- package/changelog/v1.json +14 -0
- package/package.json +2 -2
- package/src/app/[variants]/(main)/agent/features/Conversation/index.tsx +8 -2
- package/src/app/[variants]/(main)/agent/features/Portal/_layout/Mobile.tsx +1 -0
- package/src/app/[variants]/(main)/agent/features/Portal/features/Portal.tsx +4 -2
- package/src/app/[variants]/(main)/community/(detail)/features/MakedownRender.tsx +8 -6
- package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +1 -0
- package/src/app/[variants]/(main)/group/features/Portal/features/Portal.tsx +4 -2
- package/src/app/[variants]/(main)/settings/proxy/features/ProxyForm.tsx +156 -253
- package/src/app/[variants]/(main)/settings/proxy/index.tsx +1 -3
- package/src/features/Conversation/Messages/User/useMarkdown.tsx +1 -0
- package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +10 -6
- package/src/features/PageEditor/Header/index.tsx +12 -10
- package/src/features/PageEditor/Header/useMenu.tsx +45 -48
- package/src/features/RightPanel/ToggleRightPanelButton.tsx +3 -1
- package/src/hooks/useYamlArguments.ts +11 -8
- package/tests/mocks/lru_map.ts +40 -0
- package/vitest.config.mts +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.276](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.275...v2.0.0-next.276)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-13**</sup>
|
|
8
|
+
|
|
9
|
+
<br/>
|
|
10
|
+
|
|
11
|
+
<details>
|
|
12
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
13
|
+
|
|
14
|
+
</details>
|
|
15
|
+
|
|
16
|
+
<div align="right">
|
|
17
|
+
|
|
18
|
+
[](#readme-top)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
## [Version 2.0.0-next.275](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.274...v2.0.0-next.275)
|
|
23
|
+
|
|
24
|
+
<sup>Released on **2026-01-13**</sup>
|
|
25
|
+
|
|
26
|
+
#### ✨ Features
|
|
27
|
+
|
|
28
|
+
- **misc**: Improve PageEditor header UX with DropdownMenu and i18n support.
|
|
29
|
+
|
|
30
|
+
<br/>
|
|
31
|
+
|
|
32
|
+
<details>
|
|
33
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
34
|
+
|
|
35
|
+
#### What's improved
|
|
36
|
+
|
|
37
|
+
- **misc**: Improve PageEditor header UX with DropdownMenu and i18n support, closes [#11462](https://github.com/lobehub/lobe-chat/issues/11462) ([ae499c9](https://github.com/lobehub/lobe-chat/commit/ae499c9))
|
|
38
|
+
|
|
39
|
+
</details>
|
|
40
|
+
|
|
41
|
+
<div align="right">
|
|
42
|
+
|
|
43
|
+
[](#readme-top)
|
|
44
|
+
|
|
45
|
+
</div>
|
|
46
|
+
|
|
5
47
|
## [Version 2.0.0-next.274](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.273...v2.0.0-next.274)
|
|
6
48
|
|
|
7
49
|
<sup>Released on **2026-01-13**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {},
|
|
4
|
+
"date": "2026-01-13",
|
|
5
|
+
"version": "2.0.0-next.276"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"children": {
|
|
9
|
+
"features": [
|
|
10
|
+
"Improve PageEditor header UX with DropdownMenu and i18n support."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"date": "2026-01-13",
|
|
14
|
+
"version": "2.0.0-next.275"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"children": {
|
|
4
18
|
"features": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.276",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent 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",
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
"@lobehub/icons": "^4.0.2",
|
|
207
207
|
"@lobehub/market-sdk": "0.28.1",
|
|
208
208
|
"@lobehub/tts": "^4.0.2",
|
|
209
|
-
"@lobehub/ui": "^4.
|
|
209
|
+
"@lobehub/ui": "^4.18.0",
|
|
210
210
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
211
211
|
"@neondatabase/serverless": "^1.0.2",
|
|
212
212
|
"@next/third-parties": "^16.1.1",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Flexbox, TooltipGroup } from '@lobehub/ui';
|
|
2
|
-
import { memo } from 'react';
|
|
2
|
+
import React, { memo } from 'react';
|
|
3
3
|
|
|
4
4
|
import DragUploadZone, { useUploadFiles } from '@/components/DragUploadZone';
|
|
5
5
|
import { useAgentStore } from '@/store/agent';
|
|
@@ -10,6 +10,12 @@ import { systemStatusSelectors } from '@/store/global/selectors';
|
|
|
10
10
|
import ConversationArea from './ConversationArea';
|
|
11
11
|
import ChatHeader from './Header';
|
|
12
12
|
|
|
13
|
+
const wrapperStyle: React.CSSProperties = {
|
|
14
|
+
height: '100%',
|
|
15
|
+
minWidth: 300,
|
|
16
|
+
width: '100%',
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
const ChatConversation = memo(() => {
|
|
14
20
|
const showHeader = useGlobalStore(systemStatusSelectors.showChatHeader);
|
|
15
21
|
|
|
@@ -19,7 +25,7 @@ const ChatConversation = memo(() => {
|
|
|
19
25
|
const { handleUploadFiles } = useUploadFiles({ model, provider });
|
|
20
26
|
|
|
21
27
|
return (
|
|
22
|
-
<DragUploadZone onUploadFiles={handleUploadFiles} style={
|
|
28
|
+
<DragUploadZone onUploadFiles={handleUploadFiles} style={wrapperStyle}>
|
|
23
29
|
<Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
|
|
24
30
|
{showHeader && <ChatHeader />}
|
|
25
31
|
<TooltipGroup>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { DraggablePanel, type DraggablePanelProps } from '@lobehub/ui';
|
|
4
4
|
import { createStaticStyles } from 'antd-style';
|
|
5
5
|
import isEqual from 'fast-deep-equal';
|
|
6
|
-
import { type PropsWithChildren, memo, useState } from 'react';
|
|
6
|
+
import { Activity, type PropsWithChildren, memo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
CHAT_PORTAL_MAX_WIDTH,
|
|
@@ -81,7 +81,9 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
|
|
|
81
81
|
showHandleWideArea={false}
|
|
82
82
|
size={{ height: '100%', width: portalWidth }}
|
|
83
83
|
>
|
|
84
|
-
{
|
|
84
|
+
<Activity mode={showPortal ? 'visible' : 'hidden'} name="AgentPortal">
|
|
85
|
+
{children}
|
|
86
|
+
</Activity>
|
|
85
87
|
</DraggablePanel>
|
|
86
88
|
);
|
|
87
89
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Center, Empty, Markdown } from '@lobehub/ui';
|
|
4
4
|
import { FileText } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
|
-
import {
|
|
6
|
+
import { memo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
import { H1, H2, H3, H4, H5 } from './Toc/Heading';
|
|
@@ -26,7 +26,7 @@ const MarkdownRender = memo<{ children?: string }>(({ children }) => {
|
|
|
26
26
|
<Markdown
|
|
27
27
|
allowHtml
|
|
28
28
|
components={{
|
|
29
|
-
a: ({ href, ...rest }
|
|
29
|
+
a: ({ href, ...rest }) => {
|
|
30
30
|
if (href && href.startsWith('http'))
|
|
31
31
|
return <Link {...rest} href={href} target={'_blank'} />;
|
|
32
32
|
return rest?.children;
|
|
@@ -36,12 +36,14 @@ const MarkdownRender = memo<{ children?: string }>(({ children }) => {
|
|
|
36
36
|
h3: H3,
|
|
37
37
|
h4: H4,
|
|
38
38
|
h5: H5,
|
|
39
|
-
img: ({ src, ...rest }
|
|
40
|
-
|
|
39
|
+
img: ({ src, ...rest }) => {
|
|
40
|
+
// FIXME ignore experimental blob image prop passing
|
|
41
|
+
if (typeof src !== 'string') return null;
|
|
42
|
+
if (src.includes('glama.ai')) return null;
|
|
41
43
|
|
|
42
44
|
// eslint-disable-next-line @next/next/no-img-element
|
|
43
|
-
if (src
|
|
44
|
-
return;
|
|
45
|
+
if (src.startsWith('http')) return <img src={src} {...rest} />;
|
|
46
|
+
return null;
|
|
45
47
|
},
|
|
46
48
|
}}
|
|
47
49
|
enableImageGallery={false}
|
|
@@ -4,7 +4,7 @@ import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } fro
|
|
|
4
4
|
import { Flexbox } from '@lobehub/ui';
|
|
5
5
|
import { createStaticStyles, useResponsive } from 'antd-style';
|
|
6
6
|
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import { type PropsWithChildren, memo, useState } from 'react';
|
|
7
|
+
import { Activity, type PropsWithChildren, memo, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
CHAT_PORTAL_MAX_WIDTH,
|
|
@@ -94,7 +94,9 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
|
|
|
94
94
|
minWidth: CHAT_PORTAL_WIDTH,
|
|
95
95
|
}}
|
|
96
96
|
>
|
|
97
|
-
<
|
|
97
|
+
<Activity mode={showPortal ? 'visible' : 'hidden'} name="GroupPortal">
|
|
98
|
+
<Flexbox className={styles.panel}>{children}</Flexbox>
|
|
99
|
+
</Activity>
|
|
98
100
|
</DraggablePanelContainer>
|
|
99
101
|
</DraggablePanel>
|
|
100
102
|
);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { type NetworkProxySettings } from '@lobechat/electron-client-ipc';
|
|
4
|
-
import { Alert,
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
4
|
+
import { Alert, Flexbox, Form, type FormGroupItemType, Icon, Skeleton } from '@lobehub/ui';
|
|
5
|
+
import { Form as AntdForm, Button, Input, Radio, Space, Switch } from 'antd';
|
|
6
|
+
import { Loader2Icon } from 'lucide-react';
|
|
7
7
|
import { useCallback, useEffect, useState } from 'react';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
|
10
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
10
11
|
import { desktopSettingsService } from '@/services/electron/settings';
|
|
11
12
|
import { useElectronStore } from '@/store/electron';
|
|
12
13
|
|
|
@@ -19,15 +20,15 @@ interface ProxyTestResult {
|
|
|
19
20
|
const ProxyForm = () => {
|
|
20
21
|
const { t } = useTranslation('electron');
|
|
21
22
|
const [form] = Form.useForm();
|
|
22
|
-
const { message } = App.useApp();
|
|
23
23
|
const [testUrl, setTestUrl] = useState('https://www.google.com');
|
|
24
24
|
const [isTesting, setIsTesting] = useState(false);
|
|
25
25
|
const [isSaving, setIsSaving] = useState(false);
|
|
26
26
|
const [testResult, setTestResult] = useState<ProxyTestResult | null>(null);
|
|
27
27
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
28
|
+
const [loading, setLoading] = useState(false);
|
|
28
29
|
|
|
29
|
-
const isEnableProxy =
|
|
30
|
-
const proxyRequireAuth =
|
|
30
|
+
const isEnableProxy = AntdForm.useWatch('enableProxy', form);
|
|
31
|
+
const proxyRequireAuth = AntdForm.useWatch('proxyRequireAuth', form);
|
|
31
32
|
|
|
32
33
|
const [setProxySettings, useGetProxySettings] = useElectronStore((s) => [
|
|
33
34
|
s.setProxySettings,
|
|
@@ -44,19 +45,12 @@ const ProxyForm = () => {
|
|
|
44
45
|
|
|
45
46
|
// 监听表单变化
|
|
46
47
|
const handleValuesChange = useCallback(() => {
|
|
48
|
+
setLoading(true);
|
|
47
49
|
setHasUnsavedChanges(true);
|
|
48
50
|
setTestResult(null); // 清除之前的测试结果
|
|
51
|
+
setLoading(false);
|
|
49
52
|
}, []);
|
|
50
53
|
|
|
51
|
-
const updateFormValue = (value: any) => {
|
|
52
|
-
const preValues = form.getFieldsValue();
|
|
53
|
-
form.setFieldsValue(value);
|
|
54
|
-
const newValues = form.getFieldsValue();
|
|
55
|
-
if (isEqual(newValues, preValues)) return;
|
|
56
|
-
|
|
57
|
-
handleValuesChange();
|
|
58
|
-
};
|
|
59
|
-
|
|
60
54
|
// 保存配置
|
|
61
55
|
const handleSave = useCallback(async () => {
|
|
62
56
|
try {
|
|
@@ -64,15 +58,12 @@ const ProxyForm = () => {
|
|
|
64
58
|
const values = await form.validateFields();
|
|
65
59
|
await setProxySettings(values);
|
|
66
60
|
setHasUnsavedChanges(false);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (error instanceof Error) {
|
|
70
|
-
message.error(t('proxy.saveFailed', { error: error.message }));
|
|
71
|
-
}
|
|
61
|
+
} catch {
|
|
62
|
+
// validation error
|
|
72
63
|
} finally {
|
|
73
64
|
setIsSaving(false);
|
|
74
65
|
}
|
|
75
|
-
}, [form,
|
|
66
|
+
}, [form, setProxySettings]);
|
|
76
67
|
|
|
77
68
|
// 重置配置
|
|
78
69
|
const handleReset = useCallback(() => {
|
|
@@ -107,240 +98,159 @@ const ProxyForm = () => {
|
|
|
107
98
|
success: false,
|
|
108
99
|
};
|
|
109
100
|
setTestResult(result);
|
|
110
|
-
message.error(t('proxy.testFailed'));
|
|
111
101
|
} finally {
|
|
112
102
|
setIsTesting(false);
|
|
113
103
|
}
|
|
114
|
-
}, [proxySettings, testUrl]);
|
|
104
|
+
}, [proxySettings, testUrl, form]);
|
|
115
105
|
|
|
116
|
-
if (isLoading) return <Skeleton />;
|
|
106
|
+
if (isLoading) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
|
117
107
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
>
|
|
134
|
-
<Form.Item name="enableProxy" noStyle valuePropName="checked">
|
|
135
|
-
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
|
136
|
-
<Flexbox>
|
|
137
|
-
<Text as={'h4'}>{t('proxy.enable')}</Text>
|
|
138
|
-
<Text type={'secondary'}>{t('proxy.enableDesc')}</Text>
|
|
139
|
-
</Flexbox>
|
|
140
|
-
<Switch
|
|
141
|
-
checked={isEnableProxy}
|
|
142
|
-
onChange={(checked) => {
|
|
143
|
-
updateFormValue({ enableProxy: checked });
|
|
144
|
-
}}
|
|
145
|
-
/>
|
|
146
|
-
</Flexbox>
|
|
147
|
-
</Form.Item>
|
|
148
|
-
</Block>
|
|
108
|
+
const enableProxyGroup: FormGroupItemType = {
|
|
109
|
+
children: [
|
|
110
|
+
{
|
|
111
|
+
children: <Switch />,
|
|
112
|
+
desc: t('proxy.enableDesc'),
|
|
113
|
+
label: t('proxy.enable'),
|
|
114
|
+
layout: 'horizontal',
|
|
115
|
+
minWidth: undefined,
|
|
116
|
+
name: 'enableProxy',
|
|
117
|
+
valuePropName: 'checked',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
|
121
|
+
title: t('proxy.enable'),
|
|
122
|
+
};
|
|
149
123
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
124
|
+
const basicSettingsGroup: FormGroupItemType = {
|
|
125
|
+
children: [
|
|
126
|
+
{
|
|
127
|
+
children: (
|
|
128
|
+
<Radio.Group disabled={!isEnableProxy}>
|
|
129
|
+
<Radio value="http">HTTP</Radio>
|
|
130
|
+
<Radio value="https">HTTPS</Radio>
|
|
131
|
+
<Radio value="socks5">SOCKS5</Radio>
|
|
132
|
+
</Radio.Group>
|
|
133
|
+
),
|
|
134
|
+
label: t('proxy.type'),
|
|
135
|
+
minWidth: undefined,
|
|
136
|
+
name: 'proxyType',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
children: <Input disabled={!isEnableProxy} placeholder="127.0.0.1" />,
|
|
140
|
+
desc: t('proxy.validation.serverRequired'),
|
|
141
|
+
label: t('proxy.server'),
|
|
142
|
+
name: 'proxyServer',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
children: <Input disabled={!isEnableProxy} placeholder="7890" style={{ width: 120 }} />,
|
|
146
|
+
desc: t('proxy.validation.portRequired'),
|
|
147
|
+
label: t('proxy.port'),
|
|
148
|
+
name: 'proxyPort',
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
|
152
|
+
title: t('proxy.basicSettings'),
|
|
153
|
+
};
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
155
|
+
const authGroup: FormGroupItemType = {
|
|
156
|
+
children: [
|
|
157
|
+
{
|
|
158
|
+
children: <Switch disabled={!isEnableProxy} />,
|
|
159
|
+
desc: t('proxy.authDesc'),
|
|
160
|
+
label: t('proxy.auth'),
|
|
161
|
+
layout: 'horizontal',
|
|
162
|
+
minWidth: undefined,
|
|
163
|
+
name: 'proxyRequireAuth',
|
|
164
|
+
valuePropName: 'checked',
|
|
165
|
+
},
|
|
166
|
+
...(proxyRequireAuth && isEnableProxy
|
|
167
|
+
? [
|
|
168
|
+
{
|
|
169
|
+
children: <Input placeholder={t('proxy.username_placeholder')} />,
|
|
170
|
+
label: t('proxy.username'),
|
|
171
|
+
name: 'proxyUsername',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
children: <Input.Password placeholder={t('proxy.password_placeholder')} />,
|
|
175
|
+
label: t('proxy.password'),
|
|
176
|
+
name: 'proxyPassword',
|
|
177
|
+
},
|
|
178
|
+
]
|
|
179
|
+
: []),
|
|
180
|
+
],
|
|
181
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
|
182
|
+
title: t('proxy.authSettings'),
|
|
183
|
+
};
|
|
201
184
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<Form.Item
|
|
226
|
-
dependencies={['enableProxy']}
|
|
227
|
-
name="proxyRequireAuth"
|
|
228
|
-
noStyle
|
|
229
|
-
valuePropName="checked"
|
|
230
|
-
>
|
|
231
|
-
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
|
232
|
-
<Flexbox>
|
|
233
|
-
<Text as={'h5'}>{t('proxy.auth')}</Text>
|
|
234
|
-
<Text type={'secondary'}>{t('proxy.authDesc')}</Text>
|
|
185
|
+
const testGroup: FormGroupItemType = {
|
|
186
|
+
children: [
|
|
187
|
+
{
|
|
188
|
+
children: (
|
|
189
|
+
<Flexbox gap={8}>
|
|
190
|
+
<Space.Compact style={{ width: '100%' }}>
|
|
191
|
+
<Input
|
|
192
|
+
onChange={(e) => setTestUrl(e.target.value)}
|
|
193
|
+
placeholder={t('proxy.testUrlPlaceholder')}
|
|
194
|
+
style={{ flex: 1 }}
|
|
195
|
+
value={testUrl}
|
|
196
|
+
/>
|
|
197
|
+
<Button loading={isTesting} onClick={handleTest} type="default">
|
|
198
|
+
{t('proxy.testButton')}
|
|
199
|
+
</Button>
|
|
200
|
+
</Space.Compact>
|
|
201
|
+
{/* 测试结果显示 */}
|
|
202
|
+
{!testResult ? null : testResult.success ? (
|
|
203
|
+
<Alert
|
|
204
|
+
closable
|
|
205
|
+
title={
|
|
206
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
207
|
+
{t('proxy.testSuccessWithTime', { time: testResult.responseTime })}
|
|
235
208
|
</Flexbox>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
({ getFieldValue }) => ({
|
|
252
|
-
message: t('proxy.validation.usernameRequired'),
|
|
253
|
-
required: getFieldValue('proxyRequireAuth') && getFieldValue('enableProxy'),
|
|
254
|
-
}),
|
|
255
|
-
]}
|
|
256
|
-
style={{
|
|
257
|
-
display:
|
|
258
|
-
form.getFieldValue('proxyRequireAuth') && form.getFieldValue('enableProxy')
|
|
259
|
-
? 'block'
|
|
260
|
-
: 'none',
|
|
261
|
-
}}
|
|
262
|
-
>
|
|
263
|
-
<Input placeholder={t('proxy.username_placeholder')} />
|
|
264
|
-
</Form.Item>
|
|
265
|
-
|
|
266
|
-
<Form.Item
|
|
267
|
-
dependencies={['proxyRequireAuth', 'enableProxy']}
|
|
268
|
-
label={t('proxy.password')}
|
|
269
|
-
name="proxyPassword"
|
|
270
|
-
rules={[
|
|
271
|
-
({ getFieldValue }) => ({
|
|
272
|
-
message: t('proxy.validation.passwordRequired'),
|
|
273
|
-
required: getFieldValue('proxyRequireAuth') && getFieldValue('enableProxy'),
|
|
274
|
-
}),
|
|
275
|
-
]}
|
|
276
|
-
style={{
|
|
277
|
-
display:
|
|
278
|
-
form.getFieldValue('proxyRequireAuth') && form.getFieldValue('enableProxy')
|
|
279
|
-
? 'block'
|
|
280
|
-
: 'none',
|
|
281
|
-
}}
|
|
282
|
-
>
|
|
283
|
-
<Input.Password placeholder={t('proxy.password_placeholder')} />
|
|
284
|
-
</Form.Item>
|
|
285
|
-
</Flexbox>
|
|
209
|
+
}
|
|
210
|
+
type={'success'}
|
|
211
|
+
/>
|
|
212
|
+
) : (
|
|
213
|
+
<Alert
|
|
214
|
+
closable
|
|
215
|
+
title={
|
|
216
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
217
|
+
{t('proxy.testFailed')}: {testResult.message}
|
|
218
|
+
</Flexbox>
|
|
219
|
+
}
|
|
220
|
+
type={'error'}
|
|
221
|
+
variant={'outlined'}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
286
224
|
</Flexbox>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
225
|
+
),
|
|
226
|
+
desc: t('proxy.testDescription'),
|
|
227
|
+
label: t('proxy.testUrl'),
|
|
228
|
+
minWidth: undefined,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
|
232
|
+
title: t('proxy.connectionTest'),
|
|
233
|
+
};
|
|
290
234
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
/>
|
|
311
|
-
<Button loading={isTesting} onClick={handleTest} type="default">
|
|
312
|
-
{t('proxy.testButton')}
|
|
313
|
-
</Button>
|
|
314
|
-
</Space.Compact>
|
|
315
|
-
{/* 测试结果显示 */}
|
|
316
|
-
{!testResult ? null : testResult.success ? (
|
|
317
|
-
<Alert
|
|
318
|
-
closable
|
|
319
|
-
title={
|
|
320
|
-
<Flexbox align="center" gap={8} horizontal>
|
|
321
|
-
{t('proxy.testSuccessWithTime', { time: testResult.responseTime })}
|
|
322
|
-
</Flexbox>
|
|
323
|
-
}
|
|
324
|
-
type={'success'}
|
|
325
|
-
/>
|
|
326
|
-
) : (
|
|
327
|
-
<Alert
|
|
328
|
-
closable
|
|
329
|
-
title={
|
|
330
|
-
<Flexbox align="center" gap={8} horizontal>
|
|
331
|
-
{t('proxy.testFailed')}: {testResult.message}
|
|
332
|
-
</Flexbox>
|
|
333
|
-
}
|
|
334
|
-
type={'error'}
|
|
335
|
-
variant={'outlined'}
|
|
336
|
-
/>
|
|
337
|
-
)}
|
|
338
|
-
</Flexbox>
|
|
339
|
-
</Form.Item>
|
|
340
|
-
</Flexbox>
|
|
341
|
-
</Block>
|
|
342
|
-
{/* 操作按钮 */}
|
|
343
|
-
<Space>
|
|
235
|
+
return (
|
|
236
|
+
<Flexbox gap={24}>
|
|
237
|
+
<Form
|
|
238
|
+
collapsible={false}
|
|
239
|
+
form={form}
|
|
240
|
+
initialValues={proxySettings}
|
|
241
|
+
items={[enableProxyGroup, basicSettingsGroup, authGroup, testGroup]}
|
|
242
|
+
itemsType={'group'}
|
|
243
|
+
onValuesChange={handleValuesChange}
|
|
244
|
+
variant={'filled'}
|
|
245
|
+
{...FORM_STYLE}
|
|
246
|
+
/>
|
|
247
|
+
<Flexbox align="end" justify="flex-end">
|
|
248
|
+
{hasUnsavedChanges && (
|
|
249
|
+
<span style={{ color: 'var(--ant-color-warning)', marginBottom: 8 }}>
|
|
250
|
+
{t('proxy.unsavedChanges')}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
<Flexbox gap={8} horizontal>
|
|
344
254
|
<Button
|
|
345
255
|
disabled={!hasUnsavedChanges}
|
|
346
256
|
loading={isSaving}
|
|
@@ -349,19 +259,12 @@ const ProxyForm = () => {
|
|
|
349
259
|
>
|
|
350
260
|
{t('proxy.saveButton')}
|
|
351
261
|
</Button>
|
|
352
|
-
|
|
353
262
|
<Button disabled={!hasUnsavedChanges || isSaving} onClick={handleReset}>
|
|
354
263
|
{t('proxy.resetButton')}
|
|
355
264
|
</Button>
|
|
356
|
-
|
|
357
|
-
{hasUnsavedChanges && (
|
|
358
|
-
<Text style={{ marginLeft: 8 }} type="warning">
|
|
359
|
-
{t('proxy.unsavedChanges')}
|
|
360
|
-
</Text>
|
|
361
|
-
)}
|
|
362
|
-
</Space>
|
|
265
|
+
</Flexbox>
|
|
363
266
|
</Flexbox>
|
|
364
|
-
</
|
|
267
|
+
</Flexbox>
|
|
365
268
|
);
|
|
366
269
|
};
|
|
367
270
|
|
|
@@ -30,6 +30,7 @@ export const useMarkdown = (id: string): Partial<MarkdownProps> => {
|
|
|
30
30
|
() =>
|
|
31
31
|
({
|
|
32
32
|
components: Object.fromEntries(
|
|
33
|
+
// @ts-expect-error
|
|
33
34
|
markdownElements.map((element) => {
|
|
34
35
|
const Component = element.Component;
|
|
35
36
|
return [element.tag, (props: any) => <Component {...props} id={id} />];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { DEFAULT_INBOX_AVATAR } from '@lobechat/const';
|
|
3
4
|
import { nanoid } from '@lobechat/utils';
|
|
4
5
|
import { HIDE_TOOLBAR_COMMAND, type IEditor } from '@lobehub/editor';
|
|
5
6
|
import { type ChatInputActionsProps } from '@lobehub/editor/react';
|
|
6
|
-
import { Block } from '@lobehub/ui';
|
|
7
|
+
import { Avatar, Block } from '@lobehub/ui';
|
|
7
8
|
import { createStaticStyles, cssVar } from 'antd-style';
|
|
8
|
-
import { BotIcon } from 'lucide-react';
|
|
9
9
|
import { useMemo } from 'react';
|
|
10
|
+
import { useTranslation } from 'react-i18next';
|
|
10
11
|
|
|
11
12
|
import { useFileStore } from '@/store/file';
|
|
12
13
|
import { useGlobalStore } from '@/store/global';
|
|
@@ -23,11 +24,14 @@ const styles = createStaticStyles(({ css }) => ({
|
|
|
23
24
|
}));
|
|
24
25
|
|
|
25
26
|
export const useAskCopilotItem = (editor: IEditor | undefined): ChatInputActionsProps['items'] => {
|
|
27
|
+
const { t } = useTranslation('common');
|
|
26
28
|
const addSelectionContext = useFileStore((s) => s.addChatContextSelection);
|
|
27
29
|
|
|
28
30
|
return useMemo(() => {
|
|
29
31
|
if (!editor) return [];
|
|
30
32
|
|
|
33
|
+
const label = t('cmdk.askLobeAI');
|
|
34
|
+
|
|
31
35
|
return [
|
|
32
36
|
{
|
|
33
37
|
children: (
|
|
@@ -82,14 +86,14 @@ export const useAskCopilotItem = (editor: IEditor | undefined): ChatInputActions
|
|
|
82
86
|
paddingInline={12}
|
|
83
87
|
variant="borderless"
|
|
84
88
|
>
|
|
85
|
-
<
|
|
86
|
-
<span>
|
|
89
|
+
<Avatar avatar={DEFAULT_INBOX_AVATAR} shape="square" size={16} />
|
|
90
|
+
<span>{label}</span>
|
|
87
91
|
</Block>
|
|
88
92
|
),
|
|
89
93
|
key: 'ask-copilot',
|
|
90
|
-
label
|
|
94
|
+
label,
|
|
91
95
|
onClick: () => {},
|
|
92
96
|
},
|
|
93
97
|
];
|
|
94
|
-
}, [addSelectionContext, editor]);
|
|
98
|
+
}, [addSelectionContext, editor, t]);
|
|
95
99
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ActionIcon, Avatar,
|
|
4
|
-
import { ArrowLeftIcon,
|
|
3
|
+
import { ActionIcon, Avatar, DropdownMenu, Text } from '@lobehub/ui';
|
|
4
|
+
import { ArrowLeftIcon, MoreHorizontal } from 'lucide-react';
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
@@ -49,18 +49,20 @@ const Header = memo(() => {
|
|
|
49
49
|
}
|
|
50
50
|
right={
|
|
51
51
|
<>
|
|
52
|
-
<ToggleRightPanelButton icon={BotMessageSquareIcon} showActive={true} />
|
|
53
52
|
{/* Three-dot menu */}
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
style: { minWidth: 200 },
|
|
58
|
-
}}
|
|
53
|
+
<DropdownMenu
|
|
54
|
+
items={menuItems}
|
|
55
|
+
nativeButton={false}
|
|
59
56
|
placement="bottomRight"
|
|
60
|
-
|
|
57
|
+
popupProps={{
|
|
58
|
+
style: {
|
|
59
|
+
minWidth: 200,
|
|
60
|
+
},
|
|
61
|
+
}}
|
|
61
62
|
>
|
|
62
63
|
<ActionIcon icon={MoreHorizontal} size={DESKTOP_HEADER_ICON_SIZE} />
|
|
63
|
-
</
|
|
64
|
+
</DropdownMenu>
|
|
65
|
+
<ToggleRightPanelButton hideWhenExpanded showActive={false} />
|
|
64
66
|
</>
|
|
65
67
|
}
|
|
66
68
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { App
|
|
1
|
+
import { type DropdownItem, Icon } from '@lobehub/ui';
|
|
2
|
+
import { App } from 'antd';
|
|
3
3
|
import { cssVar, useResponsive } from 'antd-style';
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
import { CopyPlus, Download, Link2, Trash2 } from 'lucide-react';
|
|
@@ -75,25 +75,17 @@ export const useMenu = (): { menuItems: any[] } => {
|
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
const menuItems = useMemo(
|
|
79
|
-
|
|
78
|
+
const menuItems = useMemo<DropdownItem[]>(() => {
|
|
79
|
+
const items: DropdownItem[] = [
|
|
80
80
|
...(showViewModeSwitch
|
|
81
81
|
? [
|
|
82
82
|
{
|
|
83
|
+
checked: wideScreen,
|
|
83
84
|
key: 'full-width',
|
|
84
|
-
label: (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
checked={wideScreen}
|
|
89
|
-
onChange={toggleWideScreen}
|
|
90
|
-
onClick={(checked, event) => {
|
|
91
|
-
event.stopPropagation();
|
|
92
|
-
}}
|
|
93
|
-
size="small"
|
|
94
|
-
/>
|
|
95
|
-
</Flexbox>
|
|
96
|
-
),
|
|
85
|
+
label: t('viewMode.fullWidth', { ns: 'chat' }),
|
|
86
|
+
onCheckedChange: toggleWideScreen,
|
|
87
|
+
|
|
88
|
+
type: 'checkbox' as const,
|
|
97
89
|
},
|
|
98
90
|
{
|
|
99
91
|
type: 'divider' as const,
|
|
@@ -140,38 +132,43 @@ export const useMenu = (): { menuItems: any[] } => {
|
|
|
140
132
|
key: 'export',
|
|
141
133
|
label: t('pageEditor.menu.export'),
|
|
142
134
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
if (lastUpdatedTime) {
|
|
138
|
+
items.push(
|
|
139
|
+
{
|
|
140
|
+
type: 'divider' as const,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
disabled: true,
|
|
144
|
+
key: 'page-info',
|
|
145
|
+
label: (
|
|
146
|
+
<div style={{ color: cssVar.colorTextTertiary, fontSize: 12, lineHeight: 1.6 }}>
|
|
147
|
+
<div>
|
|
148
|
+
{lastUpdatedTime
|
|
149
|
+
? t('pageEditor.editedAt', {
|
|
150
|
+
time: dayjs(lastUpdatedTime).format('MMMM D, YYYY [at] h:mm A'),
|
|
151
|
+
})
|
|
152
|
+
: ''}
|
|
153
|
+
</div>
|
|
157
154
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
);
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return items;
|
|
160
|
+
}, [
|
|
161
|
+
lastUpdatedTime,
|
|
162
|
+
storeApi,
|
|
163
|
+
t,
|
|
164
|
+
message,
|
|
165
|
+
modal,
|
|
166
|
+
wideScreen,
|
|
167
|
+
toggleWideScreen,
|
|
168
|
+
showViewModeSwitch,
|
|
169
|
+
handleDuplicate,
|
|
170
|
+
handleExportMarkdown,
|
|
171
|
+
]);
|
|
175
172
|
|
|
176
173
|
return { menuItems };
|
|
177
174
|
};
|
|
@@ -15,6 +15,7 @@ import { HotkeyEnum } from '@/types/hotkey';
|
|
|
15
15
|
export const TOGGLE_BUTTON_ID = 'toggle_right_panel_button';
|
|
16
16
|
|
|
17
17
|
interface ToggleRightPanelButtonProps {
|
|
18
|
+
hideWhenExpanded?: boolean;
|
|
18
19
|
icon?: ActionIconProps['icon'];
|
|
19
20
|
showActive?: boolean;
|
|
20
21
|
size?: ActionIconProps['size'];
|
|
@@ -22,7 +23,7 @@ interface ToggleRightPanelButtonProps {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const ToggleRightPanelButton = memo<ToggleRightPanelButtonProps>(
|
|
25
|
-
({ title, showActive, icon, size }) => {
|
|
26
|
+
({ title, showActive, icon, hideWhenExpanded, size }) => {
|
|
26
27
|
const [expand, togglePanel] = useGlobalStore((s) => [
|
|
27
28
|
systemStatusSelectors.showRightPanel(s),
|
|
28
29
|
s.toggleRightPanel,
|
|
@@ -31,6 +32,7 @@ const ToggleRightPanelButton = memo<ToggleRightPanelButtonProps>(
|
|
|
31
32
|
|
|
32
33
|
const { t } = useTranslation(['chat', 'hotkey']);
|
|
33
34
|
|
|
35
|
+
if (hideWhenExpanded && expand) return null;
|
|
34
36
|
return (
|
|
35
37
|
<ActionIcon
|
|
36
38
|
active={showActive ? expand : undefined}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { parse } from 'partial-json';
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
import { stringify } from 'yaml';
|
|
3
4
|
|
|
4
5
|
export const useYamlArguments = (args?: string) => {
|
|
5
|
-
|
|
6
|
+
return useMemo(() => {
|
|
7
|
+
if (!args) return '';
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
try {
|
|
10
|
+
const obj = parse(args);
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
if (Object.keys(obj).length === 0) return '';
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
return stringify(obj);
|
|
15
|
+
} catch {
|
|
16
|
+
return args;
|
|
17
|
+
}
|
|
18
|
+
}, [args]);
|
|
16
19
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class LRUMap<K, V> {
|
|
2
|
+
private map = new Map<K, V>();
|
|
3
|
+
private limit: number;
|
|
4
|
+
|
|
5
|
+
constructor(limit = 0) {
|
|
6
|
+
this.limit = limit;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get size() {
|
|
10
|
+
return this.map.size;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get(key: K) {
|
|
14
|
+
return this.map.get(key);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set(key: K, value: V) {
|
|
18
|
+
if (!this.map.has(key) && this.limit > 0 && this.map.size >= this.limit) {
|
|
19
|
+
const oldest = this.map.keys().next().value as K | undefined;
|
|
20
|
+
|
|
21
|
+
if (oldest !== undefined) this.map.delete(oldest);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.map.set(key, value);
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
delete(key: K) {
|
|
29
|
+
const value = this.map.get(key);
|
|
30
|
+
this.map.delete(key);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
clear() {
|
|
35
|
+
this.map.clear();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { LRUMap };
|
|
40
|
+
export default { LRUMap };
|
package/vitest.config.mts
CHANGED
|
@@ -56,6 +56,7 @@ export default defineConfig({
|
|
|
56
56
|
'@/const': resolve(__dirname, './packages/const/src'),
|
|
57
57
|
'@': resolve(__dirname, './src'),
|
|
58
58
|
'~test-utils': resolve(__dirname, './tests/utils.tsx'),
|
|
59
|
+
'lru_map': resolve(__dirname, './tests/mocks/lru_map'),
|
|
59
60
|
/* eslint-enable */
|
|
60
61
|
},
|
|
61
62
|
coverage: {
|
|
@@ -94,7 +95,14 @@ export default defineConfig({
|
|
|
94
95
|
globals: true,
|
|
95
96
|
server: {
|
|
96
97
|
deps: {
|
|
97
|
-
inline: [
|
|
98
|
+
inline: [
|
|
99
|
+
'vitest-canvas-mock',
|
|
100
|
+
'@lobehub/ui',
|
|
101
|
+
'@lobehub/fluent-emoji',
|
|
102
|
+
'@pierre/diffs',
|
|
103
|
+
'@pierre/diffs/react',
|
|
104
|
+
'lru_map',
|
|
105
|
+
],
|
|
98
106
|
},
|
|
99
107
|
},
|
|
100
108
|
setupFiles: join(__dirname, './tests/setup.ts'),
|