@lobehub/chat 1.139.2 → 1.139.4
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/.github/workflows/desktop-pr-build.yml +2 -2
- package/.github/workflows/docker-database.yml +1 -1
- package/.github/workflows/docker-pglite.yml +1 -1
- package/.github/workflows/docker.yml +1 -1
- package/.github/workflows/release-desktop-beta.yml +2 -2
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +1 -1
- package/changelog/v1.json +18 -0
- package/docs/development/basic/work-with-server-side-database.mdx +5 -5
- package/docs/development/basic/work-with-server-side-database.zh-CN.mdx +5 -5
- package/docs/development/tests/integration-testing.zh-CN.mdx +399 -0
- package/locales/ar/chat.json +3 -1
- package/locales/bg-BG/chat.json +3 -1
- package/locales/de-DE/chat.json +3 -1
- package/locales/en-US/chat.json +3 -1
- package/locales/es-ES/chat.json +3 -1
- package/locales/fa-IR/chat.json +3 -1
- package/locales/fr-FR/chat.json +3 -1
- package/locales/it-IT/chat.json +3 -1
- package/locales/ja-JP/chat.json +3 -1
- package/locales/ko-KR/chat.json +3 -1
- package/locales/nl-NL/chat.json +3 -1
- package/locales/pl-PL/chat.json +3 -1
- package/locales/pt-BR/chat.json +3 -1
- package/locales/ru-RU/chat.json +3 -1
- package/locales/tr-TR/chat.json +3 -1
- package/locales/vi-VN/chat.json +3 -1
- package/locales/zh-CN/chat.json +3 -1
- package/locales/zh-TW/chat.json +3 -1
- package/package.json +2 -2
- package/packages/database/package.json +2 -1
- package/packages/database/tests/test-utils.ts +1 -0
- package/packages/types/src/message/chat.ts +1 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatMinimap/index.tsx +28 -9
- package/src/features/DevPanel/index.tsx +7 -1
- package/src/features/ElectronTitlebar/UpdateNotification.tsx +19 -2
- package/src/locales/default/chat.ts +2 -0
- package/src/server/routers/lambda/{agent.test.ts → __tests__/agent.test.ts} +1 -1
- package/src/server/routers/lambda/__tests__/aiChat.test.ts +259 -0
- package/src/server/routers/lambda/{aiModel.test.ts → __tests__/aiModel.test.ts} +1 -1
- package/src/server/routers/lambda/{aiProvider.test.ts → __tests__/aiProvider.test.ts} +1 -1
- package/src/server/routers/lambda/{generation.test.ts → __tests__/generation.test.ts} +1 -1
- package/src/server/routers/lambda/{generationBatch.test.ts → __tests__/generationBatch.test.ts} +1 -1
- package/src/server/routers/lambda/{generationTopic.test.ts → __tests__/generationTopic.test.ts} +1 -1
- package/src/server/routers/lambda/__tests__/integration/README.md +110 -0
- package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +545 -0
- package/src/server/routers/lambda/__tests__/integration/setup.ts +36 -0
- package/src/server/routers/lambda/{user.test.ts → __tests__/user.test.ts} +1 -1
- package/src/server/routers/lambda/aiChat.ts +2 -0
- package/src/store/chat/slices/message/action.test.ts +92 -0
- package/src/store/chat/slices/message/action.ts +3 -1
- package/src/server/routers/lambda/aiChat.test.ts +0 -108
package/locales/fa-IR/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "رفتن به پیام شماره {{index}}",
|
|
230
230
|
"nextMessage": "پیام بعدی",
|
|
231
|
-
"previousMessage": "پیام قبلی"
|
|
231
|
+
"previousMessage": "پیام قبلی",
|
|
232
|
+
"senderAssistant": "دستیار",
|
|
233
|
+
"senderUser": "شما"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "دستیار جدید",
|
|
234
236
|
"newGroupChat": "ایجاد چت گروهی جدید",
|
package/locales/fr-FR/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Aller au message n° {{index}}",
|
|
230
230
|
"nextMessage": "Message suivant",
|
|
231
|
-
"previousMessage": "Message précédent"
|
|
231
|
+
"previousMessage": "Message précédent",
|
|
232
|
+
"senderAssistant": "Assistant",
|
|
233
|
+
"senderUser": "Vous"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Nouvel agent",
|
|
234
236
|
"newGroupChat": "Nouveau groupe de discussion",
|
package/locales/it-IT/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Vai al messaggio n. {{index}}",
|
|
230
230
|
"nextMessage": "Messaggio successivo",
|
|
231
|
-
"previousMessage": "Messaggio precedente"
|
|
231
|
+
"previousMessage": "Messaggio precedente",
|
|
232
|
+
"senderAssistant": "Assistente",
|
|
233
|
+
"senderUser": "Tu"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Nuovo assistente",
|
|
234
236
|
"newGroupChat": "Nuova chat di gruppo",
|
package/locales/ja-JP/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "メッセージ {{index}} へジャンプ",
|
|
230
230
|
"nextMessage": "次のメッセージ",
|
|
231
|
-
"previousMessage": "前のメッセージ"
|
|
231
|
+
"previousMessage": "前のメッセージ",
|
|
232
|
+
"senderAssistant": "アシスタント",
|
|
233
|
+
"senderUser": "あなた"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "新しいエージェント",
|
|
234
236
|
"newGroupChat": "新しいグループチャットを作成",
|
package/locales/ko-KR/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "{{index}}번째 메시지로 이동",
|
|
230
230
|
"nextMessage": "다음 메시지",
|
|
231
|
-
"previousMessage": "이전 메시지"
|
|
231
|
+
"previousMessage": "이전 메시지",
|
|
232
|
+
"senderAssistant": "도우미",
|
|
233
|
+
"senderUser": "당신"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "새 도우미",
|
|
234
236
|
"newGroupChat": "새 그룹 채팅 만들기",
|
package/locales/nl-NL/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Ga naar bericht {{index}}",
|
|
230
230
|
"nextMessage": "Volgend bericht",
|
|
231
|
-
"previousMessage": "Vorig bericht"
|
|
231
|
+
"previousMessage": "Vorig bericht",
|
|
232
|
+
"senderAssistant": "Assistent",
|
|
233
|
+
"senderUser": "Jij"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Nieuwe assistent",
|
|
234
236
|
"newGroupChat": "Nieuwe groepschat",
|
package/locales/pl-PL/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Przejdź do wiadomości nr {{index}}",
|
|
230
230
|
"nextMessage": "Następna wiadomość",
|
|
231
|
-
"previousMessage": "Poprzednia wiadomość"
|
|
231
|
+
"previousMessage": "Poprzednia wiadomość",
|
|
232
|
+
"senderAssistant": "Asystent",
|
|
233
|
+
"senderUser": "Ty"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Nowy asystent",
|
|
234
236
|
"newGroupChat": "Utwórz nowy czat grupowy",
|
package/locales/pt-BR/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Ir para a mensagem nº {{index}}",
|
|
230
230
|
"nextMessage": "Próxima mensagem",
|
|
231
|
-
"previousMessage": "Mensagem anterior"
|
|
231
|
+
"previousMessage": "Mensagem anterior",
|
|
232
|
+
"senderAssistant": "Assistente",
|
|
233
|
+
"senderUser": "Você"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Novo Assistente",
|
|
234
236
|
"newGroupChat": "Criar novo grupo",
|
package/locales/ru-RU/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Перейти к сообщению № {{index}}",
|
|
230
230
|
"nextMessage": "Следующее сообщение",
|
|
231
|
-
"previousMessage": "Предыдущее сообщение"
|
|
231
|
+
"previousMessage": "Предыдущее сообщение",
|
|
232
|
+
"senderAssistant": "Ассистент",
|
|
233
|
+
"senderUser": "Вы"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Создать помощника",
|
|
234
236
|
"newGroupChat": "Создать групповой чат",
|
package/locales/tr-TR/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "{{index}} numaralı mesaja atla",
|
|
230
230
|
"nextMessage": "Sonraki mesaj",
|
|
231
|
-
"previousMessage": "Önceki mesaj"
|
|
231
|
+
"previousMessage": "Önceki mesaj",
|
|
232
|
+
"senderAssistant": "Asistan",
|
|
233
|
+
"senderUser": "Sen"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Yeni Asistan",
|
|
234
236
|
"newGroupChat": "Yeni grup sohbeti oluştur",
|
package/locales/vi-VN/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Chuyển đến tin nhắn thứ {{index}}",
|
|
230
230
|
"nextMessage": "Tin nhắn tiếp theo",
|
|
231
|
-
"previousMessage": "Tin nhắn trước"
|
|
231
|
+
"previousMessage": "Tin nhắn trước",
|
|
232
|
+
"senderAssistant": "Trợ lý",
|
|
233
|
+
"senderUser": "Bạn"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Tạo trợ lý mới",
|
|
234
236
|
"newGroupChat": "Tạo nhóm mới",
|
package/locales/zh-CN/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "跳转至第 {{index}} 条消息",
|
|
230
230
|
"nextMessage": "下一条消息",
|
|
231
|
-
"previousMessage": "上一条消息"
|
|
231
|
+
"previousMessage": "上一条消息",
|
|
232
|
+
"senderAssistant": "助手",
|
|
233
|
+
"senderUser": "你"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "新建助手",
|
|
234
236
|
"newGroupChat": "新建群聊",
|
package/locales/zh-TW/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "跳轉至第 {{index}} 條訊息",
|
|
230
230
|
"nextMessage": "下一條訊息",
|
|
231
|
-
"previousMessage": "上一條訊息"
|
|
231
|
+
"previousMessage": "上一條訊息",
|
|
232
|
+
"senderAssistant": "助理",
|
|
233
|
+
"senderUser": "您"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "新建助手",
|
|
234
236
|
"newGroupChat": "建立群組",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.139.
|
|
3
|
+
"version": "1.139.4",
|
|
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",
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"oidc-provider": "^9.5.1",
|
|
239
239
|
"ollama": "^0.6.0",
|
|
240
240
|
"openai": "^4.104.0",
|
|
241
|
-
"openapi-fetch": "^0.
|
|
241
|
+
"openapi-fetch": "^0.14.0",
|
|
242
242
|
"partial-json": "^0.1.7",
|
|
243
243
|
"path-browserify-esm": "^1.0.6",
|
|
244
244
|
"pdf-parse": "^1.1.1",
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
7
|
-
"./schemas": "./src/schemas/index.ts"
|
|
7
|
+
"./schemas": "./src/schemas/index.ts",
|
|
8
|
+
"./test-utils": "./tests/test-utils.ts"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"test": "npm run test:client-db && npm run test:server-db",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../src/models/__tests__/_util';
|
package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatMinimap/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@lobehub/ui';
|
|
4
|
-
import { Tooltip } from 'antd';
|
|
4
|
+
import { Popover, Tooltip } from 'antd';
|
|
5
5
|
import { createStyles, useTheme } from 'antd-style';
|
|
6
6
|
import debug from 'debug';
|
|
7
7
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
@@ -114,6 +114,19 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
|
114
114
|
indicatorContentActive: css`
|
|
115
115
|
background: ${token.colorPrimary};
|
|
116
116
|
`,
|
|
117
|
+
popoverContent: css`
|
|
118
|
+
max-width: 300px;
|
|
119
|
+
`,
|
|
120
|
+
popoverLabel: css`
|
|
121
|
+
margin-block-end: 4px;
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
color: ${token.colorTextSecondary};
|
|
125
|
+
`,
|
|
126
|
+
popoverText: css`
|
|
127
|
+
color: ${token.colorText};
|
|
128
|
+
word-break: break-word;
|
|
129
|
+
`,
|
|
117
130
|
rail: css`
|
|
118
131
|
pointer-events: auto;
|
|
119
132
|
|
|
@@ -174,6 +187,7 @@ const getPreviewText = (content: string | undefined) => {
|
|
|
174
187
|
interface MinimapIndicator {
|
|
175
188
|
id: string;
|
|
176
189
|
preview: string;
|
|
190
|
+
role: 'user' | 'assistant';
|
|
177
191
|
virtuosoIndex: number;
|
|
178
192
|
width: number;
|
|
179
193
|
}
|
|
@@ -203,6 +217,7 @@ const ChatMinimap = () => {
|
|
|
203
217
|
acc.push({
|
|
204
218
|
id: message.id,
|
|
205
219
|
preview: getPreviewText(message.content),
|
|
220
|
+
role: message.role,
|
|
206
221
|
virtuosoIndex,
|
|
207
222
|
width: getIndicatorWidth(message.content),
|
|
208
223
|
});
|
|
@@ -317,16 +332,20 @@ const ChatMinimap = () => {
|
|
|
317
332
|
</button>
|
|
318
333
|
</Tooltip>
|
|
319
334
|
<Flexbox className={styles.railContent}>
|
|
320
|
-
{indicators.map(({ id, width, preview, virtuosoIndex }, position) => {
|
|
335
|
+
{indicators.map(({ id, width, preview, role, virtuosoIndex }, position) => {
|
|
321
336
|
const isActive = activeIndicatorPosition === position;
|
|
337
|
+
const senderLabel =
|
|
338
|
+
role === 'user' ? t('minimap.senderUser') : t('minimap.senderAssistant');
|
|
339
|
+
|
|
340
|
+
const popoverContent = preview ? (
|
|
341
|
+
<div className={styles.popoverContent}>
|
|
342
|
+
<div className={styles.popoverLabel}>{senderLabel}</div>
|
|
343
|
+
<div className={styles.popoverText}>{preview}</div>
|
|
344
|
+
</div>
|
|
345
|
+
) : undefined;
|
|
322
346
|
|
|
323
347
|
return (
|
|
324
|
-
<
|
|
325
|
-
key={id}
|
|
326
|
-
mouseEnterDelay={0.1}
|
|
327
|
-
placement={'left'}
|
|
328
|
-
title={preview || undefined}
|
|
329
|
-
>
|
|
348
|
+
<Popover content={popoverContent} key={id} mouseEnterDelay={0.1} placement={'left'}>
|
|
330
349
|
<button
|
|
331
350
|
aria-current={isActive ? 'true' : undefined}
|
|
332
351
|
aria-label={t('minimap.jumpToMessage', { index: position + 1 })}
|
|
@@ -344,7 +363,7 @@ const ChatMinimap = () => {
|
|
|
344
363
|
)}
|
|
345
364
|
/>
|
|
346
365
|
</button>
|
|
347
|
-
</
|
|
366
|
+
</Popover>
|
|
348
367
|
);
|
|
349
368
|
})}
|
|
350
369
|
</Flexbox>
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import { BookText, Cog, DatabaseIcon, FlagIcon, GlobeLockIcon } from 'lucide-react';
|
|
4
|
+
import dynamic from 'next/dynamic';
|
|
2
5
|
|
|
3
6
|
import CacheViewer from './CacheViewer';
|
|
4
7
|
import FeatureFlagViewer from './FeatureFlagViewer';
|
|
5
8
|
import MetadataViewer from './MetadataViewer';
|
|
6
9
|
import PostgresViewer from './PostgresViewer';
|
|
7
10
|
import SystemInspector from './SystemInspector';
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
const FloatPanel = dynamic(() => import('./features/FloatPanel'), {
|
|
13
|
+
ssr: false,
|
|
14
|
+
});
|
|
9
15
|
|
|
10
16
|
const DevPanel = () => (
|
|
11
17
|
<FloatPanel
|
|
@@ -39,6 +39,7 @@ export const UpdateNotification: React.FC = () => {
|
|
|
39
39
|
'unconfirm' | 'installLater' | 'installNow' | null
|
|
40
40
|
>('unconfirm');
|
|
41
41
|
const [detailVisible, setDetailVisible] = useState(false);
|
|
42
|
+
const [isInstalling, setIsInstalling] = useState(false);
|
|
42
43
|
|
|
43
44
|
useWatchBroadcast('updateDownloaded', (info: UpdateInfo) => {
|
|
44
45
|
setUpdateInfo(info);
|
|
@@ -110,7 +111,15 @@ export const UpdateNotification: React.FC = () => {
|
|
|
110
111
|
{t('updater.later')}
|
|
111
112
|
</Button>
|
|
112
113
|
|
|
113
|
-
<Button
|
|
114
|
+
<Button
|
|
115
|
+
loading={isInstalling}
|
|
116
|
+
onClick={() => {
|
|
117
|
+
setIsInstalling(true);
|
|
118
|
+
autoUpdateService.installNow();
|
|
119
|
+
}}
|
|
120
|
+
size="small"
|
|
121
|
+
type="primary"
|
|
122
|
+
>
|
|
114
123
|
{t('updater.upgradeNow')}
|
|
115
124
|
</Button>
|
|
116
125
|
</div>
|
|
@@ -137,7 +146,15 @@ export const UpdateNotification: React.FC = () => {
|
|
|
137
146
|
<Button onClick={() => autoUpdateService.installLater()} size="small">
|
|
138
147
|
{t('updater.installLater')}
|
|
139
148
|
</Button>
|
|
140
|
-
<Button
|
|
149
|
+
<Button
|
|
150
|
+
loading={isInstalling}
|
|
151
|
+
onClick={() => {
|
|
152
|
+
setIsInstalling(true);
|
|
153
|
+
autoUpdateService.installNow();
|
|
154
|
+
}}
|
|
155
|
+
size="small"
|
|
156
|
+
type="primary"
|
|
157
|
+
>
|
|
141
158
|
{t('updater.restartAndInstall', '立即安装')}
|
|
142
159
|
</Button>
|
|
143
160
|
</div>
|
|
@@ -12,7 +12,7 @@ import { serverDB } from '@/database/server';
|
|
|
12
12
|
import { AgentService } from '@/server/services/agent';
|
|
13
13
|
import { KnowledgeType } from '@/types/knowledgeBase';
|
|
14
14
|
|
|
15
|
-
import { agentRouter } from '
|
|
15
|
+
import { agentRouter } from '../agent';
|
|
16
16
|
|
|
17
17
|
vi.mock('@/database/models/user', () => ({
|
|
18
18
|
UserModel: {
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { MessageModel } from '@/database/models/message';
|
|
5
|
+
import { TopicModel } from '@/database/models/topic';
|
|
6
|
+
import { AiChatService } from '@/server/services/aiChat';
|
|
7
|
+
|
|
8
|
+
import { aiChatRouter } from '../aiChat';
|
|
9
|
+
|
|
10
|
+
vi.mock('@/database/models/message');
|
|
11
|
+
vi.mock('@/database/models/topic');
|
|
12
|
+
vi.mock('@/server/services/aiChat');
|
|
13
|
+
vi.mock('@/server/services/file', () => ({
|
|
14
|
+
FileService: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
vi.mock('@/utils/server', () => ({
|
|
17
|
+
getXorPayload: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('@/server/modules/ModelRuntime', () => ({
|
|
20
|
+
initModelRuntimeWithUserPayload: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
describe('aiChatRouter', () => {
|
|
24
|
+
const mockCtx = { userId: 'u1' };
|
|
25
|
+
|
|
26
|
+
it('should create topic optionally, create user/assistant messages, and return payload', async () => {
|
|
27
|
+
const mockCreateTopic = vi.fn().mockResolvedValue({ id: 't1' });
|
|
28
|
+
const mockCreateMessage = vi
|
|
29
|
+
.fn()
|
|
30
|
+
.mockResolvedValueOnce({ id: 'm-user' })
|
|
31
|
+
.mockResolvedValueOnce({ id: 'm-assistant' });
|
|
32
|
+
const mockGet = vi
|
|
33
|
+
.fn()
|
|
34
|
+
.mockResolvedValue({ messages: [{ id: 'm-user' }, { id: 'm-assistant' }], topics: [{}] });
|
|
35
|
+
|
|
36
|
+
vi.mocked(TopicModel).mockImplementation(() => ({ create: mockCreateTopic }) as any);
|
|
37
|
+
vi.mocked(MessageModel).mockImplementation(() => ({ create: mockCreateMessage }) as any);
|
|
38
|
+
vi.mocked(AiChatService).mockImplementation(() => ({ getMessagesAndTopics: mockGet }) as any);
|
|
39
|
+
|
|
40
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
41
|
+
|
|
42
|
+
const input = {
|
|
43
|
+
newAssistantMessage: { model: 'gpt-4o', provider: 'openai' },
|
|
44
|
+
newTopic: { title: 'T', topicMessageIds: ['a', 'b'] },
|
|
45
|
+
newUserMessage: { content: 'hi', files: ['f1'] },
|
|
46
|
+
sessionId: 's1',
|
|
47
|
+
} as any;
|
|
48
|
+
|
|
49
|
+
const res = await caller.sendMessageInServer(input);
|
|
50
|
+
|
|
51
|
+
expect(mockCreateTopic).toHaveBeenCalledWith({
|
|
52
|
+
messages: ['a', 'b'],
|
|
53
|
+
sessionId: 's1',
|
|
54
|
+
title: 'T',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(1, {
|
|
58
|
+
content: 'hi',
|
|
59
|
+
files: ['f1'],
|
|
60
|
+
role: 'user',
|
|
61
|
+
sessionId: 's1',
|
|
62
|
+
topicId: 't1',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(
|
|
66
|
+
2,
|
|
67
|
+
expect.objectContaining({
|
|
68
|
+
content: expect.any(String),
|
|
69
|
+
fromModel: 'gpt-4o',
|
|
70
|
+
parentId: 'm-user',
|
|
71
|
+
role: 'assistant',
|
|
72
|
+
sessionId: 's1',
|
|
73
|
+
topicId: 't1',
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(mockGet).toHaveBeenCalledWith({ includeTopic: true, sessionId: 's1', topicId: 't1' });
|
|
78
|
+
expect(res.assistantMessageId).toBe('m-assistant');
|
|
79
|
+
expect(res.userMessageId).toBe('m-user');
|
|
80
|
+
expect(res.isCreateNewTopic).toBe(true);
|
|
81
|
+
expect(res.topicId).toBe('t1');
|
|
82
|
+
expect(res.messages?.length).toBe(2);
|
|
83
|
+
expect(res.topics?.length).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should reuse existing topic when topicId provided', async () => {
|
|
87
|
+
const mockCreateMessage = vi
|
|
88
|
+
.fn()
|
|
89
|
+
.mockResolvedValueOnce({ id: 'm-user' })
|
|
90
|
+
.mockResolvedValueOnce({ id: 'm-assistant' });
|
|
91
|
+
const mockGet = vi.fn().mockResolvedValue({ messages: [], topics: undefined });
|
|
92
|
+
|
|
93
|
+
vi.mocked(MessageModel).mockImplementation(() => ({ create: mockCreateMessage }) as any);
|
|
94
|
+
vi.mocked(AiChatService).mockImplementation(() => ({ getMessagesAndTopics: mockGet }) as any);
|
|
95
|
+
|
|
96
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
97
|
+
|
|
98
|
+
const res = await caller.sendMessageInServer({
|
|
99
|
+
newAssistantMessage: { model: 'gpt-4o', provider: 'openai' },
|
|
100
|
+
newUserMessage: { content: 'hi' },
|
|
101
|
+
sessionId: 's1',
|
|
102
|
+
topicId: 't-exist',
|
|
103
|
+
} as any);
|
|
104
|
+
|
|
105
|
+
expect(mockCreateMessage).toHaveBeenCalled();
|
|
106
|
+
expect(mockGet).toHaveBeenCalledWith({
|
|
107
|
+
includeTopic: false,
|
|
108
|
+
sessionId: 's1',
|
|
109
|
+
topicId: 't-exist',
|
|
110
|
+
});
|
|
111
|
+
expect(res.isCreateNewTopic).toBe(false);
|
|
112
|
+
expect(res.topicId).toBe('t-exist');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should pass threadId to both user and assistant messages when provided', async () => {
|
|
116
|
+
const mockCreateMessage = vi
|
|
117
|
+
.fn()
|
|
118
|
+
.mockResolvedValueOnce({ id: 'm-user' })
|
|
119
|
+
.mockResolvedValueOnce({ id: 'm-assistant' });
|
|
120
|
+
const mockGet = vi.fn().mockResolvedValue({ messages: [], topics: undefined });
|
|
121
|
+
|
|
122
|
+
vi.mocked(MessageModel).mockImplementation(() => ({ create: mockCreateMessage }) as any);
|
|
123
|
+
vi.mocked(AiChatService).mockImplementation(() => ({ getMessagesAndTopics: mockGet }) as any);
|
|
124
|
+
|
|
125
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
126
|
+
|
|
127
|
+
await caller.sendMessageInServer({
|
|
128
|
+
newAssistantMessage: { model: 'gpt-4o', provider: 'openai' },
|
|
129
|
+
newUserMessage: { content: 'hi' },
|
|
130
|
+
sessionId: 's1',
|
|
131
|
+
threadId: 'thread-123',
|
|
132
|
+
topicId: 't1',
|
|
133
|
+
} as any);
|
|
134
|
+
|
|
135
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(1, {
|
|
136
|
+
content: 'hi',
|
|
137
|
+
role: 'user',
|
|
138
|
+
sessionId: 's1',
|
|
139
|
+
threadId: 'thread-123',
|
|
140
|
+
topicId: 't1',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(
|
|
144
|
+
2,
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
parentId: 'm-user',
|
|
147
|
+
role: 'assistant',
|
|
148
|
+
sessionId: 's1',
|
|
149
|
+
threadId: 'thread-123',
|
|
150
|
+
topicId: 't1',
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('outputJSON', () => {
|
|
156
|
+
it('should successfully generate structured output', async () => {
|
|
157
|
+
const { getXorPayload } = await import('@/utils/server');
|
|
158
|
+
const { initModelRuntimeWithUserPayload } = await import('@/server/modules/ModelRuntime');
|
|
159
|
+
|
|
160
|
+
const mockPayload = { apiKey: 'test-key' };
|
|
161
|
+
const mockResult = { object: { name: 'John', age: 30 } };
|
|
162
|
+
const mockGenerateObject = vi.fn().mockResolvedValue(mockResult);
|
|
163
|
+
|
|
164
|
+
vi.mocked(getXorPayload).mockReturnValue(mockPayload);
|
|
165
|
+
vi.mocked(initModelRuntimeWithUserPayload).mockReturnValue({
|
|
166
|
+
generateObject: mockGenerateObject,
|
|
167
|
+
} as any);
|
|
168
|
+
|
|
169
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
170
|
+
|
|
171
|
+
const input = {
|
|
172
|
+
keyVaultsPayload: 'encrypted-payload',
|
|
173
|
+
messages: [{ content: 'test', role: 'user' }],
|
|
174
|
+
model: 'gpt-4o',
|
|
175
|
+
provider: 'openai',
|
|
176
|
+
schema: {
|
|
177
|
+
name: 'Person',
|
|
178
|
+
schema: {
|
|
179
|
+
type: 'object' as const,
|
|
180
|
+
properties: { name: { type: 'string' }, age: { type: 'number' } },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = await caller.outputJSON(input);
|
|
186
|
+
|
|
187
|
+
expect(getXorPayload).toHaveBeenCalledWith('encrypted-payload');
|
|
188
|
+
expect(initModelRuntimeWithUserPayload).toHaveBeenCalledWith('openai', mockPayload);
|
|
189
|
+
expect(mockGenerateObject).toHaveBeenCalledWith({
|
|
190
|
+
messages: input.messages,
|
|
191
|
+
model: 'gpt-4o',
|
|
192
|
+
schema: input.schema,
|
|
193
|
+
tools: undefined,
|
|
194
|
+
});
|
|
195
|
+
expect(result).toEqual(mockResult);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should throw error when keyVaultsPayload is invalid', async () => {
|
|
199
|
+
const { getXorPayload } = await import('@/utils/server');
|
|
200
|
+
|
|
201
|
+
vi.mocked(getXorPayload).mockReturnValue(undefined as any);
|
|
202
|
+
|
|
203
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
204
|
+
|
|
205
|
+
const input = {
|
|
206
|
+
keyVaultsPayload: 'invalid-payload',
|
|
207
|
+
messages: [],
|
|
208
|
+
model: 'gpt-4o',
|
|
209
|
+
provider: 'openai',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
await expect(caller.outputJSON(input)).rejects.toThrow('keyVaultsPayload is not correct');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should handle tools parameter when provided', async () => {
|
|
216
|
+
const { getXorPayload } = await import('@/utils/server');
|
|
217
|
+
const { initModelRuntimeWithUserPayload } = await import('@/server/modules/ModelRuntime');
|
|
218
|
+
|
|
219
|
+
const mockPayload = { apiKey: 'test-key' };
|
|
220
|
+
const mockTools = [
|
|
221
|
+
{
|
|
222
|
+
type: 'function' as const,
|
|
223
|
+
function: {
|
|
224
|
+
name: 'test',
|
|
225
|
+
parameters: {
|
|
226
|
+
type: 'object' as const,
|
|
227
|
+
properties: { input: { type: 'string' } },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
const mockGenerateObject = vi.fn().mockResolvedValue({ object: {} });
|
|
233
|
+
|
|
234
|
+
vi.mocked(getXorPayload).mockReturnValue(mockPayload);
|
|
235
|
+
vi.mocked(initModelRuntimeWithUserPayload).mockReturnValue({
|
|
236
|
+
generateObject: mockGenerateObject,
|
|
237
|
+
} as any);
|
|
238
|
+
|
|
239
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
|
240
|
+
|
|
241
|
+
const input = {
|
|
242
|
+
keyVaultsPayload: 'encrypted-payload',
|
|
243
|
+
messages: [],
|
|
244
|
+
model: 'gpt-4o',
|
|
245
|
+
provider: 'openai',
|
|
246
|
+
tools: mockTools,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
await caller.outputJSON(input);
|
|
250
|
+
|
|
251
|
+
expect(mockGenerateObject).toHaveBeenCalledWith({
|
|
252
|
+
messages: [],
|
|
253
|
+
model: 'gpt-4o',
|
|
254
|
+
schema: undefined,
|
|
255
|
+
tools: mockTools,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -4,7 +4,7 @@ import { AiModelModel } from '@/database/models/aiModel';
|
|
|
4
4
|
import { UserModel } from '@/database/models/user';
|
|
5
5
|
import { AiInfraRepos } from '@/database/repositories/aiInfra';
|
|
6
6
|
|
|
7
|
-
import { aiModelRouter } from '
|
|
7
|
+
import { aiModelRouter } from '../aiModel';
|
|
8
8
|
|
|
9
9
|
vi.mock('@/database/models/aiModel');
|
|
10
10
|
vi.mock('@/database/models/user');
|
|
@@ -6,7 +6,7 @@ import { getServerGlobalConfig } from '@/server/globalConfig';
|
|
|
6
6
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
|
7
7
|
import { AiProviderDetailItem, AiProviderRuntimeState } from '@/types/aiProvider';
|
|
8
8
|
|
|
9
|
-
import { aiProviderRouter } from '
|
|
9
|
+
import { aiProviderRouter } from '../aiProvider';
|
|
10
10
|
|
|
11
11
|
vi.mock('@/server/globalConfig');
|
|
12
12
|
vi.mock('@/server/modules/KeyVaultsEncrypt');
|
|
@@ -6,7 +6,7 @@ import { GenerationModel } from '@/database/models/generation';
|
|
|
6
6
|
import { FileService } from '@/server/services/file';
|
|
7
7
|
import { AsyncTaskStatus } from '@/types/asyncTask';
|
|
8
8
|
|
|
9
|
-
import { generationRouter } from '
|
|
9
|
+
import { generationRouter } from '../generation';
|
|
10
10
|
|
|
11
11
|
vi.mock('@/database/models/asyncTask');
|
|
12
12
|
vi.mock('@/database/models/generation');
|
package/src/server/routers/lambda/{generationBatch.test.ts → __tests__/generationBatch.test.ts}
RENAMED
|
@@ -4,7 +4,7 @@ import { GenerationBatchModel } from '@/database/models/generationBatch';
|
|
|
4
4
|
import { GenerationBatchItem } from '@/database/schemas/generation';
|
|
5
5
|
import { FileService } from '@/server/services/file';
|
|
6
6
|
|
|
7
|
-
import { generationBatchRouter } from '
|
|
7
|
+
import { generationBatchRouter } from '../generationBatch';
|
|
8
8
|
|
|
9
9
|
vi.mock('@/database/models/generationBatch');
|
|
10
10
|
vi.mock('@/server/services/file');
|