@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
@@ -100,14 +100,20 @@
100
100
  "tooLong": "分组名称长度需在 1-20 之内"
101
101
  },
102
102
  "shareModal": {
103
+ "copy": "复制",
103
104
  "download": "下载截图",
105
+ "downloadFile": "下载文件",
106
+ "exportTitle": "默认标题",
104
107
  "imageType": "图片格式",
108
+ "includeTool": "包含插件消息",
109
+ "includeUser": "包含用户消息",
105
110
  "screenshot": "截图",
106
111
  "settings": "导出设置",
107
- "shareToShareGPT": "生成 ShareGPT 分享链接",
112
+ "text": "文本",
108
113
  "withBackground": "包含背景图片",
109
114
  "withFooter": "包含页脚",
110
115
  "withPluginInfo": "包含插件信息",
116
+ "withRole": "包含消息角色",
111
117
  "withSystemRole": "包含助手角色设定"
112
118
  },
113
119
  "stt": {
@@ -320,6 +320,9 @@
320
320
  "claude-2.1": {
321
321
  "description": "Claude 2 为企业提供了关键能力的进步,包括业界领先的 200K token 上下文、大幅降低模型幻觉的发生率、系统提示以及一个新的测试功能:工具调用。"
322
322
  },
323
+ "claude-3-5-haiku-20241022": {
324
+ "description": "Claude 3.5 Haiku 是 Anthropic 最快的下一代模型。与 Claude 3 Haiku 相比,Claude 3.5 Haiku 在各项技能上都有所提升,并在许多智力基准测试中超越了上一代最大的模型 Claude 3 Opus。"
325
+ },
323
326
  "claude-3-5-sonnet-20240620": {
324
327
  "description": "Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。"
325
328
  },
@@ -100,14 +100,20 @@
100
100
  "tooLong": "分組名稱長度需在 1-20 之內"
101
101
  },
102
102
  "shareModal": {
103
+ "copy": "複製",
103
104
  "download": "下載截圖",
105
+ "downloadFile": "下載檔案",
106
+ "exportTitle": "預設標題",
104
107
  "imageType": "圖片格式",
108
+ "includeTool": "包含插件訊息",
109
+ "includeUser": "包含使用者訊息",
105
110
  "screenshot": "截圖",
106
111
  "settings": "導出設置",
107
- "shareToShareGPT": "生成 ShareGPT 分享鏈接",
112
+ "text": "文本",
108
113
  "withBackground": "包含背景圖片",
109
114
  "withFooter": "包含頁腳",
110
115
  "withPluginInfo": "包含插件信息",
116
+ "withRole": "包含訊息角色",
111
117
  "withSystemRole": "包含助手角色設定"
112
118
  },
