@lobehub/chat 1.27.2 → 1.28.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/locales/ar/chat.json +7 -1
  3. package/locales/ar/models.json +3 -0
  4. package/locales/bg-BG/chat.json +7 -1
  5. package/locales/bg-BG/models.json +3 -0
  6. package/locales/de-DE/chat.json +7 -1
  7. package/locales/de-DE/models.json +3 -0
  8. package/locales/en-US/chat.json +7 -1
  9. package/locales/en-US/models.json +3 -0
  10. package/locales/es-ES/chat.json +7 -1
  11. package/locales/es-ES/models.json +3 -0
  12. package/locales/fa-IR/chat.json +7 -1
  13. package/locales/fa-IR/models.json +3 -0
  14. package/locales/fr-FR/chat.json +7 -1
  15. package/locales/fr-FR/models.json +3 -0
  16. package/locales/it-IT/chat.json +7 -1
  17. package/locales/it-IT/models.json +3 -0
  18. package/locales/ja-JP/chat.json +7 -1
  19. package/locales/ja-JP/models.json +3 -0
  20. package/locales/ko-KR/chat.json +7 -1
  21. package/locales/ko-KR/models.json +3 -0
  22. package/locales/nl-NL/chat.json +7 -1
  23. package/locales/nl-NL/models.json +3 -0
  24. package/locales/pl-PL/chat.json +7 -1
  25. package/locales/pl-PL/models.json +3 -0
  26. package/locales/pt-BR/chat.json +7 -1
  27. package/locales/pt-BR/models.json +3 -0
  28. package/locales/ru-RU/chat.json +7 -1
  29. package/locales/ru-RU/models.json +3 -0
  30. package/locales/tr-TR/chat.json +7 -1
  31. package/locales/tr-TR/models.json +3 -0
  32. package/locales/vi-VN/chat.json +7 -1
  33. package/locales/vi-VN/models.json +3 -0
  34. package/locales/zh-CN/chat.json +7 -1
  35. package/locales/zh-CN/models.json +3 -0
  36. package/locales/zh-TW/chat.json +7 -1
  37. package/locales/zh-TW/models.json +3 -0
  38. package/package.json +1 -1
  39. package/src/app/(main)/chat/(workspace)/features/ShareButton/index.tsx +2 -1
  40. package/src/config/modelProviders/anthropic.ts +17 -2
  41. package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/Preview.tsx +5 -3
  42. package/src/features/ShareModal/ShareImage/index.tsx +103 -0
  43. package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/style.ts +1 -23
  44. package/src/features/ShareModal/ShareJSON/Preview.tsx +18 -0
  45. package/src/features/ShareModal/ShareJSON/generateMessages.test.ts +135 -0
  46. package/src/features/ShareModal/ShareJSON/generateMessages.ts +35 -0
  47. package/src/features/ShareModal/ShareJSON/index.tsx +99 -0
  48. package/src/features/ShareModal/ShareJSON/type.ts +4 -0
  49. package/src/features/ShareModal/ShareText/Preview.tsx +16 -0
  50. package/src/features/ShareModal/ShareText/index.tsx +119 -0
  51. package/src/features/ShareModal/ShareText/template.test.ts +178 -0
  52. package/src/features/ShareModal/ShareText/template.ts +79 -0
  53. package/src/features/ShareModal/ShareText/type.ts +6 -0
  54. package/src/features/ShareModal/index.tsx +69 -0
  55. package/src/features/ShareModal/style.ts +30 -0
  56. package/src/locales/default/chat.ts +7 -1
  57. package/src/services/__tests__/share.test.ts +35 -105
  58. package/src/services/share.ts +0 -30
  59. package/src/store/chat/slices/share/action.test.ts +7 -198
  60. package/src/store/chat/slices/share/action.ts +9 -113
  61. package/src/utils/client/exportFile.ts +20 -0
  62. package/src/app/(main)/chat/(workspace)/features/ShareButton/ShareModal.tsx +0 -164
  63. /package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/type.ts +0 -0
  64. /package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/useScreenshot.ts +0 -0
