@lobehub/chat 1.121.0 → 1.122.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.
- package/CHANGELOG.md +51 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +24 -2
- package/changelog/v1.json +18 -0
- package/next.config.ts +1 -0
- package/package.json +1 -1
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/session.ts +4 -11
- package/packages/database/src/models/__tests__/message.test.ts +41 -0
- package/packages/database/src/models/message.ts +21 -13
- package/packages/database/src/models/topic.ts +4 -9
- package/packages/types/src/aiChat.ts +55 -0
- package/packages/types/src/index.ts +4 -0
- package/packages/types/src/message/base.ts +17 -4
- package/packages/types/src/message/chat.ts +1 -15
- package/packages/types/src/message/index.ts +1 -0
- package/packages/types/src/message/rag.ts +21 -0
- package/packages/utils/src/index.ts +1 -0
- package/packages/utils/src/object.test.ts +11 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +3 -3
- package/src/server/routers/lambda/__tests__/message.test.ts +30 -0
- package/src/server/routers/lambda/aiChat.test.ts +107 -0
- package/src/server/routers/lambda/aiChat.ts +80 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/message.ts +7 -0
- package/src/server/services/aiChat/index.test.ts +57 -0
- package/src/server/services/aiChat/index.ts +36 -0
- package/src/services/aiChat.ts +12 -0
- package/src/services/message/_deprecated.ts +4 -0
- package/src/services/message/client.ts +5 -0
- package/src/services/message/server.ts +4 -0
- package/src/services/message/type.ts +2 -0
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +410 -0
- package/src/store/chat/slices/aiChat/actions/index.ts +7 -1
- package/src/store/chat/slices/message/action.ts +38 -1
- package/src/store/chat/slices/message/reducer.ts +11 -0
- package/src/store/chat/slices/topic/reducer.ts +14 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,57 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
## [Version 1.122.0](https://github.com/lobehub/lobe-chat/compare/v1.121.1...v1.122.0)
|
6
|
+
|
7
|
+
<sup>Released on **2025-09-04**</sup>
|
8
|
+
|
9
|
+
#### ✨ Features
|
10
|
+
|
11
|
+
- **misc**: Refactor to speed up send message in server mode.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's improved
|
19
|
+
|
20
|
+
- **misc**: Refactor to speed up send message in server mode, closes [#9046](https://github.com/lobehub/lobe-chat/issues/9046) ([4813b6d](https://github.com/lobehub/lobe-chat/commit/4813b6d))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.121.1](https://github.com/lobehub/lobe-chat/compare/v1.121.0...v1.121.1)
|
31
|
+
|
32
|
+
<sup>Released on **2025-09-03**</sup>
|
33
|
+
|
34
|
+
#### 🐛 Bug Fixes
|
35
|
+
|
36
|
+
- **misc**: Fix socks5 proxy not work problem, fix virtuaso minheight was null.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### What's fixed
|
44
|
+
|
45
|
+
- **misc**: Fix socks5 proxy not work problem, closes [#9053](https://github.com/lobehub/lobe-chat/issues/9053) ([b13563c](https://github.com/lobehub/lobe-chat/commit/b13563c))
|
46
|
+
- **misc**: Fix virtuaso minheight was null, closes [#9055](https://github.com/lobehub/lobe-chat/issues/9055) ([ef79721](https://github.com/lobehub/lobe-chat/commit/ef79721))
|
47
|
+
|
48
|
+
</details>
|
49
|
+
|
50
|
+
<div align="right">
|
51
|
+
|
52
|
+
[](#readme-top)
|
53
|
+
|
54
|
+
</div>
|
55
|
+
|
5
56
|
## [Version 1.121.0](https://github.com/lobehub/lobe-chat/compare/v1.120.7...v1.121.0)
|
6
57
|
|
7
58
|
<sup>Released on **2025-09-03**</sup>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
|
2
|
+
import { SocksProxies, socksDispatcher } from 'fetch-socks';
|
2
3
|
import { Agent, ProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici';
|
3
4
|
|
4
5
|
import { createLogger } from '@/utils/logger';
|
@@ -91,8 +92,29 @@ export class ProxyDispatcherManager {
|
|
91
92
|
*/
|
92
93
|
static createProxyAgent(proxyType: string, proxyUrl: string) {
|
93
94
|
try {
|
94
|
-
|
95
|
-
|
95
|
+
if (proxyType === 'socks5') {
|
96
|
+
// 解析 SOCKS5 代理 URL
|
97
|
+
const url = new URL(proxyUrl);
|
98
|
+
const socksProxies: SocksProxies = [
|
99
|
+
{
|
100
|
+
host: url.hostname,
|
101
|
+
port: parseInt(url.port, 10),
|
102
|
+
type: 5,
|
103
|
+
...(url.username && url.password
|
104
|
+
? {
|
105
|
+
password: url.password,
|
106
|
+
userId: url.username,
|
107
|
+
}
|
108
|
+
: {}),
|
109
|
+
},
|
110
|
+
];
|
111
|
+
|
112
|
+
// 使用 fetch-socks 处理 SOCKS5 代理
|
113
|
+
return socksDispatcher(socksProxies);
|
114
|
+
} else {
|
115
|
+
// undici 的 ProxyAgent 支持 http, https
|
116
|
+
return new ProxyAgent({ uri: proxyUrl });
|
117
|
+
}
|
96
118
|
} catch (error) {
|
97
119
|
logger.error(`Failed to create proxy agent for ${proxyType}:`, error);
|
98
120
|
throw new Error(
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {
|
4
|
+
"features": [
|
5
|
+
"Refactor to speed up send message in server mode."
|
6
|
+
]
|
7
|
+
},
|
8
|
+
"date": "2025-09-04",
|
9
|
+
"version": "1.122.0"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"children": {
|
13
|
+
"fixes": [
|
14
|
+
"Fix socks5 proxy not work problem, fix virtuaso minheight was null."
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"date": "2025-09-03",
|
18
|
+
"version": "1.121.1"
|
19
|
+
},
|
2
20
|
{
|
3
21
|
"children": {
|
4
22
|
"features": [
|
package/next.config.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.122.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import {
|
4
|
-
import {
|
1
|
+
import { LobeAgentSession, LobeSessionType } from '@lobechat/types';
|
2
|
+
|
3
|
+
import { DEFAULT_AGENT_META } from './meta';
|
4
|
+
import { DEFAULT_AGENT_CONFIG } from './settings';
|
5
5
|
|
6
6
|
export const INBOX_SESSION_ID = 'inbox';
|
7
7
|
|
@@ -16,10 +16,3 @@ export const DEFAULT_AGENT_LOBE_SESSION: LobeAgentSession = {
|
|
16
16
|
type: LobeSessionType.Agent,
|
17
17
|
updatedAt: new Date(),
|
18
18
|
};
|
19
|
-
|
20
|
-
export const DEFAULT_INBOX_SESSION: LobeAgentSession = merge(DEFAULT_AGENT_LOBE_SESSION, {
|
21
|
-
id: 'inbox',
|
22
|
-
meta: {
|
23
|
-
avatar: DEFAULT_INBOX_AVATAR,
|
24
|
-
},
|
25
|
-
});
|
@@ -2112,6 +2112,47 @@ describe('MessageModel', () => {
|
|
2112
2112
|
});
|
2113
2113
|
});
|
2114
2114
|
|
2115
|
+
describe('updateMessageRAG', () => {
|
2116
|
+
it('should insert message query chunks for RAG', async () => {
|
2117
|
+
// prepare message and query
|
2118
|
+
const messageId = 'rag-msg-1';
|
2119
|
+
const queryId = uuid();
|
2120
|
+
const chunk1 = uuid();
|
2121
|
+
const chunk2 = uuid();
|
2122
|
+
|
2123
|
+
await serverDB.transaction(async (trx) => {
|
2124
|
+
await trx.insert(messages).values({ id: messageId, role: 'user', userId, content: 'c' });
|
2125
|
+
await trx.insert(chunks).values([
|
2126
|
+
{ id: chunk1, text: 'a' },
|
2127
|
+
{ id: chunk2, text: 'b' },
|
2128
|
+
]);
|
2129
|
+
await trx
|
2130
|
+
.insert(messageQueries)
|
2131
|
+
.values({ id: queryId, messageId, userId, userQuery: 'q', rewriteQuery: 'rq' });
|
2132
|
+
});
|
2133
|
+
|
2134
|
+
await messageModel.updateMessageRAG(messageId, {
|
2135
|
+
ragQueryId: queryId,
|
2136
|
+
fileChunks: [
|
2137
|
+
{ id: chunk1, similarity: 0.9 },
|
2138
|
+
{ id: chunk2, similarity: 0.8 },
|
2139
|
+
],
|
2140
|
+
});
|
2141
|
+
|
2142
|
+
const rows = await serverDB
|
2143
|
+
.select()
|
2144
|
+
.from(messageQueryChunks)
|
2145
|
+
.where(eq(messageQueryChunks.messageId, messageId));
|
2146
|
+
|
2147
|
+
expect(rows).toHaveLength(2);
|
2148
|
+
const s1 = rows.find((r) => r.chunkId === chunk1)!;
|
2149
|
+
const s2 = rows.find((r) => r.chunkId === chunk2)!;
|
2150
|
+
expect(s1.queryId).toBe(queryId);
|
2151
|
+
expect(s1.similarity).toBe('0.90000');
|
2152
|
+
expect(s2.similarity).toBe('0.80000');
|
2153
|
+
});
|
2154
|
+
});
|
2155
|
+
|
2115
2156
|
describe('deleteMessageQuery', () => {
|
2116
2157
|
it('should delete a message query by ID', async () => {
|
2117
2158
|
// 创建测试数据
|
@@ -1,15 +1,3 @@
|
|
1
|
-
import type { HeatmapsProps } from '@lobehub/charts';
|
2
|
-
import dayjs from 'dayjs';
|
3
|
-
import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, like, sql } from 'drizzle-orm';
|
4
|
-
|
5
|
-
import { LobeChatDatabase } from '../type';
|
6
|
-
import {
|
7
|
-
genEndDateWhere,
|
8
|
-
genRangeWhere,
|
9
|
-
genStartDateWhere,
|
10
|
-
genWhere,
|
11
|
-
} from '../utils/genWhere';
|
12
|
-
import { idGenerator } from '../utils/idGenerator';
|
13
1
|
import {
|
14
2
|
ChatFileItem,
|
15
3
|
ChatImageItem,
|
@@ -22,7 +10,12 @@ import {
|
|
22
10
|
ModelRankItem,
|
23
11
|
NewMessageQueryParams,
|
24
12
|
UpdateMessageParams,
|
25
|
-
|
13
|
+
UpdateMessageRAGParams,
|
14
|
+
} from '@lobechat/types';
|
15
|
+
import type { HeatmapsProps } from '@lobehub/charts';
|
16
|
+
import dayjs from 'dayjs';
|
17
|
+
import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, like, sql } from 'drizzle-orm';
|
18
|
+
|
26
19
|
import { merge } from '@/utils/merge';
|
27
20
|
import { today } from '@/utils/time';
|
28
21
|
|
@@ -41,6 +34,9 @@ import {
|
|
41
34
|
messages,
|
42
35
|
messagesFiles,
|
43
36
|
} from '../schemas';
|
37
|
+
import { LobeChatDatabase } from '../type';
|
38
|
+
import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
|
39
|
+
import { idGenerator } from '../utils/idGenerator';
|
44
40
|
|
45
41
|
export interface QueryMessageParams {
|
46
42
|
current?: number;
|
@@ -614,6 +610,18 @@ export class MessageModel {
|
|
614
610
|
.where(eq(messageTTS.id, id));
|
615
611
|
};
|
616
612
|
|
613
|
+
async updateMessageRAG(id: string, { ragQueryId, fileChunks }: UpdateMessageRAGParams) {
|
614
|
+
return this.db.insert(messageQueryChunks).values(
|
615
|
+
fileChunks.map((chunk) => ({
|
616
|
+
chunkId: chunk.id,
|
617
|
+
messageId: id,
|
618
|
+
queryId: ragQueryId,
|
619
|
+
similarity: chunk.similarity?.toString(),
|
620
|
+
userId: this.userId,
|
621
|
+
})),
|
622
|
+
);
|
623
|
+
}
|
624
|
+
|
617
625
|
// **************** Delete *************** //
|
618
626
|
|
619
627
|
deleteMessage = async (id: string) => {
|
@@ -1,23 +1,18 @@
|
|
1
1
|
import { and, count, desc, eq, gt, ilike, inArray, isNull, sql } from 'drizzle-orm';
|
2
2
|
|
3
|
-
import { LobeChatDatabase } from '../type';
|
4
|
-
import {
|
5
|
-
genEndDateWhere,
|
6
|
-
genRangeWhere,
|
7
|
-
genStartDateWhere,
|
8
|
-
genWhere,
|
9
|
-
} from '../utils/genWhere';
|
10
|
-
import { idGenerator } from '../utils/idGenerator';
|
11
3
|
import { MessageItem } from '@/types/message';
|
12
4
|
import { TopicRankItem } from '@/types/topic';
|
13
5
|
|
14
6
|
import { TopicItem, messages, topics } from '../schemas';
|
7
|
+
import { LobeChatDatabase } from '../type';
|
8
|
+
import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
|
9
|
+
import { idGenerator } from '../utils/idGenerator';
|
15
10
|
|
16
11
|
export interface CreateTopicParams {
|
17
12
|
favorite?: boolean;
|
18
13
|
messages?: string[];
|
19
14
|
sessionId?: string | null;
|
20
|
-
title
|
15
|
+
title?: string;
|
21
16
|
}
|
22
17
|
|
23
18
|
interface QueryTopicParams {
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
import { ChatMessage } from './message';
|
4
|
+
import { ChatTopic } from './topic';
|
5
|
+
|
6
|
+
export interface SendNewMessage {
|
7
|
+
content: string;
|
8
|
+
// if message has attached with files, then add files to message and the agent
|
9
|
+
files?: string[];
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface SendMessageServerParams {
|
13
|
+
newAssistantMessage: {
|
14
|
+
model: string;
|
15
|
+
provider: string;
|
16
|
+
};
|
17
|
+
newTopic?: {
|
18
|
+
title?: string;
|
19
|
+
topicMessageIds?: string[];
|
20
|
+
};
|
21
|
+
newUserMessage: SendNewMessage;
|
22
|
+
sessionId?: string;
|
23
|
+
threadId?: string;
|
24
|
+
// if there is activeTopicId,then add topicId to message
|
25
|
+
topicId?: string;
|
26
|
+
}
|
27
|
+
|
28
|
+
export const AiSendMessageServerSchema = z.object({
|
29
|
+
newAssistantMessage: z.object({
|
30
|
+
model: z.string().optional(),
|
31
|
+
provider: z.string().optional(),
|
32
|
+
}),
|
33
|
+
newTopic: z
|
34
|
+
.object({
|
35
|
+
title: z.string().optional(),
|
36
|
+
topicMessageIds: z.array(z.string()).optional(),
|
37
|
+
})
|
38
|
+
.optional(),
|
39
|
+
newUserMessage: z.object({
|
40
|
+
content: z.string(),
|
41
|
+
files: z.array(z.string()).optional(),
|
42
|
+
}),
|
43
|
+
sessionId: z.string().optional(),
|
44
|
+
threadId: z.string().optional(),
|
45
|
+
topicId: z.string().optional(),
|
46
|
+
});
|
47
|
+
|
48
|
+
export interface SendMessageServerResponse {
|
49
|
+
assistantMessageId: string;
|
50
|
+
isCreatNewTopic: boolean;
|
51
|
+
messages: ChatMessage[];
|
52
|
+
topicId: string;
|
53
|
+
topics?: ChatTopic[];
|
54
|
+
userMessageId: string;
|
55
|
+
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
export * from './agent';
|
2
|
+
export * from './aiChat';
|
2
3
|
export * from './aiProvider';
|
3
4
|
export * from './artifact';
|
4
5
|
export * from './asyncTask';
|
@@ -11,7 +12,10 @@ export * from './knowledgeBase';
|
|
11
12
|
export * from './llm';
|
12
13
|
export * from './message';
|
13
14
|
export * from './meta';
|
15
|
+
export * from './rag';
|
14
16
|
export * from './serverConfig';
|
17
|
+
export * from './session';
|
18
|
+
export * from './topic';
|
15
19
|
export * from './user';
|
16
20
|
export * from './user/settings';
|
17
21
|
// FIXME: I think we need a refactor for the "openai" types
|
@@ -1,7 +1,20 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
import {
|
4
|
-
|
1
|
+
import { IPluginErrorType } from '@lobehub/chat-plugin-sdk';
|
2
|
+
|
3
|
+
import { ILobeAgentRuntimeErrorType } from '@/libs/model-runtime';
|
4
|
+
|
5
|
+
import { ErrorType } from '../fetch';
|
6
|
+
import { GroundingSearch } from '../search';
|
7
|
+
import { ChatImageItem } from './image';
|
8
|
+
import { ChatToolPayload, MessageToolCall } from './tools';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* 聊天消息错误对象
|
12
|
+
*/
|
13
|
+
export interface ChatMessageError {
|
14
|
+
body?: any;
|
15
|
+
message: string;
|
16
|
+
type: ErrorType | IPluginErrorType | ILobeAgentRuntimeErrorType;
|
17
|
+
}
|
5
18
|
|
6
19
|
export interface CitationItem {
|
7
20
|
id?: string;
|
@@ -1,25 +1,11 @@
|
|
1
|
-
import { IPluginErrorType } from '@lobehub/chat-plugin-sdk';
|
2
|
-
|
3
|
-
import { ILobeAgentRuntimeErrorType } from '@/libs/model-runtime';
|
4
|
-
|
5
|
-
import { ErrorType } from '../fetch';
|
6
1
|
import { MetaData } from '../meta';
|
7
2
|
import { MessageSemanticSearchChunk } from '../rag';
|
8
3
|
import { GroundingSearch } from '../search';
|
9
|
-
import { MessageMetadata, MessageRoleType, ModelReasoning } from './base';
|
4
|
+
import type { ChatMessageError, MessageMetadata, MessageRoleType, ModelReasoning } from './base';
|
10
5
|
import { ChatImageItem } from './image';
|
11
6
|
import { ChatPluginPayload, ChatToolPayload } from './tools';
|
12
7
|
import { Translate } from './translate';
|
13
8
|
|
14
|
-
/**
|
15
|
-
* 聊天消息错误对象
|
16
|
-
*/
|
17
|
-
export interface ChatMessageError {
|
18
|
-
body?: any;
|
19
|
-
message: string;
|
20
|
-
type: ErrorType | IPluginErrorType | ILobeAgentRuntimeErrorType;
|
21
|
-
}
|
22
|
-
|
23
9
|
export interface ChatTranslate extends Translate {
|
24
10
|
content?: string;
|
25
11
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
import { MessageSemanticSearchChunk } from '../rag';
|
4
|
+
|
5
|
+
export const SemanticSearchChunkSchema = z.object({
|
6
|
+
id: z.string(),
|
7
|
+
similarity: z.number(),
|
8
|
+
});
|
9
|
+
|
10
|
+
export interface UpdateMessageRAGParams {
|
11
|
+
fileChunks: MessageSemanticSearchChunk[];
|
12
|
+
ragQueryId?: string;
|
13
|
+
}
|
14
|
+
|
15
|
+
export const UpdateMessageRAGParamsSchema = z.object({
|
16
|
+
id: z.string(),
|
17
|
+
value: z.object({
|
18
|
+
fileChunks: z.array(SemanticSearchChunkSchema),
|
19
|
+
ragQueryId: z.string().optional(),
|
20
|
+
}),
|
21
|
+
});
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import { cleanObject } from './object';
|
4
|
+
|
5
|
+
describe('cleanObject', () => {
|
6
|
+
it('should remove null, undefined and empty string fields', () => {
|
7
|
+
const input = { a: 1, b: null, c: undefined, d: '', e: 0, f: false } as const;
|
8
|
+
const res = cleanObject({ ...input });
|
9
|
+
expect(res).toEqual({ a: 1, e: 0, f: false });
|
10
|
+
});
|
11
|
+
});
|
@@ -55,21 +55,21 @@ const ByTimeMode = memo(() => {
|
|
55
55
|
const groupContent = useCallback(
|
56
56
|
(index: number) => {
|
57
57
|
if (index === 0) return <div style={{ height: 1 }} />;
|
58
|
-
|
59
58
|
const topicGroup = groups[index];
|
60
59
|
return <TopicGroupItem {...topicGroup} />;
|
61
60
|
},
|
62
61
|
[groups],
|
63
62
|
);
|
64
63
|
|
65
|
-
// const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
|
66
|
-
|
67
64
|
return (
|
68
65
|
<GroupedVirtuoso
|
69
66
|
groupContent={groupContent}
|
70
67
|
groupCounts={groupCounts}
|
71
68
|
itemContent={itemContent}
|
72
69
|
ref={virtuosoRef}
|
70
|
+
style={{
|
71
|
+
minHeight: groupCounts.length === 1 ? '0px' : '200px',
|
72
|
+
}}
|
73
73
|
/>
|
74
74
|
);
|
75
75
|
});
|
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|
3
3
|
import { MessageModel } from '@/database/models/message';
|
4
4
|
import { FileService } from '@/server/services/file';
|
5
5
|
import { ChatMessage, CreateMessageParams } from '@/types/message';
|
6
|
+
import { UpdateMessageRAGParams } from '@/types/message/rag';
|
6
7
|
|
7
8
|
vi.mock('@/database/models/message', () => ({
|
8
9
|
MessageModel: vi.fn(),
|
@@ -210,4 +211,33 @@ describe('messageRouter', () => {
|
|
210
211
|
expect(mockUpdate).toHaveBeenCalledWith(input.id, input.value);
|
211
212
|
expect(result).toEqual({ success: true });
|
212
213
|
});
|
214
|
+
|
215
|
+
it('should handle updateMessageRAG', async () => {
|
216
|
+
const mockUpdateRAG = vi.fn().mockResolvedValue(undefined);
|
217
|
+
vi.mocked(MessageModel).mockImplementation(
|
218
|
+
() =>
|
219
|
+
({
|
220
|
+
updateMessageRAG: mockUpdateRAG,
|
221
|
+
}) as any,
|
222
|
+
);
|
223
|
+
|
224
|
+
const input = {
|
225
|
+
id: 'msg1',
|
226
|
+
value: { ragQueryId: 'q1', fileChunks: [{ id: 'c1', similarity: 0.9 }] },
|
227
|
+
} as {
|
228
|
+
id: string;
|
229
|
+
value: UpdateMessageRAGParams;
|
230
|
+
};
|
231
|
+
|
232
|
+
const ctx = {
|
233
|
+
messageModel: new MessageModel({} as any, 'user1'),
|
234
|
+
};
|
235
|
+
|
236
|
+
await ctx.messageModel.updateMessageRAG(input.id, input.value);
|
237
|
+
|
238
|
+
expect(mockUpdateRAG).toHaveBeenCalledWith('msg1', {
|
239
|
+
ragQueryId: 'q1',
|
240
|
+
fileChunks: [{ id: 'c1', similarity: 0.9 }],
|
241
|
+
});
|
242
|
+
});
|
213
243
|
});
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import { MessageModel } from '@/database/models/message';
|
4
|
+
import { TopicModel } from '@/database/models/topic';
|
5
|
+
import { AiChatService } from '@/server/services/aiChat';
|
6
|
+
|
7
|
+
import { aiChatRouter } from './aiChat';
|
8
|
+
|
9
|
+
vi.mock('@/database/models/message');
|
10
|
+
vi.mock('@/database/models/topic');
|
11
|
+
vi.mock('@/server/services/aiChat');
|
12
|
+
vi.mock('@/server/services/file', () => ({
|
13
|
+
FileService: vi.fn(),
|
14
|
+
}));
|
15
|
+
|
16
|
+
describe('aiChatRouter', () => {
|
17
|
+
const mockCtx = { userId: 'u1' };
|
18
|
+
|
19
|
+
it('should create topic optionally, create user/assistant messages, and return payload', async () => {
|
20
|
+
const mockCreateTopic = vi.fn().mockResolvedValue({ id: 't1' });
|
21
|
+
const mockCreateMessage = vi
|
22
|
+
.fn()
|
23
|
+
.mockResolvedValueOnce({ id: 'm-user' })
|
24
|
+
.mockResolvedValueOnce({ id: 'm-assistant' });
|
25
|
+
const mockGet = vi
|
26
|
+
.fn()
|
27
|
+
.mockResolvedValue({ messages: [{ id: 'm-user' }, { id: 'm-assistant' }], topics: [{}] });
|
28
|
+
|
29
|
+
vi.mocked(TopicModel).mockImplementation(() => ({ create: mockCreateTopic }) as any);
|
30
|
+
vi.mocked(MessageModel).mockImplementation(() => ({ create: mockCreateMessage }) as any);
|
31
|
+
vi.mocked(AiChatService).mockImplementation(() => ({ getMessagesAndTopics: mockGet }) as any);
|
32
|
+
|
33
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
34
|
+
|
35
|
+
const input = {
|
36
|
+
newAssistantMessage: { model: 'gpt-4o', provider: 'openai' },
|
37
|
+
newTopic: { title: 'T', topicMessageIds: ['a', 'b'] },
|
38
|
+
newUserMessage: { content: 'hi', files: ['f1'] },
|
39
|
+
sessionId: 's1',
|
40
|
+
} as any;
|
41
|
+
|
42
|
+
const res = await caller.sendMessageInServer(input);
|
43
|
+
|
44
|
+
expect(mockCreateTopic).toHaveBeenCalledWith({
|
45
|
+
messages: ['a', 'b'],
|
46
|
+
sessionId: 's1',
|
47
|
+
title: 'T',
|
48
|
+
});
|
49
|
+
|
50
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(1, {
|
51
|
+
content: 'hi',
|
52
|
+
files: ['f1'],
|
53
|
+
role: 'user',
|
54
|
+
sessionId: 's1',
|
55
|
+
topicId: 't1',
|
56
|
+
});
|
57
|
+
|
58
|
+
expect(mockCreateMessage).toHaveBeenNthCalledWith(
|
59
|
+
2,
|
60
|
+
expect.objectContaining({
|
61
|
+
content: expect.any(String),
|
62
|
+
fromModel: 'gpt-4o',
|
63
|
+
parentId: 'm-user',
|
64
|
+
role: 'assistant',
|
65
|
+
sessionId: 's1',
|
66
|
+
topicId: 't1',
|
67
|
+
}),
|
68
|
+
);
|
69
|
+
|
70
|
+
expect(mockGet).toHaveBeenCalledWith({ includeTopic: true, sessionId: 's1', topicId: 't1' });
|
71
|
+
expect(res.assistantMessageId).toBe('m-assistant');
|
72
|
+
expect(res.userMessageId).toBe('m-user');
|
73
|
+
expect(res.isCreatNewTopic).toBe(true);
|
74
|
+
expect(res.topicId).toBe('t1');
|
75
|
+
expect(res.messages?.length).toBe(2);
|
76
|
+
expect(res.topics?.length).toBe(1);
|
77
|
+
});
|
78
|
+
|
79
|
+
it('should reuse existing topic when topicId provided', async () => {
|
80
|
+
const mockCreateMessage = vi
|
81
|
+
.fn()
|
82
|
+
.mockResolvedValueOnce({ id: 'm-user' })
|
83
|
+
.mockResolvedValueOnce({ id: 'm-assistant' });
|
84
|
+
const mockGet = vi.fn().mockResolvedValue({ messages: [], topics: undefined });
|
85
|
+
|
86
|
+
vi.mocked(MessageModel).mockImplementation(() => ({ create: mockCreateMessage }) as any);
|
87
|
+
vi.mocked(AiChatService).mockImplementation(() => ({ getMessagesAndTopics: mockGet }) as any);
|
88
|
+
|
89
|
+
const caller = aiChatRouter.createCaller(mockCtx as any);
|
90
|
+
|
91
|
+
const res = await caller.sendMessageInServer({
|
92
|
+
newAssistantMessage: { model: 'gpt-4o', provider: 'openai' },
|
93
|
+
newUserMessage: { content: 'hi' },
|
94
|
+
sessionId: 's1',
|
95
|
+
topicId: 't-exist',
|
96
|
+
} as any);
|
97
|
+
|
98
|
+
expect(mockCreateMessage).toHaveBeenCalled();
|
99
|
+
expect(mockGet).toHaveBeenCalledWith({
|
100
|
+
includeTopic: false,
|
101
|
+
sessionId: 's1',
|
102
|
+
topicId: 't-exist',
|
103
|
+
});
|
104
|
+
expect(res.isCreatNewTopic).toBe(false);
|
105
|
+
expect(res.topicId).toBe('t-exist');
|
106
|
+
});
|
107
|
+
});
|