@lobehub/chat 1.4.0 → 1.4.2
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 +52 -0
- package/docs/self-hosting/advanced/model-list.mdx +1 -1
- package/docs/self-hosting/advanced/model-list.zh-CN.mdx +1 -1
- package/locales/ar/chat.json +2 -0
- package/locales/ar/common.json +9 -0
- package/locales/bg-BG/chat.json +2 -0
- package/locales/bg-BG/common.json +9 -0
- package/locales/de-DE/chat.json +2 -0
- package/locales/de-DE/common.json +9 -0
- package/locales/en-US/chat.json +2 -0
- package/locales/en-US/common.json +11 -2
- package/locales/es-ES/chat.json +2 -0
- package/locales/es-ES/common.json +9 -0
- package/locales/fr-FR/chat.json +2 -0
- package/locales/fr-FR/common.json +9 -0
- package/locales/it-IT/chat.json +2 -0
- package/locales/it-IT/common.json +9 -0
- package/locales/ja-JP/chat.json +2 -0
- package/locales/ja-JP/common.json +9 -0
- package/locales/ko-KR/chat.json +2 -0
- package/locales/ko-KR/common.json +9 -0
- package/locales/nl-NL/chat.json +2 -0
- package/locales/nl-NL/common.json +9 -0
- package/locales/pl-PL/chat.json +2 -0
- package/locales/pl-PL/common.json +9 -0
- package/locales/pt-BR/chat.json +2 -0
- package/locales/pt-BR/common.json +9 -0
- package/locales/ru-RU/chat.json +2 -0
- package/locales/ru-RU/common.json +9 -0
- package/locales/tr-TR/chat.json +2 -0
- package/locales/tr-TR/common.json +9 -0
- package/locales/vi-VN/chat.json +2 -0
- package/locales/vi-VN/common.json +9 -0
- package/locales/vi-VN/setting.json +6 -10
- package/locales/zh-CN/chat.json +2 -0
- package/locales/zh-CN/common.json +9 -0
- package/locales/zh-TW/chat.json +2 -0
- package/locales/zh-TW/common.json +9 -0
- package/next-sitemap.config.mjs +2 -2
- package/package.json +2 -1
- package/src/app/(main)/_layout/Desktop.tsx +19 -12
- package/src/app/(main)/_layout/Mobile.tsx +5 -0
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx +4 -1
- package/src/config/featureFlags/schema.ts +6 -0
- package/src/const/guide.ts +8 -4
- package/src/const/url.ts +2 -1
- package/src/features/AlertBanner/CloudBanner.tsx +91 -0
- package/src/features/ChatInput/ActionBar/Clear.tsx +15 -6
- package/src/features/ChatInput/Topic/index.tsx +44 -9
- package/src/features/User/UserPanel/useMenu.tsx +21 -2
- package/src/libs/agent-runtime/google/index.test.ts +3 -33
- package/src/libs/agent-runtime/google/index.ts +1 -16
- package/src/locales/default/chat.ts +2 -0
- package/src/locales/default/common.ts +10 -0
- package/src/store/serverConfig/selectors.test.ts +1 -0
- package/src/types/user/index.ts +3 -1
- package/src/utils/parseModels.test.ts +3 -3
|
@@ -361,8 +361,14 @@
|
|
|
361
361
|
},
|
|
362
362
|
"desc": "Truyền thông dữ liệu thời gian thực, điểm-điểm, cần thiết bị cùng online mới có thể đồng bộ",
|
|
363
363
|
"enabled": {
|
|
364
|
+
"invalid": "Vui lòng nhập địa chỉ máy chủ tín hiệu và tên kênh đồng bộ trước khi bật",
|
|
364
365
|
"title": "Bật đồng bộ"
|
|
365
366
|
},
|
|
367
|
+
"signaling": {
|
|
368
|
+
"desc": "WebRTC sẽ sử dụng địa chỉ này để đồng bộ",
|
|
369
|
+
"placeholder": "Vui lòng nhập địa chỉ máy chủ tín hiệu",
|
|
370
|
+
"title": "Máy chủ tín hiệu"
|
|
371
|
+
},
|
|
366
372
|
"title": "WebRTC Đồng bộ"
|
|
367
373
|
}
|
|
368
374
|
},
|
|
@@ -406,15 +412,5 @@
|
|
|
406
412
|
"store": "Cửa hàng tiện ích"
|
|
407
413
|
},
|
|
408
414
|
"title": "Công cụ mở rộng"
|
|
409
|
-
},
|
|
410
|
-
"webrtc": {
|
|
411
|
-
"enabled": {
|
|
412
|
-
"invalid": "请填写信令服务器和同步频道名称后再开启"
|
|
413
|
-
},
|
|
414
|
-
"signaling": {
|
|
415
|
-
"desc": "WebRTC sẽ sử dụng địa chỉ này để đồng bộ",
|
|
416
|
-
"placeholder": "Vui lòng nhập địa chỉ máy chủ tín hiệu",
|
|
417
|
-
"title": "Máy chủ tín hiệu"
|
|
418
|
-
}
|
|
419
415
|
}
|
|
420
416
|
}
|
package/locales/zh-CN/chat.json
CHANGED
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"duplicate": "创建副本",
|
|
100
100
|
"export": "导出话题"
|
|
101
101
|
},
|
|
102
|
+
"checkOpenNewTopic": "是否开启新话题?",
|
|
102
103
|
"confirmRemoveAll": "即将删除全部话题,删除后将不可恢复,请谨慎操作。",
|
|
103
104
|
"confirmRemoveTopic": "即将删除该话题,删除后将不可恢复,请谨慎操作。",
|
|
104
105
|
"confirmRemoveUnstarred": "即将删除未收藏话题,删除后将不可恢复,请谨慎操作。",
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
"openNewTopic": "开启新话题",
|
|
113
114
|
"removeAll": "删除全部话题",
|
|
114
115
|
"removeUnstarred": "删除未收藏话题",
|
|
116
|
+
"checkSaveCurrentMessages": "是否保存当前会话为话题?",
|
|
115
117
|
"saveCurrentMessages": "将当前会话保存为话题",
|
|
116
118
|
"searchPlaceholder": "搜索话题...",
|
|
117
119
|
"title": "话题"
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"about": "关于",
|
|
3
3
|
"advanceSettings": "高级设置",
|
|
4
|
+
"alert": {
|
|
5
|
+
"cloud": {
|
|
6
|
+
"action": "立即体验",
|
|
7
|
+
"desc": "我们为所有注册用户提供了免费的 {{credit}} 额度计算积分,无需复杂配置开箱即用, 支持全局云同步与进阶联网查询,更多高级特性等你探索。",
|
|
8
|
+
"descOnMobile": "我们为所有注册用户提供了免费的 {{credit}} 额度计算积分,无需复杂配置开箱即用。",
|
|
9
|
+
"title": "{{name}} 开始公测"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
4
12
|
"appInitializing": "应用启动中,请耐心等待...",
|
|
5
13
|
"autoGenerate": "自动补全",
|
|
6
14
|
"autoGenerateTooltip": "基于提示词自动补全助手描述",
|
|
@@ -206,6 +214,7 @@
|
|
|
206
214
|
"userPanel": {
|
|
207
215
|
"anonymousNickName": "匿名用户",
|
|
208
216
|
"billing": "账单管理",
|
|
217
|
+
"cloud": "体验 {{name}}",
|
|
209
218
|
"data": "数据存储",
|
|
210
219
|
"defaultNickname": "社区版用户",
|
|
211
220
|
"discord": "社区支持",
|
package/locales/zh-TW/chat.json
CHANGED
|
@@ -99,6 +99,8 @@
|
|
|
99
99
|
"duplicate": "建立副本",
|
|
100
100
|
"export": "匯出主題"
|
|
101
101
|
},
|
|
102
|
+
"checkOpenNewTopic": "是否開啟新主題?",
|
|
103
|
+
"checkSaveCurrentMessages": "是否將當前對話保存為話題?",
|
|
102
104
|
"confirmRemoveAll": "即將刪除全部話題,刪除後將不可恢復,請謹慎操作。",
|
|
103
105
|
"confirmRemoveTopic": "即將刪除該話題,刪除後將不可恢復,請謹慎操作。",
|
|
104
106
|
"confirmRemoveUnstarred": "即將刪除未收藏話題,刪除後將不可恢復,請謹慎操作。",
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"about": "關於",
|
|
3
3
|
"advanceSettings": "進階設定",
|
|
4
|
+
"alert": {
|
|
5
|
+
"cloud": {
|
|
6
|
+
"action": "免費體驗",
|
|
7
|
+
"desc": "我們為所有註冊用戶提供了 {{credit}} 免費的計算積分,無需複雜配置開箱即用,支持無限對話歷史記錄與全局雲同步,更多高級特性等你一起探索。",
|
|
8
|
+
"descOnMobile": "我們為所有註冊用戶提供了 {{credit}} 免費的計算積分,無需複雜配置即可使用。",
|
|
9
|
+
"title": "歡迎體驗 {{name}}"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
4
12
|
"appInitializing": "應用程式初始化中,請耐心等候...",
|
|
5
13
|
"autoGenerate": "自動生成",
|
|
6
14
|
"autoGenerateTooltip": "基於提示詞自動生成助手描述",
|
|
@@ -206,6 +214,7 @@
|
|
|
206
214
|
"userPanel": {
|
|
207
215
|
"anonymousNickName": "匿名使用者",
|
|
208
216
|
"billing": "帳單管理",
|
|
217
|
+
"cloud": "體驗 {{name}}",
|
|
209
218
|
"data": "資料儲存",
|
|
210
219
|
"defaultNickname": "社群版使用者",
|
|
211
220
|
"discord": "社區支援",
|
package/next-sitemap.config.mjs
CHANGED
|
@@ -4,7 +4,7 @@ const isVercelPreview = process.env.VERCEL === '1' && process.env.VERCEL_ENV !==
|
|
|
4
4
|
|
|
5
5
|
const vercelPreviewUrl = `https://${process.env.VERCEL_URL}`;
|
|
6
6
|
|
|
7
|
-
const siteUrl = isVercelPreview ? vercelPreviewUrl : 'https://
|
|
7
|
+
const siteUrl = isVercelPreview ? vercelPreviewUrl : 'https://lobechat.com';
|
|
8
8
|
|
|
9
9
|
/** @type {import('next-sitemap').IConfig} */
|
|
10
10
|
const config = {
|
|
@@ -46,8 +46,8 @@ const config = {
|
|
|
46
46
|
|
|
47
47
|
return paths;
|
|
48
48
|
},
|
|
49
|
-
siteUrl,
|
|
50
49
|
generateRobotsTxt: true,
|
|
50
|
+
siteUrl,
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
export default config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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",
|
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
"random-words": "^2.0.1",
|
|
171
171
|
"react": "^18.3.1",
|
|
172
172
|
"react-dom": "^18.3.1",
|
|
173
|
+
"react-fast-marquee": "^1.6.5",
|
|
173
174
|
"react-hotkeys-hook": "^4.5.0",
|
|
174
175
|
"react-i18next": "14.0.2",
|
|
175
176
|
"react-layout-kit": "^1.9.0",
|
|
@@ -4,7 +4,9 @@ import { useTheme } from 'antd-style';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
|
+
import CloudBanner, { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
|
|
7
8
|
import { usePlatform } from '@/hooks/usePlatform';
|
|
9
|
+
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
8
10
|
|
|
9
11
|
import { LayoutProps } from './type';
|
|
10
12
|
|
|
@@ -12,19 +14,24 @@ const Layout = memo<LayoutProps>(({ children, nav }) => {
|
|
|
12
14
|
const { isPWA } = usePlatform();
|
|
13
15
|
const theme = useTheme();
|
|
14
16
|
|
|
17
|
+
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
|
|
18
|
+
|
|
15
19
|
return (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
<>
|
|
21
|
+
{showCloudPromotion && <CloudBanner />}
|
|
22
|
+
<Flexbox
|
|
23
|
+
height={showCloudPromotion ? `calc(100% - ${BANNER_HEIGHT}px)` : '100%'}
|
|
24
|
+
horizontal
|
|
25
|
+
style={{
|
|
26
|
+
borderTop: isPWA ? `1px solid ${theme.colorBorder}` : undefined,
|
|
27
|
+
position: 'relative',
|
|
28
|
+
}}
|
|
29
|
+
width={'100%'}
|
|
30
|
+
>
|
|
31
|
+
{nav}
|
|
32
|
+
{children}
|
|
33
|
+
</Flexbox>
|
|
34
|
+
</>
|
|
28
35
|
);
|
|
29
36
|
});
|
|
30
37
|
|
|
@@ -4,7 +4,9 @@ import { usePathname } from 'next/navigation';
|
|
|
4
4
|
import qs from 'query-string';
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
|
|
7
|
+
import CloudBanner from '@/features/AlertBanner/CloudBanner';
|
|
7
8
|
import { useQuery } from '@/hooks/useQuery';
|
|
9
|
+
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
8
10
|
|
|
9
11
|
import { LayoutProps } from './type';
|
|
10
12
|
|
|
@@ -16,8 +18,11 @@ const Layout = memo(({ children, nav }: LayoutProps) => {
|
|
|
16
18
|
const { url } = qs.parseUrl(pathname);
|
|
17
19
|
const showNav = !showMobileWorkspace && MOBILE_NAV_ROUTES.has(url);
|
|
18
20
|
|
|
21
|
+
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
|
|
22
|
+
|
|
19
23
|
return (
|
|
20
24
|
<>
|
|
25
|
+
{showCloudPromotion && <CloudBanner mobile />}
|
|
21
26
|
{children}
|
|
22
27
|
{showNav && nav}
|
|
23
28
|
</>
|
|
@@ -14,6 +14,7 @@ import { memo, useMemo } from 'react';
|
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
import { Flexbox } from 'react-layout-kit';
|
|
16
16
|
|
|
17
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
17
18
|
import { useChatStore } from '@/store/chat';
|
|
18
19
|
|
|
19
20
|
const useStyles = createStyles(({ css }) => ({
|
|
@@ -41,6 +42,8 @@ interface TopicContentProps {
|
|
|
41
42
|
const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
42
43
|
const { t } = useTranslation('common');
|
|
43
44
|
|
|
45
|
+
const mobile = useIsMobile();
|
|
46
|
+
|
|
44
47
|
const [
|
|
45
48
|
editing,
|
|
46
49
|
favoriteTopic,
|
|
@@ -183,7 +186,7 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
183
186
|
value={title}
|
|
184
187
|
/>
|
|
185
188
|
)}
|
|
186
|
-
{showMore && !editing && (
|
|
189
|
+
{(showMore || mobile) && !editing && (
|
|
187
190
|
<Dropdown
|
|
188
191
|
arrow={false}
|
|
189
192
|
menu={{
|
|
@@ -18,6 +18,8 @@ export const FeatureFlagsSchema = z.object({
|
|
|
18
18
|
welcome_suggest: z.boolean().optional(),
|
|
19
19
|
|
|
20
20
|
clerk_sign_up: z.boolean().optional(),
|
|
21
|
+
|
|
22
|
+
cloud_promotion: z.boolean().optional(),
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
// TypeScript 类型,从 Zod schema 生成
|
|
@@ -40,6 +42,8 @@ export const DEFAULT_FEATURE_FLAGS: IFeatureFlags = {
|
|
|
40
42
|
welcome_suggest: true,
|
|
41
43
|
|
|
42
44
|
clerk_sign_up: true,
|
|
45
|
+
|
|
46
|
+
cloud_promotion: false,
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
export const mapFeatureFlagsEnvToState = (config: IFeatureFlags) => {
|
|
@@ -59,5 +63,7 @@ export const mapFeatureFlagsEnvToState = (config: IFeatureFlags) => {
|
|
|
59
63
|
showWelcomeSuggest: config.welcome_suggest,
|
|
60
64
|
|
|
61
65
|
enableClerkSignUp: config.clerk_sign_up,
|
|
66
|
+
|
|
67
|
+
showCloudPromotion: config.cloud_promotion,
|
|
62
68
|
};
|
|
63
69
|
};
|
package/src/const/guide.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
EMAIL_BUSINESS,
|
|
7
7
|
EMAIL_SUPPORT,
|
|
8
8
|
GITHUB,
|
|
9
|
+
OFFICIAL_PREVIEW_URL,
|
|
9
10
|
OFFICIAL_SITE,
|
|
10
11
|
OFFICIAL_URL,
|
|
11
12
|
SELF_HOSTING_DOCUMENTS,
|
|
@@ -39,10 +40,12 @@ and offers a one-click FREE deployment for a private ChatGPT chat application, m
|
|
|
39
40
|
- [Plugin System (Function Calling)](${urlJoin(USAGE_DOCUMENTS, '/features/plugin-system')})
|
|
40
41
|
- [Agent Market (GPTs)](${urlJoin(USAGE_DOCUMENTS, '/features/agent-market')})
|
|
41
42
|
|
|
42
|
-
###
|
|
43
|
+
### Community Edition and Cloud Version
|
|
43
44
|
|
|
44
|
-
LobeChat is currently available as a community preview version, completely open-source and free of charge.
|
|
45
|
-
|
|
45
|
+
LobeChat is currently available as a community preview version, completely open-source and free of charge.
|
|
46
|
+
|
|
47
|
+
In the LobeChat Cloud version, we provide 500,000 free computing credits to all registered users. It is ready to use without complex configurations.
|
|
48
|
+
If you require more usage, you can subscribe to the Basic, Advanced, or Professional versions for a fee.
|
|
46
49
|
|
|
47
50
|
### Self Hosting
|
|
48
51
|
|
|
@@ -60,7 +63,8 @@ Learn more about [Build your own LobeChat](${SELF_HOSTING_DOCUMENTS}) by checkin
|
|
|
60
63
|
In the response, please try to pick and include the relevant links below, and if a relevant answer cannot be provided, also offer the user these related links:
|
|
61
64
|
|
|
62
65
|
- Official Website: ${OFFICIAL_SITE}
|
|
63
|
-
-
|
|
66
|
+
- Cloud Version: ${OFFICIAL_URL}
|
|
67
|
+
- Community Edition: ${OFFICIAL_PREVIEW_URL}
|
|
64
68
|
- GitHub Repository: ${GITHUB}
|
|
65
69
|
- Latest News: ${BLOG}
|
|
66
70
|
- Usage Documentation: ${USAGE_DOCUMENTS}
|
package/src/const/url.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { withBasePath } from '@/utils/basePath';
|
|
|
6
6
|
import pkg from '../../package.json';
|
|
7
7
|
import { INBOX_SESSION_ID } from './session';
|
|
8
8
|
|
|
9
|
-
export const OFFICIAL_URL = 'https://
|
|
9
|
+
export const OFFICIAL_URL = 'https://lobechat.com/';
|
|
10
|
+
export const OFFICIAL_PREVIEW_URL = 'https://chat-preview.lobehub.com/';
|
|
10
11
|
export const OFFICIAL_SITE = 'https://lobehub.com/';
|
|
11
12
|
|
|
12
13
|
export const getCanonicalUrl = (path: string) => urlJoin(OFFICIAL_URL, path);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@lobehub/ui';
|
|
4
|
+
import { useSize } from 'ahooks';
|
|
5
|
+
import { Button } from 'antd';
|
|
6
|
+
import { createStyles } from 'antd-style';
|
|
7
|
+
import { ArrowRightIcon } from 'lucide-react';
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import Marquee from 'react-fast-marquee';
|
|
11
|
+
import { useTranslation } from 'react-i18next';
|
|
12
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
13
|
+
|
|
14
|
+
import { OFFICIAL_URL } from '@/const/url';
|
|
15
|
+
import { isOnServerSide } from '@/utils/env';
|
|
16
|
+
|
|
17
|
+
export const BANNER_HEIGHT = 40;
|
|
18
|
+
|
|
19
|
+
const useStyles = createStyles(({ css, token, stylish, cx, isDarkMode }) => ({
|
|
20
|
+
background: cx(
|
|
21
|
+
stylish.gradientAnimation,
|
|
22
|
+
css`
|
|
23
|
+
position: absolute;
|
|
24
|
+
|
|
25
|
+
width: max(64%, 1280px);
|
|
26
|
+
height: 100%;
|
|
27
|
+
|
|
28
|
+
opacity: 0.8;
|
|
29
|
+
filter: blur(60px);
|
|
30
|
+
`,
|
|
31
|
+
),
|
|
32
|
+
container: css`
|
|
33
|
+
position: relative;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
background-color: ${isDarkMode ? token.colorFill : token.colorFillSecondary};
|
|
36
|
+
`,
|
|
37
|
+
wrapper: css`
|
|
38
|
+
z-index: 1;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
`,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const CloudBanner = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
45
|
+
const ref = useRef(null);
|
|
46
|
+
const contentRef = useRef(null);
|
|
47
|
+
const size = useSize(ref);
|
|
48
|
+
const contentSize = useSize(contentRef);
|
|
49
|
+
const { styles } = useStyles();
|
|
50
|
+
const { t } = useTranslation('common');
|
|
51
|
+
const [isTruncated, setIsTruncated] = useState(mobile);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (mobile || isOnServerSide || !size || !contentSize) return;
|
|
55
|
+
setIsTruncated(contentSize.width > size.width - 120);
|
|
56
|
+
}, [size, contentSize, mobile]);
|
|
57
|
+
|
|
58
|
+
const content = (
|
|
59
|
+
<Flexbox align={'center'} flex={'none'} gap={8} horizontal ref={contentRef}>
|
|
60
|
+
<b>{t('alert.cloud.title', { name: 'LobeChat Cloud' })}:</b>
|
|
61
|
+
<span>
|
|
62
|
+
{t(mobile ? 'alert.cloud.descOnMobile' : 'alert.cloud.desc', {
|
|
63
|
+
credit: new Intl.NumberFormat('en-US').format(500_000),
|
|
64
|
+
name: 'LobeChat Cloud',
|
|
65
|
+
})}
|
|
66
|
+
</span>
|
|
67
|
+
</Flexbox>
|
|
68
|
+
);
|
|
69
|
+
return (
|
|
70
|
+
<Center
|
|
71
|
+
className={styles.container}
|
|
72
|
+
flex={'none'}
|
|
73
|
+
height={BANNER_HEIGHT}
|
|
74
|
+
paddingInline={16}
|
|
75
|
+
ref={ref}
|
|
76
|
+
width={'100%'}
|
|
77
|
+
>
|
|
78
|
+
<div className={styles.background} />
|
|
79
|
+
<Center className={styles.wrapper} gap={16} horizontal width={'100%'}>
|
|
80
|
+
{isTruncated ? <Marquee pauseOnHover>{content}</Marquee> : content}
|
|
81
|
+
<Link href={OFFICIAL_URL} target={'_blank'}>
|
|
82
|
+
<Button size={'small'} type="primary">
|
|
83
|
+
{t('alert.cloud.action')} <Icon icon={ArrowRightIcon} />
|
|
84
|
+
</Button>
|
|
85
|
+
</Link>
|
|
86
|
+
</Center>
|
|
87
|
+
</Center>
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export default CloudBanner;
|
|
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import HotKeys from '@/components/HotKeys';
|
|
8
8
|
import { ALT_KEY, CLEAN_MESSAGE_KEY, META_KEY } from '@/const/hotkeys';
|
|
9
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
9
10
|
import { useChatStore } from '@/store/chat';
|
|
10
11
|
import { useFileStore } from '@/store/file';
|
|
11
12
|
|
|
@@ -15,6 +16,7 @@ const Clear = memo(() => {
|
|
|
15
16
|
const [clearImageList] = useFileStore((s) => [s.clearImageList]);
|
|
16
17
|
const hotkeys = [META_KEY, ALT_KEY, CLEAN_MESSAGE_KEY].join('+');
|
|
17
18
|
const [confirmOpened, updateConfirmOpened] = useState(false);
|
|
19
|
+
const mobile = useIsMobile();
|
|
18
20
|
|
|
19
21
|
const resetConversation = useCallback(async () => {
|
|
20
22
|
await clearMessage();
|
|
@@ -27,6 +29,8 @@ const Clear = memo(() => {
|
|
|
27
29
|
<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} inverseTheme keys={hotkeys} />
|
|
28
30
|
);
|
|
29
31
|
|
|
32
|
+
const popconfirmPlacement = mobile ? 'top' : 'topRight';
|
|
33
|
+
|
|
30
34
|
return (
|
|
31
35
|
<Popconfirm
|
|
32
36
|
arrow={false}
|
|
@@ -34,14 +38,19 @@ const Clear = memo(() => {
|
|
|
34
38
|
onConfirm={resetConversation}
|
|
35
39
|
onOpenChange={updateConfirmOpened}
|
|
36
40
|
open={confirmOpened}
|
|
37
|
-
placement={
|
|
38
|
-
title={
|
|
41
|
+
placement={popconfirmPlacement}
|
|
42
|
+
title={
|
|
43
|
+
<div style={{ marginBottom: '8px', whiteSpace: 'pre-line', wordBreak: 'break-word' }}>
|
|
44
|
+
{t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
39
47
|
>
|
|
40
|
-
<ActionIcon
|
|
41
|
-
icon={Eraser}
|
|
48
|
+
<ActionIcon
|
|
49
|
+
icon={Eraser}
|
|
42
50
|
overlayStyle={{ maxWidth: 'none' }}
|
|
43
|
-
placement={'bottom'}
|
|
44
|
-
title={actionTitle}
|
|
51
|
+
placement={'bottom'}
|
|
52
|
+
title={actionTitle}
|
|
53
|
+
/>
|
|
45
54
|
</Popconfirm>
|
|
46
55
|
);
|
|
47
56
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionIcon, Icon, Tooltip } from '@lobehub/ui';
|
|
2
|
-
import { Button } from 'antd';
|
|
2
|
+
import { Button, Popconfirm } from 'antd';
|
|
3
3
|
import { LucideGalleryVerticalEnd, LucideMessageSquarePlus } from 'lucide-react';
|
|
4
|
-
import { memo } from 'react';
|
|
4
|
+
import { memo, useState } from 'react';
|
|
5
5
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
@@ -19,9 +19,9 @@ const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
19
19
|
|
|
20
20
|
const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
|
|
21
21
|
|
|
22
|
+
const [confirmOpened, setConfirmOpened] = useState(false);
|
|
23
|
+
|
|
22
24
|
const icon = hasTopic ? LucideMessageSquarePlus : LucideGalleryVerticalEnd;
|
|
23
|
-
const Render = mobile ? ActionIcon : Button;
|
|
24
|
-
const iconRender: any = mobile ? icon : <Icon icon={icon} />;
|
|
25
25
|
const desc = t(hasTopic ? 'topic.openNewTopic' : 'topic.saveCurrentMessages');
|
|
26
26
|
|
|
27
27
|
const hotkeys = [ALT_KEY, SAVE_TOPIC_KEY].join('+');
|
|
@@ -31,11 +31,46 @@ const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
31
31
|
preventDefault: true,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
if (mobile) {
|
|
35
|
+
return (
|
|
36
|
+
<Popconfirm
|
|
37
|
+
arrow={false}
|
|
38
|
+
okButtonProps={{ danger: false, type: 'primary' }}
|
|
39
|
+
onConfirm={() => mutate()}
|
|
40
|
+
onOpenChange={setConfirmOpened}
|
|
41
|
+
open={confirmOpened}
|
|
42
|
+
placement={'top'}
|
|
43
|
+
title={
|
|
44
|
+
<div style={{ alignItems: 'center', display: 'flex', marginBottom: '8px' }}>
|
|
45
|
+
<div style={{ marginRight: '16px', whiteSpace: 'pre-line', wordBreak: 'break-word' }}>
|
|
46
|
+
{t(hasTopic ? 'topic.checkOpenNewTopic' : 'topic.checkSaveCurrentMessages')}
|
|
47
|
+
</div>
|
|
48
|
+
<HotKeys inverseTheme={false} keys={hotkeys} />
|
|
49
|
+
</div>
|
|
50
|
+
}
|
|
51
|
+
>
|
|
52
|
+
<Tooltip>
|
|
53
|
+
<ActionIcon
|
|
54
|
+
aria-label={desc}
|
|
55
|
+
icon={icon}
|
|
56
|
+
loading={isValidating}
|
|
57
|
+
onClick={() => setConfirmOpened(true)}
|
|
58
|
+
/>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
</Popconfirm>
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
return (
|
|
64
|
+
<Tooltip title={<HotKeys desc={desc} inverseTheme keys={hotkeys} />}>
|
|
65
|
+
<Button
|
|
66
|
+
aria-label={desc}
|
|
67
|
+
icon={<Icon icon={icon} />}
|
|
68
|
+
loading={isValidating}
|
|
69
|
+
onClick={() => mutate()}
|
|
70
|
+
/>
|
|
71
|
+
</Tooltip>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
39
74
|
});
|
|
40
75
|
|
|
41
76
|
export default SaveTopic;
|
|
@@ -4,6 +4,7 @@ import { ItemType } from 'antd/es/menu/interface';
|
|
|
4
4
|
import {
|
|
5
5
|
Book,
|
|
6
6
|
CircleUserRound,
|
|
7
|
+
Cloudy,
|
|
7
8
|
Download,
|
|
8
9
|
Feather,
|
|
9
10
|
HardDriveDownload,
|
|
@@ -21,7 +22,14 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
21
22
|
import urlJoin from 'url-join';
|
|
22
23
|
|
|
23
24
|
import type { MenuProps } from '@/components/Menu';
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
DISCORD,
|
|
27
|
+
DOCUMENTS,
|
|
28
|
+
EMAIL_SUPPORT,
|
|
29
|
+
GITHUB_ISSUES,
|
|
30
|
+
OFFICIAL_URL,
|
|
31
|
+
mailTo,
|
|
32
|
+
} from '@/const/url';
|
|
25
33
|
import { isServerMode } from '@/const/version';
|
|
26
34
|
import DataImporter from '@/features/DataImporter';
|
|
27
35
|
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
|
@@ -29,6 +37,7 @@ import { usePWAInstall } from '@/hooks/usePWAInstall';
|
|
|
29
37
|
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
|
30
38
|
import { configService } from '@/services/config';
|
|
31
39
|
import { SettingsTabs } from '@/store/global/initialState';
|
|
40
|
+
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
32
41
|
import { useUserStore } from '@/store/user';
|
|
33
42
|
import { authSelectors } from '@/store/user/selectors';
|
|
34
43
|
|
|
@@ -62,6 +71,7 @@ export const useMenu = () => {
|
|
|
62
71
|
const hasNewVersion = useNewVersion();
|
|
63
72
|
const openSettings = useOpenSettings();
|
|
64
73
|
const { t } = useTranslation(['common', 'setting', 'auth']);
|
|
74
|
+
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
|
|
65
75
|
const [isLogin, isLoginWithAuth, isLoginWithClerk, openUserProfile] = useUserStore((s) => [
|
|
66
76
|
authSelectors.isLogin(s),
|
|
67
77
|
authSelectors.isLoginWithAuth(s),
|
|
@@ -163,6 +173,15 @@ export const useMenu = () => {
|
|
|
163
173
|
].filter(Boolean) as ItemType[]);
|
|
164
174
|
|
|
165
175
|
const helps: MenuProps['items'] = [
|
|
176
|
+
showCloudPromotion && {
|
|
177
|
+
icon: <Icon icon={Cloudy} />,
|
|
178
|
+
key: 'cloud',
|
|
179
|
+
label: (
|
|
180
|
+
<Link href={OFFICIAL_URL} target={'_blank'}>
|
|
181
|
+
{t('userPanel.cloud', { name: 'LobeChat Cloud' })}
|
|
182
|
+
</Link>
|
|
183
|
+
),
|
|
184
|
+
},
|
|
166
185
|
{
|
|
167
186
|
icon: <Icon icon={DiscordIcon} />,
|
|
168
187
|
key: 'discord',
|
|
@@ -209,7 +228,7 @@ export const useMenu = () => {
|
|
|
209
228
|
{
|
|
210
229
|
type: 'divider',
|
|
211
230
|
},
|
|
212
|
-
];
|
|
231
|
+
].filter(Boolean) as ItemType[];
|
|
213
232
|
|
|
214
233
|
const mainItems = [
|
|
215
234
|
{
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { FunctionDeclarationSchemaType, FunctionDeclarationsTool } from '@google/generative-ai';
|
|
3
3
|
import { JSONSchema7 } from 'json-schema';
|
|
4
4
|
import OpenAI from 'openai';
|
|
5
|
-
import {
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { OpenAIChatMessage } from '@/libs/agent-runtime';
|
|
8
8
|
|
|
9
9
|
import * as debugStreamModule from '../utils/debugStream';
|
|
10
10
|
import { LobeGoogleAI } from './index';
|
|
@@ -383,7 +383,7 @@ describe('LobeGoogleAI', () => {
|
|
|
383
383
|
role: 'user',
|
|
384
384
|
},
|
|
385
385
|
];
|
|
386
|
-
const model = 'gemini-
|
|
386
|
+
const model = 'gemini-1.5-flash-latest';
|
|
387
387
|
|
|
388
388
|
// 调用 buildGoogleMessages 方法
|
|
389
389
|
const contents = instance['buildGoogleMessages'](messages, model);
|
|
@@ -398,36 +398,6 @@ describe('LobeGoogleAI', () => {
|
|
|
398
398
|
});
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
-
describe('convertModel', () => {
|
|
402
|
-
it('should use default text model when no images are included in messages', () => {
|
|
403
|
-
const messages: OpenAIChatMessage[] = [
|
|
404
|
-
{ content: 'Hello', role: 'user' },
|
|
405
|
-
{ content: 'Hi', role: 'assistant' },
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
// 调用 buildGoogleMessages 方法
|
|
409
|
-
const model = instance['convertModel']('gemini-pro-vision', messages);
|
|
410
|
-
|
|
411
|
-
expect(model).toEqual('gemini-pro'); // 假设 'gemini-pro' 是默认文本模型
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it('should use specified model when images are included in messages', () => {
|
|
415
|
-
const messages: OpenAIChatMessage[] = [
|
|
416
|
-
{
|
|
417
|
-
content: [
|
|
418
|
-
{ type: 'text', text: 'Hello' },
|
|
419
|
-
{ type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
|
|
420
|
-
],
|
|
421
|
-
role: 'user',
|
|
422
|
-
},
|
|
423
|
-
];
|
|
424
|
-
|
|
425
|
-
const model = instance['convertModel']('gemini-pro-vision', messages);
|
|
426
|
-
|
|
427
|
-
expect(model).toEqual('gemini-pro-vision');
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
|
|
431
401
|
describe('buildGoogleTools', () => {
|
|
432
402
|
it('should return undefined when tools is undefined or empty', () => {
|
|
433
403
|
expect(instance['buildGoogleTools'](undefined)).toBeUndefined();
|