@lobehub/chat 0.157.1 → 0.158.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 (115) hide show
  1. package/.eslintignore +1 -2
  2. package/CHANGELOG.md +50 -0
  3. package/README.md +14 -14
  4. package/README.zh-CN.md +14 -14
  5. package/locales/ar/auth.json +2 -0
  6. package/locales/ar/chat.json +1 -0
  7. package/locales/ar/common.json +1 -0
  8. package/locales/bg-BG/auth.json +2 -0
  9. package/locales/bg-BG/chat.json +1 -0
  10. package/locales/bg-BG/common.json +1 -0
  11. package/locales/bg-BG/error.json +3 -3
  12. package/locales/de-DE/auth.json +2 -0
  13. package/locales/de-DE/chat.json +1 -0
  14. package/locales/de-DE/common.json +1 -0
  15. package/locales/en-US/auth.json +2 -0
  16. package/locales/en-US/chat.json +1 -0
  17. package/locales/en-US/common.json +1 -0
  18. package/locales/es-ES/auth.json +2 -0
  19. package/locales/es-ES/chat.json +1 -0
  20. package/locales/es-ES/common.json +1 -0
  21. package/locales/fr-FR/auth.json +2 -0
  22. package/locales/fr-FR/chat.json +1 -0
  23. package/locales/fr-FR/common.json +1 -0
  24. package/locales/it-IT/auth.json +2 -0
  25. package/locales/it-IT/chat.json +1 -0
  26. package/locales/it-IT/common.json +1 -0
  27. package/locales/ja-JP/auth.json +2 -0
  28. package/locales/ja-JP/chat.json +1 -0
  29. package/locales/ja-JP/common.json +1 -0
  30. package/locales/ko-KR/auth.json +2 -0
  31. package/locales/ko-KR/chat.json +1 -0
  32. package/locales/ko-KR/common.json +1 -0
  33. package/locales/nl-NL/auth.json +2 -0
  34. package/locales/nl-NL/chat.json +1 -0
  35. package/locales/nl-NL/common.json +50 -49
  36. package/locales/pl-PL/auth.json +2 -0
  37. package/locales/pl-PL/chat.json +1 -0
  38. package/locales/pl-PL/common.json +1 -0
  39. package/locales/pl-PL/error.json +3 -3
  40. package/locales/pt-BR/auth.json +2 -0
  41. package/locales/pt-BR/chat.json +1 -0
  42. package/locales/pt-BR/common.json +1 -0
  43. package/locales/ru-RU/auth.json +2 -0
  44. package/locales/ru-RU/chat.json +1 -0
  45. package/locales/ru-RU/common.json +1 -0
  46. package/locales/ru-RU/error.json +3 -3
  47. package/locales/tr-TR/auth.json +2 -0
  48. package/locales/tr-TR/chat.json +1 -0
  49. package/locales/tr-TR/common.json +1 -0
  50. package/locales/vi-VN/auth.json +2 -0
  51. package/locales/vi-VN/chat.json +1 -0
  52. package/locales/vi-VN/common.json +1 -0
  53. package/locales/zh-CN/auth.json +2 -0
  54. package/locales/zh-CN/chat.json +1 -0
  55. package/locales/zh-CN/common.json +1 -0
  56. package/locales/zh-TW/auth.json +2 -0
  57. package/locales/zh-TW/chat.json +1 -0
  58. package/locales/zh-TW/common.json +1 -0
  59. package/package.json +2 -2
  60. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +80 -0
  61. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +116 -0
  62. package/src/app/(main)/(mobile)/me/(home)/features/Category.tsx +15 -0
  63. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +37 -0
  64. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +95 -0
  65. package/src/app/(main)/(mobile)/me/{page.tsx → (home)/page.tsx} +6 -10
  66. package/src/app/(main)/(mobile)/me/data/features/Category.tsx +48 -0
  67. package/src/app/(main)/(mobile)/me/data/features/Header.tsx +33 -0
  68. package/src/app/(main)/(mobile)/me/data/layout.tsx +13 -0
  69. package/src/app/(main)/(mobile)/me/data/loading.tsx +5 -0
  70. package/src/app/(main)/(mobile)/me/data/page.tsx +17 -0
  71. package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +45 -0
  72. package/src/app/(main)/(mobile)/me/profile/features/Header.tsx +33 -0
  73. package/src/app/(main)/(mobile)/me/profile/layout.tsx +16 -0
  74. package/src/app/(main)/(mobile)/me/profile/loading.tsx +5 -0
  75. package/src/app/(main)/(mobile)/me/profile/page.tsx +17 -0
  76. package/src/app/(main)/(mobile)/me/settings/features/Category.tsx +15 -0
  77. package/src/app/(main)/(mobile)/me/settings/features/Header.tsx +33 -0
  78. package/src/app/(main)/(mobile)/me/settings/features/useCategory.tsx +57 -0
  79. package/src/app/(main)/(mobile)/me/settings/layout.tsx +13 -0
  80. package/src/app/(main)/(mobile)/me/settings/loading.tsx +5 -0
  81. package/src/app/(main)/(mobile)/me/settings/page.tsx +17 -0
  82. package/src/app/(main)/_layout/Mobile.tsx +5 -4
  83. package/src/app/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +21 -1
  84. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +74 -0
  85. package/src/app/(main)/profile/[[...slugs]]/page.tsx +18 -0
  86. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +26 -0
  87. package/src/app/(main)/profile/_layout/Mobile/index.tsx +16 -0
  88. package/src/app/(main)/profile/layout.tsx +20 -0
  89. package/src/app/(main)/profile/loading.tsx +23 -0
  90. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +2 -1
  91. package/src/app/(main)/settings/hooks/useCategory.tsx +7 -13
  92. package/src/app/@modal/layout.tsx +3 -0
  93. package/src/components/Cell/Divider.tsx +3 -2
  94. package/src/components/Cell/index.tsx +28 -18
  95. package/src/features/User/DataStatistics.tsx +3 -1
  96. package/src/features/User/UserLoginOrSignup.tsx +2 -2
  97. package/src/features/User/UserPanel/PanelContent.tsx +9 -3
  98. package/src/features/User/UserPanel/useMenu.tsx +29 -29
  99. package/src/features/User/__tests__/PanelContent.test.tsx +7 -0
  100. package/src/features/User/__tests__/useMenu.test.tsx +142 -0
  101. package/src/layout/AuthProvider/Clerk/useAppearance.ts +5 -4
  102. package/src/libs/agent-runtime/azureOpenai/index.test.ts +161 -27
  103. package/src/libs/agent-runtime/utils/streams/openai.ts +4 -0
  104. package/src/locales/default/auth.ts +2 -0
  105. package/src/locales/default/chat.ts +1 -0
  106. package/src/locales/default/common.ts +1 -0
  107. package/src/store/user/slices/auth/selectors.ts +2 -1
  108. package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +0 -13
  109. package/src/app/(auth)/profile/[[...slugs]]/page.tsx +0 -14
  110. package/src/app/(main)/(mobile)/me/features/Cate.tsx +0 -33
  111. package/src/app/(main)/(mobile)/me/features/ExtraCate.tsx +0 -24
  112. package/src/app/(main)/(mobile)/me/features/useExtraCate.tsx +0 -62
  113. /package/src/app/(main)/(mobile)/me/{features → (home)/features}/Header.tsx +0 -0
  114. /package/src/app/(main)/(mobile)/me/{layout.tsx → (home)/layout.tsx} +0 -0
  115. /package/src/app/(main)/(mobile)/me/{loading.tsx → (home)/loading.tsx} +0 -0