113
119
  "stt": {
@@ -320,6 +320,9 @@
320
320
  "claude-2.1": {
321
321
  "description": "Claude 2 為企業提供了關鍵能力的進步,包括業界領先的 200K token 上下文、大幅降低模型幻覺的發生率、系統提示以及一個新的測試功能:工具調用。"
322
322
  },
323
+ "claude-3-5-haiku-20241022": {
324
+ "description": "Claude 3.5 Haiku 是 Anthropic 最快的下一代模型。與 Claude 3 Haiku 相比,Claude 3.5 Haiku 在各項技能上都有所提升,並在許多智力基準測試中超越了上一代最大的模型 Claude 3 Opus。"
325
+ },
323
326
  "claude-3-5-sonnet-20240620": {
324
327
  "description": "Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同時保持與 Sonnet 相同的價格。Sonnet 特別擅長編程、數據科學、視覺處理、代理任務。"
325
328
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.27.2",
3
+ "version": "1.28.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -11,7 +11,8 @@ import { useChatStore } from '@/store/chat';
11
11
 
12
12
  import { useWorkspaceModal } from '../useWorkspaceModal';
13
13
 
14
- const ShareModal = dynamic(() => import('./ShareModal'));
14
+ const ShareModal = dynamic(() => import('@/features/ShareModal'));
15
+
15
16
  interface ShareButtonProps {
16
17
  mobile?: boolean;
17
18
  open?: boolean;
@@ -3,6 +3,23 @@ import { ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://docs.anthropic.com/en/docs/about-claude/models#model-names
4
4
  const Anthropic: ModelProviderCard = {
5
5
  chatModels: [
6
+ {
7
+ description:
8
+ 'Claude 3.5 Haiku 是 Anthropic 最快的下一代模型。与 Claude 3 Haiku 相比,Claude 3.5 Haiku 在各项技能上都有所提升,并在许多智力基准测试中超越了上一代最大的模型 Claude 3 Opus。',
9
+ displayName: 'Claude 3.5 Haiku',
10
+ enabled: true,
11
+ functionCall: true,
12
+ id: 'claude-3-5-haiku-20241022',
13
+ maxOutput: 8192,
14
+ pricing: {
15
+ cachedInput: 0.1,
16
+ input: 1,
17
+ output: 5,
18
+ writeCacheInput: 1.25,
19
+ },
20
+ releasedAt: '2024-11-05',
21
+ tokens: 200_000,
22
+ },
6
23
  {
7
24
  description:
8
25
  'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
@@ -42,7 +59,6 @@ const Anthropic: ModelProviderCard = {
42
59
  description:
43
60
  'Claude 3 Haiku 是 Anthropic 的最快且最紧凑的模型,旨在实现近乎即时的响应。它具有快速且准确的定向性能。',
44
61
  displayName: 'Claude 3 Haiku',
45
- enabled: true,
46
62
  functionCall: true,
47
63
  id: 'claude-3-haiku-20240307',
48
64
  maxOutput: 4096,
@@ -58,7 +74,6 @@ const Anthropic: ModelProviderCard = {
58
74
  description:
59
75
  'Claude 3 Sonnet 在智能和速度方面为企业工作负载提供了理想的平衡。它以更低的价格提供最大效用,可靠且适合大规模部署。',
60
76
  displayName: 'Claude 3 Sonnet',
61
- enabled: true,
62
77
  functionCall: true,
63
78
  id: 'claude-3-sonnet-20240229',
64
79
  maxOutput: 4096,
@@ -4,7 +4,7 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
- import pkg from '@/../package.json';
7
+ import PluginTag from '@/app/(main)/chat/(workspace)/features/PluginTag';
8
8
  import { ProductLogo } from '@/components/Branding';
9
9
  import ChatList from '@/features/Conversation/components/ChatList';
10
10
  import { useAgentStore } from '@/store/agent';
@@ -12,7 +12,8 @@ import { agentSelectors } from '@/store/agent/selectors';
12
12
  import { useSessionStore } from '@/store/session';
13
13
  import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selectors';
14
14
 
15
- import PluginTag from '../PluginTag';
15
+ import pkg from '../../../../package.json';
16
+ import { useContainerStyles } from '../style';
16
17
  import { useStyles } from './style';
17
18
  import { FieldType } from './type';
18
19
 
@@ -32,12 +33,13 @@ const Preview = memo<FieldType & { title?: string }>(
32
33
 
33
34
  const { t } = useTranslation('chat');
34
35
  const { styles } = useStyles(withBackground);
36
+ const { styles: containerStyles } = useContainerStyles();
35
37
 
36
38
  const displayTitle = isInbox ? t('inbox.title') : title;
37
39
  const displayDesc = isInbox ? t('inbox.desc') : description;
38
40
 
39
41
  return (
40
- <div className={styles.preview}>
42
+ <div className={containerStyles.preview}>
41
43
  <div className={withBackground ? styles.background : undefined} id={'preview'}>
42
44
  <Flexbox className={styles.container} gap={16}>
43
45
  <div className={styles.header}>
@@ -0,0 +1,103 @@
1
+ import { Form, type FormItemProps } from '@lobehub/ui';
2
+ import { Button, Segmented, SegmentedProps, Switch } from 'antd';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { FORM_STYLE } from '@/const/layoutTokens';
8
+ import { useIsMobile } from '@/hooks/useIsMobile';
9
+
10
+ import Preview from './Preview';
11
+ import { FieldType, ImageType } from './type';
12
+ import { useScreenshot } from './useScreenshot';
13
+
14
+ export const imageTypeOptions: SegmentedProps['options'] = [
15
+ {
16
+ label: 'JPG',
17
+ value: ImageType.JPG,
18
+ },
19
+ {
20
+ label: 'PNG',
21
+ value: ImageType.PNG,
22
+ },
23
+ {
24
+ label: 'SVG',
25
+ value: ImageType.SVG,
26
+ },
27
+ {
28
+ label: 'WEBP',
29
+ value: ImageType.WEBP,
30
+ },
31
+ ];
32
+
33
+ const DEFAULT_FIELD_VALUE: FieldType = {
34
+ imageType: ImageType.JPG,
35
+ withBackground: true,
36
+ withFooter: true,
37
+ withPluginInfo: false,
38
+ withSystemRole: false,
39
+ };
40
+
41
+ const ShareImage = memo(() => {
42
+ const [fieldValue, setFieldValue] = useState<FieldType>(DEFAULT_FIELD_VALUE);
43
+ const { t } = useTranslation('chat');
44
+ const { loading, onDownload, title } = useScreenshot(fieldValue.imageType);
45
+
46
+ const settings: FormItemProps[] = [
47
+ {
48
+ children: <Switch />,
49
+ label: t('shareModal.withSystemRole'),
50
+ minWidth: undefined,
51
+ name: 'withSystemRole',
52
+ valuePropName: 'checked',
53
+ },
54
+ {
55
+ children: <Switch />,
56
+ label: t('shareModal.withBackground'),
57
+ minWidth: undefined,
58
+ name: 'withBackground',
59
+ valuePropName: 'checked',
60
+ },
61
+ {
62
+ children: <Switch />,
63
+ label: t('shareModal.withFooter'),
64
+ minWidth: undefined,
65
+ name: 'withFooter',
66
+ valuePropName: 'checked',
67
+ },
68
+ {
69
+ children: <Segmented options={imageTypeOptions} />,
70
+ label: t('shareModal.imageType'),
71
+ minWidth: undefined,
72
+ name: 'imageType',
73
+ },
74
+ ];
75
+
76
+ const isMobile = useIsMobile();
77
+ return (
78
+ <Flexbox gap={16} horizontal={!isMobile}>
79
+ <Preview title={title} {...fieldValue} />
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
+ />
88
+ <Button
89
+ block
90
+ loading={loading}
91
+ onClick={onDownload}
92
+ size={'large'}
93
+ style={isMobile ? { bottom: 0, position: 'sticky' } : undefined}
94
+ type={'primary'}
95
+ >
96
+ {t('shareModal.download')}
97
+ </Button>
98
+ </Flexbox>
99
+ </Flexbox>
100
+ );
101
+ });
102
+
103
+ export default ShareImage;
@@ -2,7 +2,7 @@ import { createStyles } from 'antd-style';
2
2
 
3
3
  import { imageUrl } from '@/const/url';
4
4
 
5
- export const useStyles = createStyles(({ css, token, stylish, cx }, withBackground: boolean) => ({
5
+ export const useStyles = createStyles(({ css, token, cx }, withBackground: boolean) => ({
6
6
  background: css`
7
7
  padding: 24px;
8
8
 
@@ -33,28 +33,6 @@ export const useStyles = createStyles(({ css, token, stylish, cx }, withBackgrou
33
33
  background: ${token.colorBgContainer};
34
34
  border-block-end: 1px solid ${token.colorBorder};
35
35
  `,
36
- preview: cx(
37
- stylish.noScrollbar,
38
- css`
39
- overflow: hidden scroll;
40
-
41
- width: 100%;
42
- max-height: 40dvh;
43
-
44
- background: ${token.colorBgLayout};
45
- border: 1px solid ${token.colorBorder};
46
- border-radius: ${token.borderRadiusLG}px;
47
-
48
- * {
49
- pointer-events: none;
50
-
51
- ::-webkit-scrollbar {
52
- width: 0 !important;
53
- height: 0 !important;
54
- }
55
- }
56
- `,
57
- ),
58
36
  role: css`
59
37
  margin-block-start: 12px;
60
38
  padding-block-start: 12px;
@@ -0,0 +1,18 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+
4
+ import { useContainerStyles } from '../style';
5
+
6
+ const Preview = memo<{ content: string }>(({ content }) => {
7
+ const { styles } = useContainerStyles();
8
+
9
+ return (
10
+ <div className={styles.preview}>
11
+ <Highlighter language={'json'} wrap>
12
+ {content}
13
+ </Highlighter>
14
+ </div>
15
+ );
16
+ });
17
+
18
+ export default Preview;
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { LOADING_FLAT } from '@/const/message';
4
+ import { ChatMessage } from '@/types/message';
5
+
6
+ import { generateMessages } from './generateMessages';
7
+
8
+ describe('generateMessages', () => {
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: 'Tool response',
32
+ role: 'tool',
33
+ createdAt: Date.now(),
34
+ tool_call_id: 'tool1',
35
+ },
36
+ ] as ChatMessage[];
37
+
38
+ it('should filter out loading messages', () => {
39
+ const result = generateMessages({
40
+ messages: mockMessages,
41
+ withSystemRole: false,
42
+ includeTool: false,
43
+ systemRole: '',
44
+ });
45
+
46
+ expect(result).toHaveLength(2);
47
+ expect(result.some((m) => m.content === LOADING_FLAT)).toBeFalsy();
48
+ });
49
+
50
+ it('should include system role when withSystemRole is true and systemRole is provided', () => {
51
+ const systemRole = 'I am a helpful assistant';
52
+ const result = generateMessages({
53
+ messages: mockMessages,
54
+ withSystemRole: true,
55
+ includeTool: false,
56
+ systemRole,
57
+ });
58
+
59
+ expect(result[0]).toEqual({
60
+ content: systemRole,
61
+ role: 'system',
62
+ });
63
+ expect(result).toHaveLength(3); // system role + 2 messages
64
+ });
65
+
66
+ it('should not include system role when withSystemRole is false', () => {
67
+ const systemRole = 'I am a helpful assistant';
68
+ const result = generateMessages({
69
+ messages: mockMessages,
70
+ withSystemRole: false,
71
+ includeTool: false,
72
+ systemRole,
73
+ });
74
+
75
+ expect(result[0].role).not.toBe('system');
76
+ expect(result).toHaveLength(2);
77
+ });
78
+
79
+ it('should include tool messages when includeTool is true', () => {
80
+ const result = generateMessages({
81
+ messages: mockMessages,
82
+ withSystemRole: false,
83
+ includeTool: true,
84
+ systemRole: '',
85
+ });
86
+
87
+ expect(result).toHaveLength(3);
88
+ expect(result.some((m) => m.role === 'tool')).toBeTruthy();
89
+ expect((result.find((m) => m.role === 'tool')! as any).tool_call_id).toBeDefined();
90
+ });
91
+
92
+ it('should exclude tool messages when includeTool is false', () => {
93
+ const result = generateMessages({
94
+ messages: mockMessages,
95
+ withSystemRole: false,
96
+ includeTool: false,
97
+ systemRole: '',
98
+ });
99
+
100
+ expect(result).toHaveLength(2);
101
+ expect(result.some((m) => m.role === 'tool')).toBeFalsy();
102
+ });
103
+
104
+ it('should trim message content', () => {
105
+ const messagesWithSpaces = [
106
+ {
107
+ id: '1',
108
+ content: ' Hello ',
109
+ role: 'user',
110
+ createdAt: Date.now(),
111
+ },
112
+ ] as ChatMessage[];
113
+
114
+ const result = generateMessages({
115
+ messages: messagesWithSpaces,
116
+ withSystemRole: false,
117
+ includeTool: false,
118
+ systemRole: '',
119
+ });
120
+
121
+ expect(result[0].content).toBe('Hello');
122
+ });
123
+
124
+ it('should not include system role when systemRole is empty', () => {
125
+ const result = generateMessages({
126
+ messages: mockMessages,
127
+ withSystemRole: true,
128
+ includeTool: false,
129
+ systemRole: '',
130
+ });
131
+
132
+ expect(result).toHaveLength(2);
133
+ expect(result[0].role).not.toBe('system');
134
+ });
135
+ });
@@ -0,0 +1,35 @@
1
+ import { LOADING_FLAT } from '@/const/message';
2
+ import { ChatMessage } from '@/types/message';
3
+
4
+ import { FieldType } from './type';
5
+
6
+ interface JSONParams extends FieldType {
7
+ messages: ChatMessage[];
8
+ systemRole: string;
9
+ }
10
+ export const generateMessages = ({
11
+ messages,
12
+ withSystemRole,
13
+ includeTool,
14
+ systemRole,
15
+ }: JSONParams) => {
16
+ const defaultMessages = messages
17
+ .filter((m) => m.content !== LOADING_FLAT)
18
+ .filter((m) => (!includeTool ? m.role !== 'tool' : true))
19
+ .map((m) => ({
20
+ content: m.content.trim(),
21
+ role: m.role,
22
+ tool_call_id: includeTool && m.tool_call_id ? m.tool_call_id : undefined,
23
+ tools: includeTool && m.tools ? m.tools : undefined,
24
+ }));
25
+
26
+ return withSystemRole && !!systemRole
27
+ ? [
28
+ {
29
+ content: systemRole,
30
+ role: 'system',
31
+ },
32
+ ...defaultMessages,
33
+ ]
34
+ : defaultMessages;
35
+ };
@@ -0,0 +1,99 @@
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 { generateMessages } from './generateMessages';
19
+ import { FieldType } from './type';
20
+
21
+ const DEFAULT_FIELD_VALUE: FieldType = {
22
+ includeTool: true,
23
+ withSystemRole: true,
24
+ };
25
+
26
+ const ShareImage = memo(() => {
27
+ const [fieldValue, setFieldValue] = useState(DEFAULT_FIELD_VALUE);
28
+ const { t } = useTranslation(['chat', 'common']);
29
+ const { message } = App.useApp();
30
+
31
+ const settings: FormItemProps[] = [
32
+ {
33
+ children: <Switch />,
34
+ label: t('shareModal.withSystemRole'),
35
+ minWidth: undefined,
36
+ name: 'withSystemRole',
37
+ valuePropName: 'checked',
38
+ },
39
+ {
40
+ children: <Switch />,
41
+ label: t('shareModal.includeTool'),
42
+ minWidth: undefined,
43
+ name: 'includeTool',
44
+ valuePropName: 'checked',
45
+ },
46
+ ];
47
+
48
+ const systemRole = useAgentStore(agentSelectors.currentAgentSystemRole);
49
+ const messages = useChatStore(chatSelectors.currentChats, isEqual);
50
+ const data = generateMessages({ ...fieldValue, messages, systemRole });
51
+ const content = JSON.stringify(data, null, 2);
52
+
53
+ const topic = useChatStore(topicSelectors.currentActiveTopic, isEqual);
54
+ const title = topic?.title || t('shareModal.exportTitle');
55
+
56
+ const isMobile = useIsMobile();
57
+ return (
58
+ <Flexbox gap={16} horizontal={!isMobile}>
59
+ <Preview content={content} />
60
+ <Flexbox gap={16}>
61
+ <Form
62
+ initialValues={DEFAULT_FIELD_VALUE}
63
+ items={settings}
64
+ itemsType={'flat'}
65
+ onValuesChange={(_, v) => setFieldValue(v)}
66
+ {...FORM_STYLE}
67
+ itemMinWidth={320}
68
+ />
69
+ <Button
70
+ block
71
+ icon={<Icon icon={CopyIcon} />}
72
+ onClick={async () => {
73
+ await copyToClipboard(content);
74
+ message.success(t('copySuccess', { defaultValue: 'Copy Success', ns: 'common' }));
75
+ }}
76
+ size={'large'}
77
+ style={isMobile ? { bottom: 0, position: 'sticky' } : undefined}
78
+ type={'primary'}
79
+ >
80
+ {t('copy', { ns: 'common' })}
81
+ </Button>
82
+ {!isMobile && (
83
+ <Button
84
+ block
85
+ onClick={() => {
86
+ exportFile(content, `${title}.json`);
87
+ }}
88
+ size={'large'}
89
+ variant={'filled'}
90
+ >
91
+ {t('shareModal.downloadFile')}
92
+ </Button>
93
+ )}
94
+ </Flexbox>
95
+ </Flexbox>
96
+ );
97
+ });
98
+
99
+ export default ShareImage;
@@ -0,0 +1,4 @@
1
+ export interface FieldType {
2
+ includeTool: boolean;
3
+ withSystemRole: boolean;
4
+ }
@@ -0,0 +1,16 @@
1
+ import { Markdown } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+
4
+ import { useContainerStyles } from '../style';
5
+
6
+ const Preview = memo<{ content: string }>(({ content }) => {
7
+ const { styles } = useContainerStyles();
8
+
9
+ return (
10
+ <div className={styles.preview} style={{ padding: 12 }}>
11
+ <Markdown>{content}</Markdown>
12
+ </div>
13
+ );
14
+ });
15
+
16
+ export default Preview;