@lobehub/chat 1.27.3 → 1.28.1

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 (81) 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/database/server/migrations/0010_add_accessed_at_and_clean_tables.sql +26 -0
  41. package/src/database/server/migrations/meta/0010_snapshot.json +3184 -0
  42. package/src/database/server/migrations/meta/_journal.json +7 -0
  43. package/src/database/server/models/__tests__/session.test.ts +0 -2
  44. package/src/database/server/models/__tests__/topic.test.ts +2 -0
  45. package/src/database/server/schemas/lobechat/_helpers.ts +8 -0
  46. package/src/database/server/schemas/lobechat/agent.ts +6 -7
  47. package/src/database/server/schemas/lobechat/asyncTask.ts +2 -3
  48. package/src/database/server/schemas/lobechat/file.ts +5 -5
  49. package/src/database/server/schemas/lobechat/index.ts +0 -1
  50. package/src/database/server/schemas/lobechat/message.ts +2 -3
  51. package/src/database/server/schemas/lobechat/rag.ts +4 -6
  52. package/src/database/server/schemas/lobechat/ragEvals.ts +5 -7
  53. package/src/database/server/schemas/lobechat/relations.ts +0 -33
  54. package/src/database/server/schemas/lobechat/session.ts +3 -5
  55. package/src/database/server/schemas/lobechat/topic.ts +7 -8
  56. package/src/database/server/schemas/lobechat/user.ts +3 -5
  57. package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/Preview.tsx +5 -3
  58. package/src/features/ShareModal/ShareImage/index.tsx +103 -0
  59. package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/style.ts +1 -23
  60. package/src/features/ShareModal/ShareJSON/Preview.tsx +18 -0
  61. package/src/features/ShareModal/ShareJSON/generateMessages.test.ts +135 -0
  62. package/src/features/ShareModal/ShareJSON/generateMessages.ts +35 -0
  63. package/src/features/ShareModal/ShareJSON/index.tsx +99 -0
  64. package/src/features/ShareModal/ShareJSON/type.ts +4 -0
  65. package/src/features/ShareModal/ShareText/Preview.tsx +16 -0
  66. package/src/features/ShareModal/ShareText/index.tsx +119 -0
  67. package/src/features/ShareModal/ShareText/template.test.ts +178 -0
  68. package/src/features/ShareModal/ShareText/template.ts +79 -0
  69. package/src/features/ShareModal/ShareText/type.ts +6 -0
  70. package/src/features/ShareModal/index.tsx +69 -0
  71. package/src/features/ShareModal/style.ts +30 -0
  72. package/src/locales/default/chat.ts +7 -1
  73. package/src/services/__tests__/share.test.ts +35 -105
  74. package/src/services/share.ts +0 -30
  75. package/src/store/chat/slices/share/action.test.ts +7 -198
  76. package/src/store/chat/slices/share/action.ts +9 -113
  77. package/src/utils/client/exportFile.ts +20 -0
  78. package/src/app/(main)/chat/(workspace)/features/ShareButton/ShareModal.tsx +0 -164
  79. package/src/database/server/schemas/lobechat/discover.ts +0 -84
  80. /package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/type.ts +0 -0
  81. /package/src/{app/(main)/chat/(workspace)/features/ShareButton → features/ShareModal/ShareImage}/useScreenshot.ts +0 -0
@@ -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
  });
@@ -1,40 +1,10 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { LOBE_URL_IMPORT_NAME } from '@/const/url';
4
- import { ShareGPTConversation } from '@/types/share';
5
4
  import { UserSettings } from '@/types/user/settings';
6
5
  import { withBasePath } from '@/utils/basePath';
7
- import { parseMarkdown } from '@/utils/parseMarkdown';
8
-
9
- export const SHARE_GPT_URL = 'https://sharegpt.com/api/conversations';
10
6
 