@@ -0,0 +1,119 @@
1
+ import { Form, type FormItemProps, Icon, copyToClipboard } from '@lobehub/ui';
2
+ import { App, Button, Switch } from 'antd';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { CopyIcon } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { FORM_STYLE } from '@/const/layoutTokens';
10
+ import { useIsMobile } from '@/hooks/useIsMobile';
11
+ import { useAgentStore } from '@/store/agent';
12
+ import { agentSelectors } from '@/store/agent/selectors';
13
+ import { useChatStore } from '@/store/chat';
14
+ import { chatSelectors, topicSelectors } from '@/store/chat/selectors';
15
+ import { exportFile } from '@/utils/client/exportFile';
16
+
17
+ import Preview from './Preview';
18
+ import { generateMarkdown } from './template';
19
+ import { FieldType } from './type';
20
+
21
+ const DEFAULT_FIELD_VALUE: FieldType = {
22
+ includeTool: true,
23
+ includeUser: true,
24
+ withRole: true,
25
+ withSystemRole: false,
26
+ };
27
+
28
+ const ShareText = memo(() => {
29
+ const [fieldValue, setFieldValue] = useState(DEFAULT_FIELD_VALUE);
30
+ const { t } = useTranslation(['chat', 'common']);
31
+
32
+ const { message } = App.useApp();
33
+ const settings: FormItemProps[] = [
34
+ {
35
+ children: <Switch />,
36
+ label: t('shareModal.withSystemRole'),
37
+ minWidth: undefined,
38
+ name: 'withSystemRole',
39
+ valuePropName: 'checked',
40
+ },
41
+ {
42
+ children: <Switch />,
43
+ label: t('shareModal.withRole'),
44
+ minWidth: undefined,
45
+ name: 'withRole',
46
+ valuePropName: 'checked',
47
+ },
48
+ {
49
+ children: <Switch />,
50
+ label: t('shareModal.includeUser'),
51
+ minWidth: undefined,
52
+ name: 'includeUser',
53
+ valuePropName: 'checked',
54
+ },
55
+ {
56
+ children: <Switch />,
57
+ label: t('shareModal.includeTool'),
58
+ minWidth: undefined,
59
+ name: 'includeTool',
60
+ valuePropName: 'checked',
61
+ },
62
+ ];
63
+
64
+ const [systemRole] = useAgentStore((s) => [agentSelectors.currentAgentSystemRole(s)]);
65
+ const messages = useChatStore(chatSelectors.currentChats, isEqual);
66
+ const topic = useChatStore(topicSelectors.currentActiveTopic, isEqual);
67
+
68
+ const title = topic?.title || t('shareModal.exportTitle');
69
+ const content = generateMarkdown({
70
+ ...fieldValue,
71
+ messages,
72
+ systemRole,
73
+ title,
74
+ }).replaceAll('\n\n\n', '\n');
75
+
76
+ const isMobile = useIsMobile();
77
+ return (
78
+ <Flexbox gap={16} horizontal={!isMobile}>
79
+ <Preview content={content} />
80
+ <Flexbox gap={16}>
81
+ <Form
82
+ initialValues={DEFAULT_FIELD_VALUE}
83
+ items={settings}
84
+ itemsType={'flat'}
85
+ onValuesChange={(_, v) => setFieldValue(v)}
86
+ {...FORM_STYLE}
87
+ itemMinWidth={320}
88
+ />
89
+ <Button
90
+ block
91
+ icon={<Icon icon={CopyIcon} />}
92
+ onClick={async () => {
93
+ await copyToClipboard(content);
94
+ message.success(t('copySuccess', { defaultValue: 'Copy Success', ns: 'common' }));
95
+ }}
96
+ size={'large'}
97
+ style={isMobile ? { bottom: 0, position: 'sticky' } : undefined}
98
+ type={'primary'}
99
+ >
100
+ {t('copy', { ns: 'common' })}
101
+ </Button>
102
+ {!isMobile && (
103
+ <Button
104
+ block
105
+ onClick={() => {
106
+ exportFile(content, `${title}.md`);
107
+ }}
108
+ size={'large'}
109
+ variant={'filled'}
110
+ >
111
+ {t('shareModal.downloadFile')}
112
+ </Button>
113
+ )}
114
+ </Flexbox>
115
+ </Flexbox>
116
+ );
117
+ });
118
+
119
+ export default ShareText;
@@ -0,0 +1,178 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { LOADING_FLAT } from '@/const/message';
4
+ import { ChatMessage } from '@/types/message';
5
+
6
+ import { generateMarkdown } from './template';
7
+
8
+ describe('generateMarkdown', () => {
9
+ // 创建测试用的消息数据
10
+ const mockMessages = [
11
+ {
12
+ id: '1',
13
+ content: 'Hello',
14
+ role: 'user',
15
+ createdAt: Date.now(),
16
+ },
17
+ {
18
+ id: '2',
19
+ content: 'Hi there',
20
+ role: 'assistant',
21
+ createdAt: Date.now(),
22
+ },
23
+ {
24
+ id: '3',
25
+ content: LOADING_FLAT,
26
+ role: 'assistant',
27
+ createdAt: Date.now(),
28
+ },
29
+ {
30
+ id: '4',
31
+ content: '{"result": "tool data"}',
32
+ role: 'tool',
33
+ createdAt: Date.now(),
34
+ tool_call_id: 'tool1',
35
+ },
36
+ {
37
+ id: '5',
38
+ content: 'Message with tools',
39
+ role: 'assistant',
40
+ createdAt: Date.now(),
41
+ tools: [{ name: 'calculator', result: '42' }],
42
+ },
43
+ ] as ChatMessage[];
44
+
45
+ const defaultParams = {
46
+ messages: mockMessages,
47
+ title: 'Chat Title',
48
+ includeTool: false,
49
+ includeUser: true,
50
+ withSystemRole: false,
51
+ withRole: false,
52
+ systemRole: '',
53
+ };
54
+
55
+ it('should generate basic markdown with title', () => {
56
+ const result = generateMarkdown(defaultParams);
57
+
58
+ expect(result).toContain('# Chat Title');
59
+ expect(result).toContain('Hello');
60
+ expect(result).toContain('Hi there');
61
+ });
62
+
63
+ it('should include system role when withSystemRole is true', () => {
64
+ const systemRole = 'I am a helpful assistant';
65
+ const result = generateMarkdown({
66
+ ...defaultParams,
67
+ withSystemRole: true,
68
+ systemRole,
69
+ });
70
+
71
+ expect(result).toContain('````md\nI am a helpful assistant\n````');
72
+ });
73
+
74
+ it('should not include system role when withSystemRole is false', () => {
75
+ const systemRole = 'I am a helpful assistant';
76
+ const result = generateMarkdown({
77
+ ...defaultParams,
78
+ withSystemRole: false,
79
+ systemRole,
80
+ });
81
+
82
+ expect(result).not.toContain('```\nI am a helpful assistant\n```');
83
+ });
84
+
85
+ it('should add role labels when withRole is true', () => {
86
+ const result = generateMarkdown({
87
+ ...defaultParams,
88
+ withRole: true,
89
+ });
90
+
91
+ expect(result).toContain('##### User:');
92
+ expect(result).toContain('##### Assistant:');
93
+ });
94
+
95
+ it('should not add role labels when withRole is false', () => {
96
+ const result = generateMarkdown({
97
+ ...defaultParams,
98
+ withRole: false,
99
+ });
100
+
101
+ expect(result).not.toContain('##### User:');
102
+ expect(result).not.toContain('##### Assistant:');
103
+ });
104
+
105
+ it('should include tool messages when includeTool is true', () => {
106
+ const result = generateMarkdown({
107
+ ...defaultParams,
108
+ includeTool: true,
109
+ withRole: true,
110
+ });
111
+
112
+ expect(result).toContain('##### Tools Calling:');
113
+ expect(result).toContain('```json\n{"result": "tool data"}\n```');
114
+ });
115
+
116
+ it('should exclude tool messages when includeTool is false', () => {
117
+ const result = generateMarkdown({
118
+ ...defaultParams,
119
+ includeTool: false,
120
+ });
121
+
122
+ expect(result).not.toContain('{"result": "tool data"}');
123
+ });
124
+
125
+ it('should exclude user messages when includeUser is false', () => {
126
+ const result = generateMarkdown({
127
+ ...defaultParams,
128
+ includeUser: false,
129
+ });
130
+
131
+ expect(result).not.toContain('Hello');
132
+ expect(result).toContain('Hi there');
133
+ });
134
+
135
+ it('should filter out loading messages', () => {
136
+ const result = generateMarkdown(defaultParams);
137
+
138
+ expect(result).not.toContain(LOADING_FLAT);
139
+ });
140
+
141
+ it('should include tools data when includeTool is true', () => {
142
+ const result = generateMarkdown({
143
+ ...defaultParams,
144
+ includeTool: true,
145
+ });
146
+
147
+ expect(result).toContain('"name": "calculator"');
148
+ expect(result).toContain('"result": "42"');
149
+ });
150
+
151
+ it('should handle empty messages array', () => {
152
+ const result = generateMarkdown({
153
+ ...defaultParams,
154
+ messages: [],
155
+ });
156
+
157
+ expect(result).toContain('# Chat Title');
158
+ // Should not throw error and should contain at least the title
159
+ });
160
+
161
+ it('should handle messages with special characters', () => {
162
+ const messagesWithSpecialChars = [
163
+ {
164
+ id: '1',
165
+ content: '**Bold** *Italic* `Code`',
166
+ role: 'user',
167
+ createdAt: Date.now(),
168
+ },
169
+ ] as ChatMessage[];
170
+
171
+ const result = generateMarkdown({
172
+ ...defaultParams,
173
+ messages: messagesWithSpecialChars,
174
+ });
175
+
176
+ expect(result).toContain('**Bold** *Italic* `Code`');
177
+ });
178
+ });
@@ -0,0 +1,79 @@
1
+ import { template } from 'lodash-es';
2
+
3
+ import { LOADING_FLAT } from '@/const/message';
4
+ import { FieldType } from '@/features/ShareModal/ShareText/type';
5
+ import { ChatMessage } from '@/types/message';
6
+
7
+ const markdownTemplate = template(
8
+ `# {{title}}
9
+
10
+ <% if (systemRole) { %>
11
+ \`\`\`\`md
12
+ {{systemRole}}
13
+ \`\`\`\`
14
+ <% } %>
15
+
16
+ <% messages.forEach(function(chat) { %>
17
+
18
+ <% if (withRole) { %>
19
+
20
+ <% if (chat.role === 'user') { %>
21
+ ##### User:
22
+ <% } else if (chat.role === 'assistant') { %>
23
+ ##### Assistant:
24
+ <% } else if (chat.role === 'tool') { %>
25
+ ##### Tools Calling:
26
+ <% } %>
27
+
28
+ <% } %>
29
+
30
+ <% if (chat.role === 'tool') { %>
31
+ \`\`\`json
32
+ {{chat.content}}
33
+ \`\`\`
34
+ <% } else { %>
35
+
36
+ {{chat.content}}
37
+
38
+ <% if (includeTool && chat.tools) { %>
39
+
40
+ \`\`\`json
41
+ {{JSON.stringify(chat.tools, null, 2)}}
42
+ \`\`\`
43
+
44
+ <% } %>
45
+ <% } %>
46
+
47
+ <% }); %>
48
+ `,
49
+ {
50
+ evaluate: /<%([\S\s]+?)%>/g,
51
+ interpolate: /{{([\S\s]+?)}}/g,
52
+ },
53
+ );
54
+
55
+ interface MarkdownParams extends FieldType {
56
+ messages: ChatMessage[];
57
+ systemRole: string;
58
+ title: string;
59
+ }
60
+
61
+ export const generateMarkdown = ({
62
+ messages,
63
+ title,
64
+ includeTool,
65
+ includeUser,
66
+ withSystemRole,
67
+ withRole,
68
+ systemRole,
69
+ }: MarkdownParams) =>
70
+ markdownTemplate({
71
+ includeTool,
72
+ messages: messages
73
+ .filter((m) => m.content !== LOADING_FLAT)
74
+ .filter((m) => (!includeUser ? m.role !== 'user' : true))
75
+ .filter((m) => (!includeTool ? m.role !== 'tool' : true)),
76
+ systemRole: withSystemRole ? systemRole : undefined,
77
+ title,
78
+ withRole,
79
+ });
@@ -0,0 +1,6 @@
1
+ export type FieldType = {
2
+ includeTool: boolean;
3
+ includeUser: boolean;
4
+ withRole: boolean;
5
+ withSystemRole: boolean;
6
+ };
@@ -0,0 +1,69 @@
1
+ import { Modal, type ModalProps } from '@lobehub/ui';
2
+ import { Segmented, SegmentedProps } from 'antd';
3
+ import { memo, useMemo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useIsMobile } from '@/hooks/useIsMobile';
8
+
9
+ import ShareImage from './ShareImage';
10
+ import ShareJSON from './ShareJSON';
11
+ import ShareText from './ShareText';
12
+
13
+ enum Tab {
14
+ JSON = 'json',
15
+ Screenshot = 'screenshot',
16
+ Text = 'text',
17
+ }
18
+
19
+ const ShareModal = memo<ModalProps>(({ onCancel, open }) => {
20
+ const [tab, setTab] = useState<Tab>(Tab.Screenshot);
21
+ const { t } = useTranslation('chat');
22
+
23
+ const options: SegmentedProps['options'] = useMemo(
24
+ () => [
25
+ {
26
+ label: t('shareModal.screenshot'),
27
+ value: Tab.Screenshot,
28
+ },
29
+ {
30
+ label: t('shareModal.text'),
31
+ value: Tab.Text,
32
+ },
33
+ {
34
+ label: 'JSON',
35
+ value: Tab.JSON,
36
+ },
37
+ ],
38
+ [],
39
+ );
40
+
41
+ const isMobile = useIsMobile();
42
+ return (
43
+ <Modal
44
+ allowFullscreen
45
+ centered={false}
46
+ footer={null}
47
+ maxHeight={false}
48
+ onCancel={onCancel}
49
+ open={open}
50
+ title={t('share', { ns: 'common' })}
51
+ width={1440}
52
+ >
53
+ <Flexbox gap={isMobile ? 8 : 24}>
54
+ <Segmented
55
+ block
56
+ onChange={(value) => setTab(value as Tab)}
57
+ options={options}
58
+ style={{ width: '100%' }}
59
+ value={tab}
60
+ />
61
+ {tab === Tab.Screenshot && <ShareImage />}
62
+ {tab === Tab.Text && <ShareText />}
63
+ {tab === Tab.JSON && <ShareJSON />}
64
+ </Flexbox>
65
+ </Modal>
66
+ );
67
+ });
68
+
69
+ export default ShareModal;
@@ -0,0 +1,30 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useContainerStyles = createStyles(({ css, token, stylish, cx, responsive }) => ({
4
+ preview: cx(
5
+ stylish.noScrollbar,
6
+ css`
7
+ overflow: hidden scroll;
8
+
9
+ width: 100%;
10
+ max-height: 70dvh;
11
+
12
+ background: ${token.colorBgLayout};
13
+ border: 1px solid ${token.colorBorder};
14
+ border-radius: ${token.borderRadiusLG}px;
15
+
16
+ * {
17
+ pointer-events: none;
18
+
19
+ ::-webkit-scrollbar {
20
+ width: 0 !important;
21
+ height: 0 !important;
22
+ }
23
+ }
24
+
25
+ ${responsive.mobile} {
26
+ max-height: 40dvh;
27
+ }
28
+ `,
29
+ ),
30
+ }));
@@ -102,14 +102,20 @@ export default {
102
102
  tooLong: '分组名称长度需在 1-20 之内',
103
103
  },
