@lobehub/chat 1.19.30 → 1.19.32
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 +50 -0
- package/locales/ar/models.json +12 -0
- package/locales/bg-BG/models.json +12 -0
- package/locales/de-DE/models.json +12 -0
- package/locales/en-US/models.json +12 -0
- package/locales/es-ES/models.json +12 -0
- package/locales/fr-FR/models.json +12 -0
- package/locales/it-IT/models.json +12 -0
- package/locales/ja-JP/models.json +12 -0
- package/locales/ko-KR/models.json +12 -0
- package/locales/nl-NL/models.json +12 -0
- package/locales/pl-PL/models.json +12 -0
- package/locales/pt-BR/models.json +12 -0
- package/locales/ru-RU/models.json +12 -0
- package/locales/tr-TR/models.json +12 -0
- package/locales/vi-VN/models.json +12 -0
- package/locales/zh-CN/models.json +13 -1
- package/locales/zh-TW/models.json +12 -0
- package/next.config.mjs +5 -7
- package/package.json +3 -2
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx +1 -0
- package/src/app/sw.ts +26 -0
- package/src/config/modelProviders/google.ts +54 -2
- package/src/config/modelProviders/taichu.ts +5 -2
- package/src/features/FileViewer/Renderer/Image/index.tsx +1 -0
- package/src/features/PWAInstall/Install.tsx +80 -0
- package/src/features/PWAInstall/index.tsx +6 -61
- package/src/libs/agent-runtime/google/index.test.ts +99 -5
- package/src/libs/agent-runtime/google/index.ts +58 -30
- package/src/server/services/discover/index.ts +1 -1
- package/src/services/message/server.ts +1 -1
- package/src/services/session/server.ts +2 -2
- package/src/store/chat/slices/plugin/action.ts +3 -1
- package/tsconfig.json +3 -3
@@ -332,9 +332,15 @@
|
|
332
332
|
"gemini-1.5-flash-001": {
|
333
333
|
"description": "Gemini 1.5 Flash 001 是一款高效的多模态模型,支持广泛应用的扩展。"
|
334
334
|
},
|
335
|
+
"gemini-1.5-flash-002": {
|
336
|
+
"description": "Gemini 1.5 Flash 002 是一款高效的多模态模型,支持广泛应用的扩展。"
|
337
|
+
},
|
335
338
|
"gemini-1.5-flash-8b-exp-0827": {
|
336
339
|
"description": "Gemini 1.5 Flash 8B 0827 专为处理大规模任务场景设计,提供无与伦比的处理速度。"
|
337
340
|
},
|
341
|
+
"gemini-1.5-flash-8b-exp-0924": {
|
342
|
+
"description": "Gemini 1.5 Flash 8B 0924 是最新的实验性模型,在文本和多模态用例中都有显著的性能提升。"
|
343
|
+
},
|
338
344
|
"gemini-1.5-flash-exp-0827": {
|
339
345
|
"description": "Gemini 1.5 Flash 0827 提供了优化后的多模态处理能力,适用多种复杂任务场景。"
|
340
346
|
},
|
@@ -344,6 +350,9 @@
|
|
344
350
|
"gemini-1.5-pro-001": {
|
345
351
|
"description": "Gemini 1.5 Pro 001 是可扩展的多模态AI解决方案,支持广泛的复杂任务。"
|
346
352
|
},
|
353
|
+
"gemini-1.5-pro-002": {
|
354
|
+
"description": "Gemini 1.5 Pro 002 是最新的生产就绪模型,提供更高质量的输出,特别在数学、长上下文和视觉任务方面有显著提升。"
|
355
|
+
},
|
347
356
|
"gemini-1.5-pro-exp-0801": {
|
348
357
|
"description": "Gemini 1.5 Pro 0801 提供出色的多模态处理能力,为应用开发带来更大灵活性。"
|
349
358
|
},
|
@@ -872,7 +881,10 @@
|
|
872
881
|
"description": "支持大规模上下文交互,适合复杂对话场景。"
|
873
882
|
},
|
874
883
|
"taichu_llm": {
|
875
|
-
"description": "
|
884
|
+
"description": "Taichu 2.0 基于海量高质数据训练,具有更强的文本理解、内容创作、对话问答等能力"
|
885
|
+
},
|
886
|
+
"taichu_vqa": {
|
887
|
+
"description": "Taichu 2.0V 融合了图像理解、知识迁移、逻辑归因等能力,在图文问答领域表现突出"
|
876
888
|
},
|
877
889
|
"togethercomputer/StripedHyena-Nous-7B": {
|
878
890
|
"description": "StripedHyena Nous (7B) 通过高效的策略和模型架构,提供增强的计算能力。"
|
@@ -332,9 +332,15 @@
|
|
332
332
|
"gemini-1.5-flash-001": {
|
333
333
|
"description": "Gemini 1.5 Flash 001 是一款高效的多模態模型,支持廣泛應用的擴展。"
|
334
334
|
},
|
335
|
+
"gemini-1.5-flash-002": {
|
336
|
+
"description": "Gemini 1.5 Flash 002 是一款高效的多模態模型,支持廣泛應用的擴展。"
|
337
|
+
},
|
335
338
|
"gemini-1.5-flash-8b-exp-0827": {
|
336
339
|
"description": "Gemini 1.5 Flash 8B 0827 專為處理大規模任務場景設計,提供無與倫比的處理速度。"
|
337
340
|
},
|
341
|
+
"gemini-1.5-flash-8b-exp-0924": {
|
342
|
+
"description": "Gemini 1.5 Flash 8B 0924 是最新的實驗性模型,在文本和多模態用例中都有顯著的性能提升。"
|
343
|
+
},
|
338
344
|
"gemini-1.5-flash-exp-0827": {
|
339
345
|
"description": "Gemini 1.5 Flash 0827 提供了優化後的多模態處理能力,適用多種複雜任務場景。"
|
340
346
|
},
|
@@ -344,6 +350,9 @@
|
|
344
350
|
"gemini-1.5-pro-001": {
|
345
351
|
"description": "Gemini 1.5 Pro 001 是可擴展的多模態 AI 解決方案,支持廣泛的複雜任務。"
|
346
352
|
},
|
353
|
+
"gemini-1.5-pro-002": {
|
354
|
+
"description": "Gemini 1.5 Pro 002 是最新的生產就緒模型,提供更高品質的輸出,特別在數學、長上下文和視覺任務方面有顯著提升。"
|
355
|
+
},
|
347
356
|
"gemini-1.5-pro-exp-0801": {
|
348
357
|
"description": "Gemini 1.5 Pro 0801 提供出色的多模態處理能力,為應用開發帶來更大靈活性。"
|
349
358
|
},
|
@@ -874,6 +883,9 @@
|
|
874
883
|
"taichu_llm": {
|
875
884
|
"description": "紫東太初語言大模型具備超強語言理解能力以及文本創作、知識問答、代碼編程、數學計算、邏輯推理、情感分析、文本摘要等能力。創新性地將大數據預訓練與多源豐富知識相結合,通過持續打磨算法技術,並不斷吸收海量文本數據中詞彙、結構、語法、語義等方面的新知識,實現模型效果不斷進化。為用戶提供更加便捷的信息和服務以及更為智能化的體驗。"
|
876
885
|
},
|
886
|
+
"taichu_vqa": {
|
887
|
+
"description": "Taichu 2.0V 融合了圖像理解、知識遷移、邏輯歸因等能力,在圖文問答領域表現突出。"
|
888
|
+
},
|
877
889
|
"togethercomputer/StripedHyena-Nous-7B": {
|
878
890
|
"description": "StripedHyena Nous (7B) 通過高效的策略和模型架構,提供增強的計算能力。"
|
879
891
|
},
|
package/next.config.mjs
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import nextPWA from '@ducanh2912/next-pwa';
|
2
1
|
import analyzer from '@next/bundle-analyzer';
|
3
2
|
import { withSentryConfig } from '@sentry/nextjs';
|
3
|
+
import withSerwistInit from '@serwist/next';
|
4
4
|
|
5
5
|
const isProd = process.env.NODE_ENV === 'production';
|
6
6
|
const buildWithDocker = process.env.DOCKER === 'true';
|
@@ -192,12 +192,10 @@ const noWrapper = (config) => config;
|
|
192
192
|
const withBundleAnalyzer = process.env.ANALYZE === 'true' ? analyzer() : noWrapper;
|
193
193
|
|
194
194
|
const withPWA = isProd
|
195
|
-
?
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
skipWaiting: true,
|
200
|
-
},
|
195
|
+
? withSerwistInit({
|
196
|
+
register: false,
|
197
|
+
swDest: 'public/sw.js',
|
198
|
+
swSrc: 'src/app/sw.ts',
|
201
199
|
})
|
202
200
|
: noWrapper;
|
203
201
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.19.
|
3
|
+
"version": "1.19.32",
|
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",
|
@@ -127,6 +127,7 @@
|
|
127
127
|
"@next/third-parties": "^14.2.6",
|
128
128
|
"@react-spring/web": "^9.7.3",
|
129
129
|
"@sentry/nextjs": "^7.119.0",
|
130
|
+
"@serwist/next": "^9.0.8",
|
130
131
|
"@t3-oss/env-nextjs": "^0.11.0",
|
131
132
|
"@tanstack/react-query": "^5.52.1",
|
132
133
|
"@trpc/client": "next",
|
@@ -232,7 +233,6 @@
|
|
232
233
|
},
|
233
234
|
"devDependencies": {
|
234
235
|
"@commitlint/cli": "^19.4.0",
|
235
|
-
"@ducanh2912/next-pwa": "^10.2.8",
|
236
236
|
"@edge-runtime/vm": "^4.0.2",
|
237
237
|
"@lobehub/i18n-cli": "^1.19.1",
|
238
238
|
"@lobehub/lint": "^1.24.4",
|
@@ -288,6 +288,7 @@
|
|
288
288
|
"remark-cli": "^11.0.0",
|
289
289
|
"remark-parse": "^10.0.2",
|
290
290
|
"semantic-release": "^21.1.2",
|
291
|
+
"serwist": "^9.0.8",
|
291
292
|
"stylelint": "^15.11.0",
|
292
293
|
"supports-color": "8",
|
293
294
|
"tsx": "^4.17.0",
|
package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx
CHANGED
@@ -150,6 +150,7 @@ describe('<InputArea />', () => {
|
|
150
150
|
const beforeUnloadHandler = vi.fn();
|
151
151
|
|
152
152
|
addEventListenerSpy.mockImplementation((event, handler) => {
|
153
|
+
// @ts-ignore
|
153
154
|
if (event === 'beforeunload') {
|
154
155
|
beforeUnloadHandler.mockImplementation(handler as any);
|
155
156
|
}
|
package/src/app/sw.ts
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
import { defaultCache } from '@serwist/next/worker';
|
2
|
+
import type { PrecacheEntry, SerwistGlobalConfig } from 'serwist';
|
3
|
+
import { Serwist } from 'serwist';
|
4
|
+
|
5
|
+
// This declares the value of `injectionPoint` to TypeScript.
|
6
|
+
// `injectionPoint` is the string that will be replaced by the
|
7
|
+
// actual precache manifest. By default, this string is set to
|
8
|
+
// `"self.__SW_MANIFEST"`.
|
9
|
+
declare global {
|
10
|
+
interface WorkerGlobalScope extends SerwistGlobalConfig {
|
11
|
+
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
// eslint-disable-next-line no-undef
|
16
|
+
declare const self: ServiceWorkerGlobalScope;
|
17
|
+
|
18
|
+
const serwist = new Serwist({
|
19
|
+
clientsClaim: true,
|
20
|
+
navigationPreload: true,
|
21
|
+
precacheEntries: self.__SW_MANIFEST,
|
22
|
+
runtimeCaching: defaultCache,
|
23
|
+
skipWaiting: true,
|
24
|
+
});
|
25
|
+
|
26
|
+
serwist.addEventListeners();
|
@@ -22,7 +22,6 @@ const Google: ModelProviderCard = {
|
|
22
22
|
{
|
23
23
|
description: 'Gemini 1.5 Flash 0827 提供了优化后的多模态处理能力,适用多种复杂任务场景。',
|
24
24
|
displayName: 'Gemini 1.5 Flash 0827',
|
25
|
-
enabled: true,
|
26
25
|
functionCall: true,
|
27
26
|
id: 'gemini-1.5-flash-exp-0827',
|
28
27
|
maxOutput: 8192,
|
@@ -45,12 +44,49 @@ const Google: ModelProviderCard = {
|
|
45
44
|
tokens: 1_048_576 + 8192,
|
46
45
|
vision: true,
|
47
46
|
},
|
47
|
+
{
|
48
|
+
description:
|
49
|
+
'Gemini 1.5 Flash 8B 0924 是最新的实验性模型,在文本和多模态用例中都有显著的性能提升。',
|
50
|
+
displayName: 'Gemini 1.5 Flash 8B 0924',
|
51
|
+
enabled: true,
|
52
|
+
functionCall: true,
|
53
|
+
id: 'gemini-1.5-flash-8b-exp-0924',
|
54
|
+
maxOutput: 8192,
|
55
|
+
pricing: {
|
56
|
+
cachedInput: 0.018_75,
|
57
|
+
input: 0.075,
|
58
|
+
output: 0.3,
|
59
|
+
},
|
60
|
+
releasedAt: '2024-09-24',
|
61
|
+
tokens: 1_048_576 + 8192,
|
62
|
+
vision: true,
|
63
|
+
},
|
48
64
|
{
|
49
65
|
description: 'Gemini 1.5 Flash 001 是一款高效的多模态模型,支持广泛应用的扩展。',
|
50
66
|
displayName: 'Gemini 1.5 Flash 001',
|
51
67
|
functionCall: true,
|
52
68
|
id: 'gemini-1.5-flash-001',
|
53
69
|
maxOutput: 8192,
|
70
|
+
pricing: {
|
71
|
+
cachedInput: 0.018_75,
|
72
|
+
input: 0.075,
|
73
|
+
output: 0.3,
|
74
|
+
},
|
75
|
+
tokens: 1_048_576 + 8192,
|
76
|
+
vision: true,
|
77
|
+
},
|
78
|
+
{
|
79
|
+
description: 'Gemini 1.5 Flash 002 是一款高效的多模态模型,支持广泛应用的扩展。',
|
80
|
+
displayName: 'Gemini 1.5 Flash 002',
|
81
|
+
functionCall: true,
|
82
|
+
id: 'gemini-1.5-flash-002',
|
83
|
+
maxOutput: 8192,
|
84
|
+
pricing: {
|
85
|
+
cachedInput: 0.018_75,
|
86
|
+
input: 0.075,
|
87
|
+
output: 0.3,
|
88
|
+
},
|
89
|
+
releasedAt: '2024-09-25',
|
54
90
|
tokens: 1_048_576 + 8192,
|
55
91
|
vision: true,
|
56
92
|
},
|
@@ -74,7 +110,6 @@ const Google: ModelProviderCard = {
|
|
74
110
|
{
|
75
111
|
description: 'Gemini 1.5 Pro 0827 结合最新优化技术,带来更高效的多模态数据处理能力。',
|
76
112
|
displayName: 'Gemini 1.5 Pro 0827',
|
77
|
-
enabled: true,
|
78
113
|
functionCall: true,
|
79
114
|
id: 'gemini-1.5-pro-exp-0827',
|
80
115
|
maxOutput: 8192,
|
@@ -117,6 +152,23 @@ const Google: ModelProviderCard = {
|
|
117
152
|
tokens: 2_097_152 + 8192,
|
118
153
|
vision: true,
|
119
154
|
},
|
155
|
+
{
|
156
|
+
description:
|
157
|
+
'Gemini 1.5 Pro 002 是最新的生产就绪模型,提供更高质量的输出,特别在数学、长上下文和视觉任务方面有显著提升。',
|
158
|
+
displayName: 'Gemini 1.5 Pro 002',
|
159
|
+
enabled: true,
|
160
|
+
functionCall: true,
|
161
|
+
id: 'gemini-1.5-pro-002',
|
162
|
+
maxOutput: 8192,
|
163
|
+
pricing: {
|
164
|
+
cachedInput: 0.315,
|
165
|
+
input: 1.25,
|
166
|
+
output: 2.5,
|
167
|
+
},
|
168
|
+
releasedAt: '2024-09-24',
|
169
|
+
tokens: 2_097_152 + 8192,
|
170
|
+
vision: true,
|
171
|
+
},
|
120
172
|
{
|
121
173
|
description: 'Gemini 1.0 Pro 是Google的高性能AI模型,专为广泛任务扩展而设计。',
|
122
174
|
displayName: 'Gemini 1.0 Pro',
|
@@ -8,11 +8,13 @@ const Taichu: ModelProviderCard = {
|
|
8
8
|
'Taichu 2.0 基于海量高质数据训练,具有更强的文本理解、内容创作、对话问答等能力',
|
9
9
|
displayName: 'Taichu 2.0',
|
10
10
|
enabled: true,
|
11
|
-
functionCall:
|
11
|
+
functionCall: true,
|
12
12
|
id: 'taichu_llm',
|
13
13
|
tokens: 32_768,
|
14
14
|
},
|
15
|
-
|
15
|
+
/*
|
16
|
+
// TODO: Not support for now
|
17
|
+
{
|
16
18
|
description:
|
17
19
|
'Taichu 2.0V 融合了图像理解、知识迁移、逻辑归因等能力,在图文问答领域表现突出',
|
18
20
|
displayName: 'Taichu 2.0V',
|
@@ -20,6 +22,7 @@ const Taichu: ModelProviderCard = {
|
|
20
22
|
tokens: 4096,
|
21
23
|
vision: true,
|
22
24
|
},
|
25
|
+
*/
|
23
26
|
],
|
24
27
|
checkModel: 'taichu_llm',
|
25
28
|
description:
|
@@ -0,0 +1,80 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import dynamic from 'next/dynamic';
|
4
|
+
import { memo, useEffect, useLayoutEffect } from 'react';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
|
7
|
+
import { BRANDING_NAME } from '@/const/branding';
|
8
|
+
import { PWA_INSTALL_ID } from '@/const/layoutTokens';
|
9
|
+
import { usePWAInstall } from '@/hooks/usePWAInstall';
|
10
|
+
import { useGlobalStore } from '@/store/global';
|
11
|
+
import { systemStatusSelectors } from '@/store/global/selectors';
|
12
|
+
import { useUserStore } from '@/store/user';
|
13
|
+
|
14
|
+
// @ts-ignore
|
15
|
+
const PWA: any = dynamic(() => import('@khmyznikov/pwa-install/dist/pwa-install.react.js'), {
|
16
|
+
ssr: false,
|
17
|
+
});
|
18
|
+
|
19
|
+
const PWAInstall = memo(() => {
|
20
|
+
const { t } = useTranslation('metadata');
|
21
|
+
|
22
|
+
const { install, canInstall } = usePWAInstall();
|
23
|
+
|
24
|
+
const isShowPWAGuide = useUserStore((s) => s.isShowPWAGuide);
|
25
|
+
const [hidePWAInstaller, updateSystemStatus] = useGlobalStore((s) => [
|
26
|
+
systemStatusSelectors.hidePWAInstaller(s),
|
27
|
+
s.updateSystemStatus,
|
28
|
+
]);
|
29
|
+
|
30
|
+
// we need to make the pwa installer hidden by default
|
31
|
+
useLayoutEffect(() => {
|
32
|
+
sessionStorage.setItem('pwa-hide-install', 'true');
|
33
|
+
}, []);
|
34
|
+
|
35
|
+
const pwaInstall =
|
36
|
+
// eslint-disable-next-line unicorn/prefer-query-selector
|
37
|
+
typeof window === 'undefined' ? undefined : document.getElementById(PWA_INSTALL_ID);
|
38
|
+
|
39
|
+
// add an event listener to control the user close installer action
|
40
|
+
useEffect(() => {
|
41
|
+
if (!pwaInstall) return;
|
42
|
+
|
43
|
+
const handler = (e: Event) => {
|
44
|
+
const event = e as CustomEvent;
|
45
|
+
|
46
|
+
// it means user hide installer
|
47
|
+
if (event.detail.message === 'dismissed') {
|
48
|
+
updateSystemStatus({ hidePWAInstaller: true });
|
49
|
+
}
|
50
|
+
};
|
51
|
+
|
52
|
+
pwaInstall.addEventListener('pwa-user-choice-result-event', handler);
|
53
|
+
return () => {
|
54
|
+
pwaInstall.removeEventListener('pwa-user-choice-result-event', handler);
|
55
|
+
};
|
56
|
+
}, [pwaInstall]);
|
57
|
+
|
58
|
+
// trigger the PWA guide on demand
|
59
|
+
useEffect(() => {
|
60
|
+
if (!canInstall || hidePWAInstaller) return;
|
61
|
+
|
62
|
+
// trigger the pwa installer and register the service worker
|
63
|
+
if (isShowPWAGuide) {
|
64
|
+
install();
|
65
|
+
if ('serviceWorker' in navigator && window.serwist !== undefined) {
|
66
|
+
window.serwist.register();
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}, [canInstall, hidePWAInstaller, isShowPWAGuide]);
|
70
|
+
|
71
|
+
return (
|
72
|
+
<PWA
|
73
|
+
description={t('chat.description', { appName: BRANDING_NAME })}
|
74
|
+
id={PWA_INSTALL_ID}
|
75
|
+
manifest-url={'/manifest.webmanifest'}
|
76
|
+
/>
|
77
|
+
);
|
78
|
+
});
|
79
|
+
|
80
|
+
export default PWAInstall;
|
@@ -1,79 +1,24 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
3
|
import dynamic from 'next/dynamic';
|
4
|
-
import { memo
|
5
|
-
import { useTranslation } from 'react-i18next';
|
4
|
+
import { memo } from 'react';
|
6
5
|
|
7
|
-
import { BRANDING_NAME } from '@/const/branding';
|
8
|
-
import { PWA_INSTALL_ID } from '@/const/layoutTokens';
|
9
|
-
import { usePWAInstall } from '@/hooks/usePWAInstall';
|
10
6
|
import { usePlatform } from '@/hooks/usePlatform';
|
11
|
-
import { useGlobalStore } from '@/store/global';
|
12
|
-
import { systemStatusSelectors } from '@/store/global/selectors';
|
13
7
|
import { useUserStore } from '@/store/user';
|
14
8
|
|
15
|
-
|
16
|
-
const PWA: any = dynamic(() => import('@khmyznikov/pwa-install/dist/pwa-install.react.js'), {
|
9
|
+
const Install: any = dynamic(() => import('./Install'), {
|
17
10
|
ssr: false,
|
18
11
|
});
|
19
12
|
|
20
13
|
const PWAInstall = memo(() => {
|
21
|
-
const { t } = useTranslation('metadata');
|
22
14
|
const { isPWA } = usePlatform();
|
23
|
-
|
24
|
-
const { install, canInstall } = usePWAInstall();
|
25
|
-
|
26
15
|
const isShowPWAGuide = useUserStore((s) => s.isShowPWAGuide);
|
27
|
-
const [hidePWAInstaller, updateSystemStatus] = useGlobalStore((s) => [
|
28
|
-
systemStatusSelectors.hidePWAInstaller(s),
|
29
|
-
s.updateSystemStatus,
|
30
|
-
]);
|
31
|
-
|
32
|
-
// we need to make the pwa installer hidden by default
|
33
|
-
useLayoutEffect(() => {
|
34
|
-
sessionStorage.setItem('pwa-hide-install', 'true');
|
35
|
-
}, []);
|
36
|
-
|
37
|
-
const pwaInstall =
|
38
|
-
// eslint-disable-next-line unicorn/prefer-query-selector
|
39
|
-
typeof window === 'undefined' ? undefined : document.getElementById(PWA_INSTALL_ID);
|
40
|
-
|
41
|
-
// add an event listener to control the user close installer action
|
42
|
-
useEffect(() => {
|
43
|
-
if (!pwaInstall) return;
|
44
|
-
|
45
|
-
const handler = (e: Event) => {
|
46
|
-
const event = e as CustomEvent;
|
47
|
-
|
48
|
-
// it means user hide installer
|
49
|
-
if (event.detail.message === 'dismissed') {
|
50
|
-
updateSystemStatus({ hidePWAInstaller: true });
|
51
|
-
}
|
52
|
-
};
|
53
|
-
|
54
|
-
pwaInstall.addEventListener('pwa-user-choice-result-event', handler);
|
55
|
-
return () => {
|
56
|
-
pwaInstall.removeEventListener('pwa-user-choice-result-event', handler);
|
57
|
-
};
|
58
|
-
}, [pwaInstall]);
|
59
|
-
|
60
|
-
// trigger the PWA guide on demand
|
61
|
-
useEffect(() => {
|
62
|
-
if (!canInstall || hidePWAInstaller) return;
|
63
16
|
|
64
|
-
|
65
|
-
install();
|
66
|
-
}
|
67
|
-
}, [canInstall, hidePWAInstaller, isShowPWAGuide]);
|
17
|
+
if (isPWA || !isShowPWAGuide) return null;
|
68
18
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
description={t('chat.description', { appName: BRANDING_NAME })}
|
73
|
-
id={PWA_INSTALL_ID}
|
74
|
-
manifest-url={'/manifest.webmanifest'}
|
75
|
-
/>
|
76
|
-
);
|
19
|
+
// only when the user is suitable for the pwa install and not install the pwa
|
20
|
+
// then show the installation guide
|
21
|
+
return <Install />;
|
77
22
|
});
|
78
23
|
|
79
24
|
export default PWAInstall;
|
@@ -304,6 +304,30 @@ describe('LobeGoogleAI', () => {
|
|
304
304
|
|
305
305
|
describe('private method', () => {
|
306
306
|
describe('convertContentToGooglePart', () => {
|
307
|
+
it('should handle text type messages', async () => {
|
308
|
+
const result = await instance['convertContentToGooglePart']({
|
309
|
+
type: 'text',
|
310
|
+
text: 'Hello',
|
311
|
+
});
|
312
|
+
expect(result).toEqual({ text: 'Hello' });
|
313
|
+
});
|
314
|
+
|
315
|
+
it('should handle base64 type images', async () => {
|
316
|
+
const base64Image =
|
317
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
318
|
+
const result = await instance['convertContentToGooglePart']({
|
319
|
+
type: 'image_url',
|
320
|
+
image_url: { url: base64Image },
|
321
|
+
});
|
322
|
+
|
323
|
+
expect(result).toEqual({
|
324
|
+
inlineData: {
|
325
|
+
data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
326
|
+
mimeType: 'image/png',
|
327
|
+
},
|
328
|
+
});
|
329
|
+
});
|
330
|
+
|
307
331
|
it('should handle URL type images', async () => {
|
308
332
|
const imageUrl = 'http://example.com/image.png';
|
309
333
|
const mockBase64 = 'mockBase64Data';
|
@@ -357,7 +381,7 @@ describe('LobeGoogleAI', () => {
|
|
357
381
|
{ content: 'Hi', role: 'assistant' },
|
358
382
|
];
|
359
383
|
|
360
|
-
const contents = await instance['buildGoogleMessages'](messages, 'gemini-
|
384
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-1.0');
|
361
385
|
|
362
386
|
expect(contents).toHaveLength(3);
|
363
387
|
expect(contents).toEqual([
|
@@ -373,7 +397,7 @@ describe('LobeGoogleAI', () => {
|
|
373
397
|
{ content: 'Who are you', role: 'user' },
|
374
398
|
];
|
375
399
|
|
376
|
-
const contents = await instance['buildGoogleMessages'](messages, 'gemini-
|
400
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-1.0');
|
377
401
|
|
378
402
|
expect(contents).toHaveLength(3);
|
379
403
|
expect(contents).toEqual([
|
@@ -487,9 +511,6 @@ describe('LobeGoogleAI', () => {
|
|
487
511
|
});
|
488
512
|
});
|
489
513
|
|
490
|
-
// 类似地添加 array/string/number/boolean 类型schema的测试用例
|
491
|
-
// ...
|
492
|
-
|
493
514
|
it('should correctly convert nested schema', () => {
|
494
515
|
const schema: JSONSchema7 = {
|
495
516
|
type: 'object',
|
@@ -523,6 +544,36 @@ describe('LobeGoogleAI', () => {
|
|
523
544
|
},
|
524
545
|
});
|
525
546
|
});
|
547
|
+
|
548
|
+
it('should correctly convert array schema', () => {
|
549
|
+
const schema: JSONSchema7 = {
|
550
|
+
type: 'array',
|
551
|
+
items: { type: 'string' },
|
552
|
+
};
|
553
|
+
const converted = instance['convertSchemaObject'](schema);
|
554
|
+
expect(converted).toEqual({
|
555
|
+
type: FunctionDeclarationSchemaType.ARRAY,
|
556
|
+
items: { type: FunctionDeclarationSchemaType.STRING },
|
557
|
+
});
|
558
|
+
});
|
559
|
+
|
560
|
+
it('should correctly convert string schema', () => {
|
561
|
+
const schema: JSONSchema7 = { type: 'string' };
|
562
|
+
const converted = instance['convertSchemaObject'](schema);
|
563
|
+
expect(converted).toEqual({ type: FunctionDeclarationSchemaType.STRING });
|
564
|
+
});
|
565
|
+
|
566
|
+
it('should correctly convert number schema', () => {
|
567
|
+
const schema: JSONSchema7 = { type: 'number' };
|
568
|
+
const converted = instance['convertSchemaObject'](schema);
|
569
|
+
expect(converted).toEqual({ type: FunctionDeclarationSchemaType.NUMBER });
|
570
|
+
});
|
571
|
+
|
572
|
+
it('should correctly convert boolean schema', () => {
|
573
|
+
const schema: JSONSchema7 = { type: 'boolean' };
|
574
|
+
const converted = instance['convertSchemaObject'](schema);
|
575
|
+
expect(converted).toEqual({ type: FunctionDeclarationSchemaType.BOOLEAN });
|
576
|
+
});
|
526
577
|
});
|
527
578
|
|
528
579
|
describe('convertOAIMessagesToGoogleMessage', () => {
|
@@ -592,6 +643,49 @@ describe('LobeGoogleAI', () => {
|
|
592
643
|
],
|
593
644
|
});
|
594
645
|
});
|
646
|
+
|
647
|
+
it('should correctly convert function call message', async () => {
|
648
|
+
const message = {
|
649
|
+
role: 'assistant',
|
650
|
+
tool_calls: [
|
651
|
+
{
|
652
|
+
id: 'call_1',
|
653
|
+
function: {
|
654
|
+
name: 'get_current_weather',
|
655
|
+
arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
|
656
|
+
},
|
657
|
+
type: 'function',
|
658
|
+
},
|
659
|
+
],
|
660
|
+
} as OpenAIChatMessage;
|
661
|
+
|
662
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
663
|
+
expect(converted).toEqual({
|
664
|
+
role: 'function',
|
665
|
+
parts: [
|
666
|
+
{
|
667
|
+
functionCall: {
|
668
|
+
name: 'get_current_weather',
|
669
|
+
args: { location: 'London', unit: 'celsius' },
|
670
|
+
},
|
671
|
+
},
|
672
|
+
],
|
673
|
+
});
|
674
|
+
});
|
675
|
+
|
676
|
+
it('should correctly handle empty content', async () => {
|
677
|
+
const message: OpenAIChatMessage = {
|
678
|
+
role: 'user',
|
679
|
+
content: '' as any, // explicitly set as empty string
|
680
|
+
};
|
681
|
+
|
682
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
683
|
+
|
684
|
+
expect(converted).toEqual({
|
685
|
+
role: 'user',
|
686
|
+
parts: [{ text: '' }],
|
687
|
+
});
|
688
|
+
});
|
595
689
|
});
|
596
690
|
});
|
597
691
|
});
|