11
7
  class ShareService {
12
- public async createShareGPTUrl(conversation: ShareGPTConversation) {
13
- const items = [];
14
-
15
- for (const item of conversation.items) {
16
- items.push({
17
- from: item.from,
18
- value: item.from === 'gpt' ? await parseMarkdown(item.value) : item.value,
19
- });
20
- }
21
-
22
- const res = await fetch(SHARE_GPT_URL, {
23
- body: JSON.stringify({ ...conversation, items }),
24
- headers: {
25
- 'Content-Type': 'application/json',
26
- },
27
- method: 'POST',
28
- });
29
-
30
- const { id } = await res.json();
31
-
32
- if (!id) throw new Error('Failed to create ShareGPT URL');
33
-
34
- // short link to the ShareGPT post
35
- return `https://shareg.pt/${id}`;
36
- }
37
-
38
8
  /**
39
9
  * Creates a share settings URL with the provided settings.
40
10
  * @param settings - The settings object to be encoded in the URL.
@@ -6,208 +6,17 @@ import { useChatStore } from '@/store/chat';
6
6
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
7
7
  import { ChatMessage } from '@/types/message';
8
8
 
9
- describe('shareSlice actions', () => {
10
- let shareServiceSpy: any;
11
- let windowOpenSpy;
12
-
13
- beforeEach(() => {
14
- shareServiceSpy = vi.spyOn(shareService, 'createShareGPTUrl').mockResolvedValue('test-url');
15
- windowOpenSpy = vi.spyOn(window, 'open');
16
- });
17
-
18
- afterEach(() => {
19
- vi.restoreAllMocks();
20
- });
21
-
22
- describe('shareToShareGPT', () => {
23
- it('should share to ShareGPT and open a new window', async () => {
24
- const { result } = renderHook(() => useChatStore());
25
- const shareServiceSpy = vi.spyOn(shareService, 'createShareGPTUrl');
26
- const windowOpenSpy = vi.spyOn(window, 'open');
27
- const avatar = 'avatar-url';
28
- const withPluginInfo = true;
29
- const withSystemRole = true;
30
-
31
- await act(async () => {
32
- await result.current.shareToShareGPT({ avatar, withPluginInfo, withSystemRole });
33
- });
34
-
35
- expect(shareServiceSpy).toHaveBeenCalled();
36
- expect(windowOpenSpy).toHaveBeenCalled();
37
- });
38
- it('should handle messages from different roles correctly', async () => {
39
- // 注意:此处需要你根据实际情况模拟 chatSelectors.currentChats 和 agentSelectors 返回的数据
40
- // 此外,你可能需要调整 useChatStore 的引用路径
41
- const { result } = renderHook(() => useChatStore());
42
- await act(async () => {
43
- await result.current.shareToShareGPT({
44
- withPluginInfo: true,
45
- withSystemRole: true,
46
- });
47
- });
48
- // 根据你的逻辑添加对应的expect断言
49
- });
50
-
51
- it('should not include system role information when withSystemRole is false or systemRole is undefined', async () => {
52
- // 模拟不同的配置以测试这个行为
53
- // 你可能需要调整 useChatStore 的引用路径
54
- const { result } = renderHook(() => useChatStore());
55
- await act(async () => {
56
- await result.current.shareToShareGPT({
57
- withSystemRole: false,
58
- });
59
- });
60
- // 根据你的逻辑添加对应的expect断言
61
- });
62
-
63
- it('should use default avatar URL when avatar is not provided', async () => {
64
- const { result } = renderHook(() => useChatStore());
65
- await act(async () => {
66
- await result.current.shareToShareGPT({});
67
- });
68
-
69
- expect(shareServiceSpy).toHaveBeenCalledWith(
70
- expect.objectContaining({
71
- avatarUrl: DEFAULT_USER_AVATAR_URL,
72
- }),
73
- );
74
- });
75
-
76
- it('should set shareLoading to true before sharing and to false after sharing', async () => {
77
- const { result } = renderHook(() => useChatStore());
78
- expect(result.current.shareLoading).toBe(false);
79
- await act(async () => {
80
- await result.current.shareToShareGPT({});
81
- });
82
- expect(result.current.shareLoading).toBe(false);
83
- // 注意:这里的验证可能需要你根据实际的状态管理逻辑进行调整
84
- });
85
-
86
- it('should include plugin information when withPluginInfo is true', async () => {
87
- // 模拟带有插件信息的消息
88
- const pluginMessage = {
89
- role: 'tool',
90
- content: 'plugin content',
91
- plugin: {
92
- type: 'default',
93
- arguments: '{}',
94
- apiName: 'test-api',
95
- identifier: 'test-identifier',
96
- },
97
- id: 'abc',
98
- } as ChatMessage;
99
-
100
- act(() => {
101
- useChatStore.setState({
102
- messagesMap: {
103
- [messageMapKey('abc')]: [pluginMessage],
104
- },
105
- activeId: 'abc',
106
- });
107
- });
108
-
109
- const { result } = renderHook(() => useChatStore());
110
- await act(async () => {
111
- result.current.shareToShareGPT({ withPluginInfo: true });
112
- });
113
- expect(shareServiceSpy).toHaveBeenCalledWith(
114
- expect.objectContaining({
115
- items: expect.arrayContaining([
116
- expect.objectContaining({
117
- from: 'gpt',
118
- value: expect.stringContaining('Function Calling Plugin'),
119
- }),
120
- ]),
121
- }),
122
- );
123
- });
124
-
125
- it('should not include plugin information when withPluginInfo is false', async () => {
126
- const pluginMessage = {
127
- role: 'tool',
128
- content: 'plugin content',
129
- plugin: {
130
- type: 'default',
131
- arguments: '{}',
132
- apiName: 'test-api',
133
- identifier: 'test-identifier',
134
- },
135
- id: 'abc',
136
- } as ChatMessage;
137
-
138
- act(() => {
139
- useChatStore.setState({
140
- messagesMap: {
141
- [messageMapKey('abc')]: [pluginMessage],
142
- },
143
- activeId: 'abc',
144
- });
145
- });
146
-
147
- const { result } = renderHook(() => useChatStore());
148
- await act(async () => {
149
- result.current.shareToShareGPT({ withPluginInfo: false });
150
- });
151
- expect(shareServiceSpy).toHaveBeenCalledWith(
152
- expect.objectContaining({
153
- items: expect.not.arrayContaining([
154
- expect.objectContaining({
155
- from: 'gpt',
156
- value: expect.stringContaining('Function Calling Plugin'),
157
- }),
158
- ]),
159
- }),
160
- );
161
- });
162
-
163
- it('should handle messages from different roles correctly', async () => {
164
- const messages = [
165
- { role: 'user', content: 'user message', id: '1' },
166
- { role: 'assistant', content: 'assistant message', id: '2' },
167
- {
168
- role: 'tool',
169
- content: 'plugin content',
170
- plugin: {
171
- type: 'default',
172
- arguments: '{}',
173
- apiName: 'test-api',
174
- identifier: 'test-identifier',
175
- },
176
- id: '3',
177
- },
178
- ] as ChatMessage[];
179
-
180
- act(() => {
181
- useChatStore.setState({
182
- messagesMap: {
183
- [messageMapKey('abc')]: messages,
184
- },
185
- activeId: 'abc',
186
- });
187
- });
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ });
188
12
 
13
+ describe('shareSlice actions', () => {
14
+ describe('genShareUrl', () => {
15
+ it('TODO', async () => {
189
16
  const { result } = renderHook(() => useChatStore());
190
17
  await act(async () => {
191
- await result.current.shareToShareGPT({
192
- withPluginInfo: true,
193
- withSystemRole: true,
194
- });
18
+ await result.current.genShareUrl();
195
19
  });
196
-
197
- expect(shareServiceSpy).toHaveBeenCalledWith(
198
- expect.objectContaining({
199
- items: [
200
- expect.objectContaining({ from: 'gpt' }), // Agent meta info
201
- expect.objectContaining({ from: 'human', value: 'user message' }),
202
- expect.objectContaining({ from: 'gpt', value: 'assistant message' }),
203
- expect.objectContaining({
204
- from: 'gpt',
205
- value: expect.stringContaining('Function Calling Plugin'),
206
- }),
207
- expect.objectContaining({ from: 'gpt', value: expect.stringContaining('Share from') }), // Footer
208
- ],
209
- }),
210
- );
211
20
  });
212
21
  });
213
22
  });
@@ -1,122 +1,18 @@
1
- import dayjs from 'dayjs';
2
- import { produce } from 'immer';
3
1
  import { StateCreator } from 'zustand/vanilla';
4
2
 
5
- import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
6
- import { shareService } from '@/services/share';
7
- import { useAgentStore } from '@/store/agent';
8
- import { agentSelectors } from '@/store/agent/selectors';
9
- import { useSessionStore } from '@/store/session';
10
- import { sessionMetaSelectors } from '@/store/session/selectors';
11
- import { ShareGPTConversation } from '@/types/share';
12
-
13
- import { chatSelectors } from '../../selectors';
14
3
  import { ChatStore } from '../../store';
15
4
 
16
- interface ShareMessage {
17
- from: 'human' | 'gpt';
18
- value: string;
19
- }
20
-
21
- const Footer: ShareMessage = {
22
- from: 'gpt',
23
- value: `Share from [**🤯 LobeChat**](https://github.com/lobehub/lobe-chat) - ${dayjs().format(
24
- 'YYYY-MM-DD',
25
- )}`,
26
- };
27
-
28
- const PLUGIN_INFO = (plugin: {
29
- apiName: string;
30
- content: string;
31
- identifier: string;
32
- }): ShareMessage => ({
33
- from: 'gpt',
34
- value: [
35
- `**🧩 Function Calling Plugin**`,
36
- `- Identifier: \`${plugin.identifier}\``,
37
- `- API name: \`${plugin.apiName}\``,
38
- `- Result:`,
39
- ``,
40
- '```json',
41
- plugin.content,
42
- '```',
43
- ].join('\n'),
44
- });
45
-
46
- // const t = setNamespace('chat/share');
47
5
  export interface ShareAction {
48
- shareToShareGPT: (props: {
49
- avatar?: string;
50
- withPluginInfo?: boolean;
51
- withSystemRole?: boolean;
52
- }) => Promise<void>;
6
+ genShareUrl: () => Promise<string>;
53
7
  }
54
8
 
55
- export const chatShare: StateCreator<ChatStore, [['zustand/devtools', never]], [], ShareAction> = (
56
- set,
57
- get,
58
- ) => ({
59
- shareToShareGPT: async ({ withSystemRole, withPluginInfo, avatar }) => {
60
- const messages = chatSelectors.currentChats(get());
61
- const config = agentSelectors.currentAgentConfig(useAgentStore.getState());
62
- const meta = sessionMetaSelectors.currentAgentMeta(useSessionStore.getState());
63
-
64
- const defaultMsg: ShareGPTConversation['items'] = [];
65
- const showSystemRole = withSystemRole && !!config.systemRole;
66
- const shareMsgs = produce(defaultMsg, (draft) => {
67
- draft.push({
68
- from: 'gpt',
69
- value: [
70
- `${meta.avatar} **${meta.title}** - ${meta.description}`,
71
- showSystemRole && '---',
72
- showSystemRole && config.systemRole,
73
- ]
74
- .filter(Boolean)
75
- .join('\n\n'),
76
- });
77
-
78
- for (const i of messages) {
79
- switch (i.role) {
80
- case 'assistant': {
81
- draft.push({ from: 'gpt', value: i.content });
82
- break;
83
- }
84
- case 'tool': {
85
- if (withPluginInfo)
86
- draft.push(
87
- PLUGIN_INFO({
88
- apiName: i.plugin?.apiName || 'undefined',
89
- content: i.content,
90
- identifier: i.plugin?.identifier || 'undefined',
91
- }),
92
- );
93
- break;
94
- }
95
- case 'user': {
96
- draft.push({ from: 'human', value: i.content });
97
- break;
98
- }
99
- }
100
- }
101
-
102
- draft.push(Footer);
103
- });
104
-
105
- set({ shareLoading: true });
106
-
107
- const res = await shareService.createShareGPTUrl({
108
- avatarUrl: avatar || DEFAULT_USER_AVATAR_URL,
109
- items: shareMsgs,
110
- });
111
- set({ shareLoading: false });
112
-
113
- window.open(res, '_blank');
9
+ export const chatShare: StateCreator<
10
+ ChatStore,
11
+ [['zustand/devtools', never]],
12
+ [],
13
+ ShareAction
14
+ > = () => ({
15
+ genShareUrl: () => {
16
+ return Promise.resolve('TODO');
114
17
  },
115
- // genShareUrl: () => {
116
- // const session = sessionSelectors.currentSession(get());
117
- // if (!session) return '';
118
- //
119
- // const agent = session.config;
120
- // return genShareMessagesUrl(session.chats, agent.systemRole);
121
- // },
122
18
  });
@@ -0,0 +1,20 @@
1
+ export const exportFile = (content: string, filename?: string) => {
2
+ // 创建一个 Blob 对象
3
+ const blob = new Blob([content], { type: 'plain/text' });
4
+
5
+ // 创建一个 URL 对象,用于下载
6
+ const url = URL.createObjectURL(blob);
7
+
8
+ // 创建一个 <a> 元素,设置下载链接和文件名
9
+ const a = document.createElement('a');
10
+ a.href = url;
11
+ a.download = filename || 'file.txt';
12
+
13
+ // 触发 <a> 元素的点击事件,开始下载
14
+ document.body.append(a);
15
+ a.click();
16
+
17
+ // 下载完成后,清除 URL 对象
18
+ URL.revokeObjectURL(url);
19
+ a.remove();
20
+ };