104
104
  shareModal: {
105
+ copy: '复制',
105
106
  download: '下载截图',
107
+ downloadFile: '下载文件',
108
+ exportTitle: '默认标题',
106
109
  imageType: '图片格式',
110
+ includeTool: '包含插件消息',
111
+ includeUser: '包含用户消息',
107
112
  screenshot: '截图',
108
113
  settings: '导出设置',
109
- shareToShareGPT: '生成 ShareGPT 分享链接',
114
+ text: '文本',
110
115
  withBackground: '包含背景图片',
111
116
  withFooter: '包含页脚',
112
117
  withPluginInfo: '包含插件信息',
118
+ withRole: '包含消息角色',
113
119
  withSystemRole: '包含助手角色设定',
114
120
  },
115
121
  stt: {
@@ -2,11 +2,9 @@ import { DeepPartial } from 'utility-types';
2
2
  import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { LOBE_URL_IMPORT_NAME } from '@/const/url';
5
- import { ShareGPTConversation } from '@/types/share';
6
5
  import { UserSettings } from '@/types/user/settings';
7
- import { parseMarkdown } from '@/utils/parseMarkdown';
8
6
 
9
- import { SHARE_GPT_URL, shareService } from '../share';
7
+ import { shareService } from '../share';
10
8
 
11
9
  // Mock dependencies
12
10
  vi.mock('@/utils/parseMarkdown', () => ({
@@ -15,116 +13,48 @@ vi.mock('@/utils/parseMarkdown', () => ({
15
13
 
16
14
  global.fetch = vi.fn();
17
15
 
18
- describe('ShareGPTService', () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- });
22
-
23
- it('should create and return a ShareGPT URL when fetch is successful', async () => {
24
- // Arrange
25
- const mockId = '123abc';
26
- const conversation: ShareGPTConversation = {
27
- items: [
28
- { from: 'human', value: 'Hello' },
29
- { from: 'gpt', value: 'Hi there!' },
30
- ],
31
- };
32
- (parseMarkdown as Mock).mockResolvedValue('Parsed markdown');
33
- (fetch as Mock).mockResolvedValue({
34
- json: () => Promise.resolve({ id: mockId }),
35
- });
36
-
37
- // Act
38
- const url = await shareService.createShareGPTUrl(conversation);
39
-
40
- // Assert
41
- expect(parseMarkdown).toHaveBeenCalledWith('Hi there!');
42
- expect(fetch).toHaveBeenCalledWith(SHARE_GPT_URL, expect.anything());
43
- expect(url).toBe(`https://shareg.pt/${mockId}`);
44
- });
45
-
46
- it('should throw an error when the fetch call fails', async () => {
47
- // Arrange
48
- const conversation: ShareGPTConversation = {
49
- items: [{ from: 'human', value: 'Hello' }],
50
- };
51
- (fetch as Mock).mockRejectedValue(new Error('Network error'));
52
-
53
- // Act & Assert
54
- await expect(shareService.createShareGPTUrl(conversation)).rejects.toThrow('Network error');
55
- });
56
-
57
- it('should not parse markdown for items not from gpt', async () => {
58
- // Arrange
59
- const mockId = '123abc';
60
- const conversation: ShareGPTConversation = {
61
- items: [
62
- { from: 'human', value: 'Hello' },
63
- { from: 'human', value: 'How are you?' },
64
- ],
65
- };
66
- (fetch as Mock).mockResolvedValue({
67
- json: () => Promise.resolve({ id: mockId }),
68
- });
69
-
70
- // Act
71
- await shareService.createShareGPTUrl(conversation);
72
-
73
- // Assert
74
- expect(parseMarkdown).not.toHaveBeenCalled();
75
- });
76
-
77
- it('should throw an error if the response does not contain an id', async () => {
78
- // Arrange
79
- const conversation: ShareGPTConversation = {
80
- items: [{ from: 'human', value: 'Hello' }],
81
- };
82
- (fetch as Mock).mockResolvedValue({
83
- json: () => Promise.resolve({}),
84
- });
85
-
86
- // Act & Assert
87
- await expect(shareService.createShareGPTUrl(conversation)).rejects.toThrow();
88
- });
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
89
18
  });
90
-
91
- describe('ShareViaUrl', () => {
92
- describe('createShareSettingsUrl', () => {
93
- it('should create a share settings URL with the provided settings', () => {
94
- const settings: DeepPartial<UserSettings> = {
95
- keyVaults: {
96
- openai: {
97
- apiKey: 'user-key',
98
- },
99
- },
100
- };
101
- const url = shareService.createShareSettingsUrl(settings);
102
- expect(url).toBe(
103
- `/?${LOBE_URL_IMPORT_NAME}=%7B%22keyVaults%22:%7B%22openai%22:%7B%22apiKey%22:%22user-key%22%7D%7D%7D`,
104
- );
105
- });
106
- });
107
-
108
- describe('decodeShareSettings', () => {
109
- it('should decode share settings from search params', () => {
110
- const settings = '{"languageModel":{"openai":{"apiKey":"user-key"}}}';
111
- const decodedSettings = shareService.decodeShareSettings(settings);
112
- expect(decodedSettings).toEqual({
113
- data: {
114
- languageModel: {
19
+ describe('ShareGPTService', () => {
20
+ describe('ShareViaUrl', () => {
21
+ describe('createShareSettingsUrl', () => {
22
+ it('should create a share settings URL with the provided settings', () => {
23
+ const settings: DeepPartial<UserSettings> = {
24
+ keyVaults: {
115
25
  openai: {
116
26
  apiKey: 'user-key',
117
27
  },
118
28
  },
119
- },
29
+ };
30
+ const url = shareService.createShareSettingsUrl(settings);
31
+ expect(url).toBe(
32
+ `/?${LOBE_URL_IMPORT_NAME}=%7B%22keyVaults%22:%7B%22openai%22:%7B%22apiKey%22:%22user-key%22%7D%7D%7D`,
33
+ );
120
34
  });
121
35
  });
122
36
 
123
- it('should return an error message if decoding fails', () => {
124
- const settings = '%7B%22theme%22%3A%22dark%22%2C%22fontSize%22%3A16%';
125
- const decodedSettings = shareService.decodeShareSettings(settings);
126
- expect(decodedSettings).toEqual({
127
- message: expect.any(String),
37
+ describe('decodeShareSettings', () => {
38
+ it('should decode share settings from search params', () => {
39
+ const settings = '{"languageModel":{"openai":{"apiKey":"user-key"}}}';
40
+ const decodedSettings = shareService.decodeShareSettings(settings);
41
+ expect(decodedSettings).toEqual({
42
+ data: {
43
+ languageModel: {
44
+ openai: {
45
+ apiKey: 'user-key',
46
+ },
47
+ },
48
+ },
49
+ });
50
+ });
51
+
52
+ it('should return an error message if decoding fails', () => {
53
+ const settings = '%7B%22theme%22%3A%22dark%22%2C%22fontSize%22%3A16%';
54
+ const decodedSettings = shareService.decodeShareSettings(settings);
55
+ expect(decodedSettings).toEqual({
56
+ message: expect.any(String),
57
+ });
128
58
  });
129
59
  });
130
60
  });