@@ -1,5 +1,6 @@
1
1
  // @vitest-environment node
2
2
  import { AzureKeyCredential, OpenAIClient } from '@azure/openai';
3
+ import OpenAI from 'openai';
3
4
  import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
5
 
5
6
  import * as debugStreamModule from '../utils/debugStream';
@@ -50,7 +51,7 @@ describe('LobeAzureOpenAI', () => {
50
51
  });
51
52
 
52
53
  describe('chat', () => {
53
- it('should return a StreamingTextResponse on successful API call', async () => {
54
+ it('should return a Response on successful API call', async () => {
54
55
  // Arrange
55
56
  const mockStream = new ReadableStream();
56
57
  const mockResponse = Promise.resolve(mockStream);
@@ -68,6 +69,140 @@ describe('LobeAzureOpenAI', () => {
68
69
  expect(result).toBeInstanceOf(Response);
69
70
  });
70
71
 
72
+ describe('streaming response', () => {
73
+ it('should handle multiple data chunks correctly', async () => {
74
+ const data = [
75
+ {
76
+ choices: [],
77
+ created: 0,
78
+ id: '',
79
+ model: '',
80
+ object: '',
81
+ prompt_filter_results: [
82
+ {
83
+ prompt_index: 0,
84
+ content_filter_results: {
85
+ hate: { filtered: false, severity: 'safe' },
86
+ self_harm: { filtered: false, severity: 'safe' },
87
+ sexual: { filtered: false, severity: 'safe' },
88
+ violence: { filtered: false, severity: 'safe' },
89
+ },
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ choices: [
95
+ {
96
+ content_filter_results: {
97
+ hate: { filtered: false, severity: 'safe' },
98
+ self_harm: { filtered: false, severity: 'safe' },
99
+ sexual: { filtered: false, severity: 'safe' },
100
+ violence: { filtered: false, severity: 'safe' },
101
+ },
102
+ delta: { content: '你' },
103
+ finish_reason: null,
104
+ index: 0,
105
+ logprobs: null,
106
+ },
107
+ ],
108
+ created: 1715516381,
109
+ id: 'chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
110
+ model: 'gpt-35-turbo-16k',
111
+ object: 'chat.completion.chunk',
112
+ system_fingerprint: null,
113
+ },
114
+ {
115
+ choices: [
116
+ {
117
+ content_filter_results: {
118
+ hate: { filtered: false, severity: 'safe' },
119
+ self_harm: { filtered: false, severity: 'safe' },
120
+ sexual: { filtered: false, severity: 'safe' },
121
+ violence: { filtered: false, severity: 'safe' },
122
+ },
123
+ delta: { content: '好' },
124
+ finish_reason: null,
125
+ index: 0,
126
+ logprobs: null,
127
+ },
128
+ ],
129
+ created: 1715516381,
130
+ id: 'chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
131
+ model: 'gpt-35-turbo-16k',
132
+ object: 'chat.completion.chunk',
133
+ system_fingerprint: null,
134
+ },
135
+ {
136
+ choices: [
137
+ {
138
+ content_filter_results: {
139
+ hate: { filtered: false, severity: 'safe' },
140
+ self_harm: { filtered: false, severity: 'safe' },
141
+ sexual: { filtered: false, severity: 'safe' },
142
+ violence: { filtered: false, severity: 'safe' },
143
+ },
144
+ delta: { content: '!' },
145
+ finish_reason: null,
146
+ index: 0,
147
+ logprobs: null,
148
+ },
149
+ ],
150
+ created: 1715516381,
151
+ id: 'chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
152
+ model: 'gpt-35-turbo-16k',
153
+ object: 'chat.completion.chunk',
154
+ system_fingerprint: null,
155
+ },
156
+ ];
157
+
158
+ const mockStream = new ReadableStream({
159
+ start(controller) {
160
+ data.forEach((chunk) => controller.enqueue(chunk));
161
+ controller.close();
162
+ },
163
+ });
164
+ vi.spyOn(instance['client'], 'streamChatCompletions').mockResolvedValue(mockStream as any);
165
+
166
+ const result = await instance.chat({
167
+ stream: true,
168
+ max_tokens: 2048,
169
+ temperature: 0.6,
170
+ top_p: 1,
171
+ model: 'gpt-35-turbo-16k',
172
+ presence_penalty: 0,
173
+ frequency_penalty: 0,
174
+ messages: [{ role: 'user', content: '你好' }],
175
+ });
176
+
177
+ const decoder = new TextDecoder();
178
+ const reader = result.body!.getReader();
179
+ const stream: string[] = [];
180
+
181
+ while (true) {
182
+ const { value, done } = await reader.read();
183
+ if (done) break;
184
+ stream.push(decoder.decode(value));
185
+ }
186
+
187
+ expect(stream).toEqual(
188
+ [
189
+ 'id: ',
190
+ 'event: data',
191
+ 'data: {"choices":[],"created":0,"id":"","model":"","object":"","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}]}\n',
192
+ 'id: chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
193
+ 'event: text',
194
+ 'data: "你"\n',
195
+ 'id: chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
196
+ 'event: text',
197
+ 'data: "好"\n',
198
+ 'id: chatcmpl-9O2SzeGv5xy6yz0TcQNA1DHHLJ8N1',
199
+ 'event: text',
200
+ 'data: "!"\n',
201
+ ].map((item) => `${item}\n`),
202
+ );
203
+ });
204
+ });
205
+
71
206
  describe('Error', () => {
72
207
  it('should return AzureBizError with DeploymentNotFound error', async () => {
73
208
  // Arrange
@@ -165,7 +300,6 @@ describe('LobeAzureOpenAI', () => {
165
300
  });
166
301
 
167
302
  describe('private method', () => {
168
-
169
303
  describe('tocamelCase', () => {
170
304
  it('should convert string to camel case', () => {
171
305
  const key = 'image_url';
@@ -179,41 +313,41 @@ describe('LobeAzureOpenAI', () => {
179
313
  describe('camelCaseKeys', () => {
180
314
  it('should convert object keys to camel case', () => {
181
315
  const obj = {
182
- "frequency_penalty": 0,
183
- "messages": [
316
+ frequency_penalty: 0,
317
+ messages: [
184
318
  {
185
- "role": "user",
186
- "content": [
319
+ role: 'user',
320
+ content: [
187
321
  {
188
- "type": "image_url",
189
- "image_url": {
190
- "url": "<image URL>"
191
- }
192
- }
193
- ]
194
- }
195
- ]
322
+ type: 'image_url',
323
+ image_url: {
324
+ url: '<image URL>',
325
+ },
326
+ },
327
+ ],
328
+ },
329
+ ],
196
330
  };
197
331
 
198
332
  const newObj = instance['camelCaseKeys'](obj);
199
333
 
200
334
  expect(newObj).toEqual({
201
- "frequencyPenalty": 0,
202
- "messages": [
335
+ frequencyPenalty: 0,
336
+ messages: [
203
337
  {
204
- "role": "user",
205
- "content": [
338
+ role: 'user',
339
+ content: [
206
340
  {
207
- "type": "image_url",
208
- "imageUrl": {
209
- "url": "<image URL>"
210
- }
211
- }
212
- ]
213
- }
214
- ]
341
+ type: 'image_url',
342
+ imageUrl: {
343
+ url: '<image URL>',
344
+ },
345
+ },
346
+ ],
347
+ },
348
+ ],
215
349
  });
216
350
  });
217
351
  });
218
- })
352
+ });
219
353
  });
@@ -14,7 +14,11 @@ import {
14
14
 
15
15
  export const transformOpenAIStream = (chunk: OpenAI.ChatCompletionChunk): StreamProtocolChunk => {
16
16
  // maybe need another structure to add support for multiple choices
17
+
17
18
  const item = chunk.choices[0];
19
+ if (!item) {
20
+ return { data: chunk, id: chunk.id, type: 'data' };
21
+ }
18
22
 
19
23
  if (typeof item.delta?.content === 'string') {
20
24
  return { data: item.delta.content, id: chunk.id, type: 'text' };
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  login: '登录',
3
3
  loginOrSignup: '登录 / 注册',
4
+ profile: '个人资料',
5
+ security: '安全',
4
6
  signout: '退出登录',
5
7
  signup: '注册',
6
8
  };
@@ -5,6 +5,7 @@ export default {
5
5
  agentDefaultMessage:
6
6
  '你好,我是 **{{name}}**,你可以立即与我开始对话,也可以前往 [助手设置](/chat/settings#session={{id}}) 完善我的信息。',
7
7
  agentDefaultMessageWithSystemRole: '你好,我是 **{{name}}**,{{systemRole}},让我们开始对话吧!',
8
+ agentsAndConversations: '助手与会话',
8
9
  backToBottom: '跳转至当前',
9
10
  clearCurrentMessages: '清空当前会话消息',
10
11
  confirmClearCurrentMessages: '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
@@ -163,6 +163,7 @@ export default {
163
163
  userPanel: {
164
164
  anonymousNickName: '匿名用户',
165
165
  billing: '账单管理',
166
+ data: '数据存储',
166
167
  defaultNickname: '社区版用户',
167
168
  discord: '社区支持',
168
169
  docs: '使用文档',
@@ -1,6 +1,6 @@
1
1
  import { t } from 'i18next';
2
2
 
3
- import { enableAuth } from '@/const/auth';
3
+ import { enableAuth, enableClerk } from '@/const/auth';
4
4
  import { UserStore } from '@/store/user';
5
5
  import { LobeUser } from '@/types/user';
6
6
 
@@ -43,4 +43,5 @@ const isLogin = (s: UserStore) => {
43
43
  export const authSelectors = {
44
44
  isLogin,
45
45
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
46
+ isLoginWithClerk: (s: UserStore) => s.isSignedIn && enableClerk,
46
47
  };
@@ -1,13 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
-
6
- import PageTitle from '@/components/PageTitle';
7
-
8
- const Title = memo(() => {
9
- const { t } = useTranslation('auth');
10
-
11
- return <PageTitle title={t('signup')} />;
12
- });
13
- export default Title;
@@ -1,14 +0,0 @@
1
- import { UserProfile } from '@clerk/nextjs';
2
-
3
- import PageTitle from './PageTitle';
4
-
5
- const Page = () => {
6
- return (
7
- <>
8
- <PageTitle />
9
- <UserProfile />
10
- </>
11
- );
12
- };
13
-
14
- export default Page;
@@ -1,33 +0,0 @@
1
- 'use client';
2
-
3
- import { useRouter } from 'next/navigation';
4
- import { memo } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
6
- import urlJoin from 'url-join';
7
-
8
- import { useCategory } from '@/app/(main)/settings/hooks/useCategory';
9
- import Cell from '@/components/Cell';
10
- import Divider from '@/components/Cell/Divider';
11
-
12
- const SettingCate = memo(() => {
13
- const settingItems = useCategory({ mobile: true });
14
- const router = useRouter();
15
-
16
- return (
17
- <Flexbox width={'100%'}>
18
- {settingItems?.map(({ key, icon, label, type }: any, index) => {
19
- if (type === 'divider') return <Divider key={index} />;
20
- return (
21
- <Cell
22
- icon={icon}
23
- key={key}
24
- label={label}
25
- onClick={() => router.push(urlJoin('/settings', key))}
26
- />
27
- );
28
- })}
29
- </Flexbox>
30
- );
31
- });
32
-
33
- export default SettingCate;
@@ -1,24 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import Cell from '@/components/Cell';
7
- import Divider from '@/components/Cell/Divider';
8
-
9
- import { useExtraCate } from './useExtraCate';
10
-
11
- const ExtraCate = memo(() => {
12
- const mainItems = useExtraCate();
13
-
14
- return (
15
- <Flexbox width={'100%'}>
16
- {mainItems?.map(({ key, icon, label, type, onClick }: any, index) => {
17
- if (type === 'divider') return <Divider key={index} />;
18
- return <Cell icon={icon} key={key} label={label} onClick={onClick} />;
19
- })}
20
- </Flexbox>
21
- );
22
- });
23
-
24
- export default ExtraCate;
@@ -1,62 +0,0 @@
1
- import { DiscordIcon, Icon } from '@lobehub/ui';
2
- import { Book, Feather, HardDriveDownload, HardDriveUpload } from 'lucide-react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import { type MenuProps } from '@/components/Menu';
6
- import { DISCORD, DOCUMENTS, FEEDBACK } from '@/const/url';
7
- import DataImporter from '@/features/DataImporter';
8
- import { configService } from '@/services/config';
9
-
10
- export const useExtraCate = () => {
11
- const { t } = useTranslation(['common', 'setting']);
12
-
13
- const iconSize = { fontSize: 20 };
14
-
15
- const exports: MenuProps['items'] = [
16
- {
17
- icon: <Icon icon={HardDriveUpload} size={iconSize} />,
18
- key: 'import',
19
- label: <DataImporter>{t('import')}</DataImporter>,
20
- },
21
- {
22
- icon: <Icon icon={HardDriveDownload} size={iconSize} />,
23
- key: 'export',
24
- label: t('export'),
25
- onClick: configService.exportAll,
26
- },
27
- {
28
- type: 'divider',
29
- },
30
- ];
31
-
32
- const helps: MenuProps['items'] = [
33
- {
34
- icon: <Icon icon={Book} size={iconSize} />,
35
- key: 'docs',
36
- label: t('document'),
37
- onClick: () => window.open(DOCUMENTS, '__blank'),
38
- },
39
- {
40
- icon: <Icon icon={Feather} size={iconSize} />,
41
- key: 'feedback',
42
- label: t('feedback'),
43
- onClick: () => window.open(FEEDBACK, '__blank'),
44
- },
45
- {
46
- icon: <Icon icon={DiscordIcon} size={iconSize} />,
47
- key: 'discord',
48
- label: 'Discord',
49
- onClick: () => window.open(DISCORD, '__blank'),
50
- },
51
- ];
52
-
53
- const mainItems = [
54
- {
55
- type: 'divider',
56
- },
57
- ...exports,
58
- ...helps,
59
- ].filter(Boolean) as MenuProps['items'];
60
-
61
- return mainItems;
62
- };