@lobehub/chat 1.84.27 → 1.85.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/development/database-schema.dbml +59 -1
  4. package/package.json +3 -2
  5. package/packages/file-loaders/package.json +5 -1
  6. package/packages/file-loaders/src/loadFile.ts +51 -1
  7. package/packages/file-loaders/src/loaders/docx/index.ts +16 -1
  8. package/packages/file-loaders/src/loaders/excel/index.ts +30 -2
  9. package/packages/file-loaders/src/loaders/pdf/__snapshots__/index.test.ts.snap +1 -1
  10. package/packages/file-loaders/src/loaders/pdf/index.ts +52 -12
  11. package/packages/file-loaders/src/loaders/pptx/index.ts +32 -1
  12. package/packages/file-loaders/src/loaders/text/index.test.ts +1 -1
  13. package/packages/file-loaders/src/loaders/text/index.ts +13 -1
  14. package/packages/file-loaders/test/__snapshots__/loaders.test.ts.snap +41 -0
  15. package/packages/file-loaders/test/loaders.test.ts +20 -0
  16. package/packages/file-loaders/test/setup.ts +17 -0
  17. package/packages/file-loaders/vitest.config.ts +14 -0
  18. package/src/config/aiModels/infiniai.ts +113 -9
  19. package/src/const/file.ts +8 -1
  20. package/src/database/client/migrations.json +23 -1
  21. package/src/database/migrations/0022_add_documents.sql +49 -0
  22. package/src/database/migrations/meta/0022_snapshot.json +5340 -0
  23. package/src/database/migrations/meta/_journal.json +7 -0
  24. package/src/database/models/_template.ts +1 -1
  25. package/src/database/models/document.ts +54 -0
  26. package/src/database/models/message.ts +25 -0
  27. package/src/database/repositories/tableViewer/index.test.ts +1 -1
  28. package/src/database/schemas/document.ts +104 -0
  29. package/src/database/schemas/index.ts +1 -0
  30. package/src/database/schemas/relations.ts +34 -2
  31. package/src/database/schemas/topic.ts +31 -8
  32. package/src/database/utils/idGenerator.ts +1 -0
  33. package/src/features/ChatInput/Desktop/FilePreview/FileItem/Content.tsx +1 -1
  34. package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +10 -10
  35. package/src/features/ChatInput/components/UploadDetail/UploadStatus.tsx +2 -2
  36. package/src/features/Conversation/Actions/Error.tsx +2 -2
  37. package/src/libs/agent-runtime/infiniai/index.ts +1 -1
  38. package/src/libs/trpc/lambda/context.ts +7 -0
  39. package/src/prompts/files/file.ts +6 -4
  40. package/src/server/routers/lambda/__tests__/message.test.ts +213 -0
  41. package/src/server/routers/lambda/document.ts +36 -0
  42. package/src/server/routers/lambda/index.ts +2 -0
  43. package/src/server/services/document/index.ts +66 -0
  44. package/src/server/services/file/__tests__/index.test.ts +115 -0
  45. package/src/server/services/mcp/index.ts +0 -4
  46. package/src/server/utils/__tests__/tempFileManager.test.ts +94 -0
  47. package/src/services/rag.ts +4 -0
  48. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +2 -2
  49. package/src/store/chat/slices/aiChat/actions/rag.ts +2 -3
  50. package/src/store/file/slices/chat/action.ts +3 -51
  51. package/src/types/document/index.ts +172 -0
  52. package/src/types/message/chat.ts +1 -0
  53. package/src/features/ChatInput/Desktop/FilePreview/FileItem/style.ts +0 -4
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { MessageModel } from '@/database/models/message';
4
+ import { FileService } from '@/server/services/file';
5
+ import { ChatMessage, CreateMessageParams } from '@/types/message';
6
+
7
+ vi.mock('@/database/models/message', () => ({
8
+ MessageModel: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('@/server/services/file', () => ({
12
+ FileService: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('@/database/server', () => ({
16
+ getServerDB: vi.fn(),
17
+ }));
18
+
19
+ describe('messageRouter', () => {
20
+ it('should handle batchCreateMessages', async () => {
21
+ const mockBatchCreate = vi.fn().mockResolvedValue({ rowCount: 2 });
22
+ vi.mocked(MessageModel).mockImplementation(
23
+ () =>
24
+ ({
25
+ batchCreate: mockBatchCreate,
26
+ }) as any,
27
+ );
28
+
29
+ const input = [
30
+ {
31
+ id: '1',
32
+ role: 'user',
33
+ content: 'test',
34
+ sessionId: 'session1',
35
+ createdAt: new Date(),
36
+ updatedAt: new Date(),
37
+ agentId: 'agent1',
38
+ clientId: 'client1',
39
+ parentId: null,
40
+ quotaId: null,
41
+ model: null,
42
+ provider: null,
43
+ topicId: null,
44
+ error: null,
45
+ favorite: false,
46
+ observationId: null,
47
+ reasoning: null,
48
+ pluginState: null,
49
+ translate: null,
50
+ tts: null,
51
+ search: null,
52
+ threadId: null,
53
+ tools: null,
54
+ traceId: null,
55
+ userId: 'user1',
56
+ } as any,
57
+ ];
58
+
59
+ const ctx = {
60
+ messageModel: new MessageModel({} as any, 'user1'),
61
+ };
62
+
63
+ const result = await ctx.messageModel.batchCreate(input);
64
+
65
+ expect(mockBatchCreate).toHaveBeenCalledWith(input);
66
+ expect(result.rowCount).toBe(2);
67
+ });
68
+
69
+ it('should handle count', async () => {
70
+ const mockCount = vi.fn().mockResolvedValue(5);
71
+ vi.mocked(MessageModel).mockImplementation(
72
+ () =>
73
+ ({
74
+ count: mockCount,
75
+ }) as any,
76
+ );
77
+
78
+ const input = { startDate: '2024-01-01' };
79
+ const ctx = {
80
+ messageModel: new MessageModel({} as any, 'user1'),
81
+ };
82
+
83
+ const result = await ctx.messageModel.count(input);
84
+
85
+ expect(mockCount).toHaveBeenCalledWith(input);
86
+ expect(result).toBe(5);
87
+ });
88
+
89
+ it('should handle createMessage', async () => {
90
+ const mockCreate = vi.fn().mockResolvedValue({ id: 'msg1' });
91
+ vi.mocked(MessageModel).mockImplementation(
92
+ () =>
93
+ ({
94
+ create: mockCreate,
95
+ }) as any,
96
+ );
97
+
98
+ const input: CreateMessageParams = {
99
+ content: 'test',
100
+ role: 'user',
101
+ sessionId: 'session1',
102
+ };
103
+
104
+ const ctx = {
105
+ messageModel: new MessageModel({} as any, 'user1'),
106
+ };
107
+
108
+ const result = await ctx.messageModel.create(input);
109
+
110
+ expect(mockCreate).toHaveBeenCalledWith(input);
111
+ expect(result.id).toBe('msg1');
112
+ });
113
+
114
+ it('should handle getMessages', async () => {
115
+ const mockQuery = vi.fn().mockResolvedValue([{ id: 'msg1' }]);
116
+ const mockGetFullFileUrl = vi
117
+ .fn()
118
+ .mockImplementation((path: string | null, file: { fileType: string }) => {
119
+ return Promise.resolve('url');
120
+ });
121
+
122
+ vi.mocked(MessageModel).mockImplementation(
123
+ () =>
124
+ ({
125
+ query: mockQuery,
126
+ }) as any,
127
+ );
128
+
129
+ vi.mocked(FileService).mockImplementation(
130
+ () =>
131
+ ({
132
+ getFullFileUrl: mockGetFullFileUrl,
133
+ }) as any,
134
+ );
135
+
136
+ const input = { sessionId: 'session1' };
137
+ const ctx = {
138
+ messageModel: new MessageModel({} as any, 'user1'),
139
+ fileService: new FileService({} as any, 'user1'),
140
+ userId: 'user1',
141
+ };
142
+
143
+ const result = await ctx.messageModel.query(input, {
144
+ postProcessUrl: mockGetFullFileUrl,
145
+ });
146
+
147
+ expect(mockQuery).toHaveBeenCalledWith(input, expect.any(Object));
148
+ expect(result).toEqual([{ id: 'msg1' }]);
149
+ });
150
+
151
+ it('should handle getAllMessages', async () => {
152
+ const mockQueryAll = vi.fn().mockResolvedValue([
153
+ {
154
+ id: 'msg1',
155
+ meta: {},
156
+ } as ChatMessage,
157
+ ]);
158
+ vi.mocked(MessageModel).mockImplementation(
159
+ () =>
160
+ ({
161
+ queryAll: mockQueryAll,
162
+ }) as any,
163
+ );
164
+
165
+ const ctx = {
166
+ messageModel: new MessageModel({} as any, 'user1'),
167
+ };
168
+
169
+ const result = await ctx.messageModel.queryAll();
170
+
171
+ expect(mockQueryAll).toHaveBeenCalled();
172
+ expect(result).toEqual([{ id: 'msg1', meta: {} }]);
173
+ });
174
+
175
+ it('should handle removeMessage', async () => {
176
+ const mockDelete = vi.fn().mockResolvedValue(undefined);
177
+ vi.mocked(MessageModel).mockImplementation(
178
+ () =>
179
+ ({
180
+ deleteMessage: mockDelete,
181
+ }) as any,
182
+ );
183
+
184
+ const input = { id: 'msg1' };
185
+ const ctx = {
186
+ messageModel: new MessageModel({} as any, 'user1'),
187
+ };
188
+
189
+ await ctx.messageModel.deleteMessage(input.id);
190
+
191
+ expect(mockDelete).toHaveBeenCalledWith(input.id);
192
+ });
193
+
194
+ it('should handle updateMessage', async () => {
195
+ const mockUpdate = vi.fn().mockResolvedValue({ success: true });
196
+ vi.mocked(MessageModel).mockImplementation(
197
+ () =>
198
+ ({
199
+ update: mockUpdate,
200
+ }) as any,
201
+ );
202
+
203
+ const input = { id: 'msg1', value: { content: 'updated' } };
204
+ const ctx = {
205
+ messageModel: new MessageModel({} as any, 'user1'),
206
+ };
207
+
208
+ const result = await ctx.messageModel.update(input.id, input.value);
209
+
210
+ expect(mockUpdate).toHaveBeenCalledWith(input.id, input.value);
211
+ expect(result).toEqual({ success: true });
212
+ });
213
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+
3
+ import { ChunkModel } from '@/database/models/chunk';
4
+ import { FileModel } from '@/database/models/file';
5
+ import { MessageModel } from '@/database/models/message';
6
+ import { authedProcedure, router } from '@/libs/trpc/lambda';
7
+ import { serverDatabase } from '@/libs/trpc/lambda/middleware';
8
+ import { DocumentService } from '@/server/services/document';
9
+
10
+ const documentProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
11
+ const { ctx } = opts;
12
+
13
+ return opts.next({
14
+ ctx: {
15
+ chunkModel: new ChunkModel(ctx.serverDB, ctx.userId),
16
+ documentService: new DocumentService(ctx.serverDB, ctx.userId),
17
+ fileModel: new FileModel(ctx.serverDB, ctx.userId),
18
+ messageModel: new MessageModel(ctx.serverDB, ctx.userId),
19
+ },
20
+ });
21
+ });
22
+
23
+ export const documentRouter = router({
24
+ parseFileContent: documentProcedure
25
+ .input(
26
+ z.object({
27
+ id: z.string(),
28
+ skipExist: z.boolean().optional(),
29
+ }),
30
+ )
31
+ .mutation(async ({ ctx, input }) => {
32
+ const lobeDocument = await ctx.documentService.parseFile(input.id);
33
+
34
+ return lobeDocument;
35
+ }),
36
+ });
@@ -7,6 +7,7 @@ import { agentRouter } from './agent';
7
7
  import { aiModelRouter } from './aiModel';
8
8
  import { aiProviderRouter } from './aiProvider';
9
9
  import { chunkRouter } from './chunk';
10
+ import { documentRouter } from './document';
10
11
  import { exporterRouter } from './exporter';
11
12
  import { fileRouter } from './file';
12
13
  import { importerRouter } from './importer';
@@ -25,6 +26,7 @@ export const lambdaRouter = router({
25
26
  aiModel: aiModelRouter,
26
27
  aiProvider: aiProviderRouter,
27
28
  chunk: chunkRouter,
29
+ document: documentRouter,
28
30
  exporter: exporterRouter,
29
31
  file: fileRouter,
30
32
  healthcheck: publicProcedure.query(() => "i'm live!"),
@@ -0,0 +1,66 @@
1
+ import { loadFile } from '@lobechat/file-loaders';
2
+ import debug from 'debug';
3
+
4
+ import { DocumentModel } from '@/database/models/document';
5
+ import { FileModel } from '@/database/models/file';
6
+ import { LobeChatDatabase } from '@/database/type';
7
+ import { LobeDocument } from '@/types/document';
8
+
9
+ import { FileService } from '../file';
10
+
11
+ const log = debug('lobe-chat:service:document');
12
+
13
+ export class DocumentService {
14
+ userId: string;
15
+ private fileModel: FileModel;
16
+ private documentModel: DocumentModel;
17
+ private fileService: FileService;
18
+
19
+ constructor(db: LobeChatDatabase, userId: string) {
20
+ this.userId = userId;
21
+ this.fileModel = new FileModel(db, userId);
22
+ this.fileService = new FileService(db, userId);
23
+ this.documentModel = new DocumentModel(db, userId);
24
+ }
25
+
26
+ /**
27
+ * 解析文件内容
28
+ *
29
+ */
30
+ async parseFile(fileId: string): Promise<LobeDocument> {
31
+ const { filePath, file, cleanup } = await this.fileService.downloadFileToLocal(fileId);
32
+
33
+ const logPrefix = `[${file.name}]`;
34
+ log(`${logPrefix} 开始解析文件, 路径: ${filePath}`);
35
+
36
+ try {
37
+ // 使用loadFile加载文件内容
38
+ const fileDocument = await loadFile(filePath);
39
+
40
+ log(`${logPrefix} 文件解析成功 %O`, {
41
+ fileType: fileDocument.fileType,
42
+ size: fileDocument.content.length,
43
+ });
44
+
45
+ const document = await this.documentModel.create({
46
+ content: fileDocument.content,
47
+ fileId,
48
+ fileType: file.fileType,
49
+ metadata: fileDocument.metadata,
50
+ pages: fileDocument.pages,
51
+ source: file.url,
52
+ sourceType: 'file',
53
+ title: fileDocument.metadata?.title,
54
+ totalCharCount: fileDocument.totalCharCount,
55
+ totalLineCount: fileDocument.totalLineCount,
56
+ });
57
+
58
+ return document as LobeDocument;
59
+ } catch (error) {
60
+ console.error(`${logPrefix} 文件解析失败:`, error);
61
+ throw error;
62
+ } finally {
63
+ cleanup();
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,115 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { FileModel } from '@/database/models/file';
5
+ import { TempFileManager } from '@/server/utils/tempFileManager';
6
+
7
+ import { FileService } from '../index';
8
+
9
+ vi.mock('@/config/db', () => ({
10
+ serverDBEnv: {
11
+ REMOVE_GLOBAL_FILE: false,
12
+ },
13
+ }));
14
+
15
+ vi.mock('../impls', () => ({
16
+ createFileServiceModule: () => ({
17
+ deleteFile: vi.fn(),
18
+ deleteFiles: vi.fn(),
19
+ getFileContent: vi.fn(),
20
+ getFileByteArray: vi.fn(),
21
+ createPreSignedUrl: vi.fn(),
22
+ createPreSignedUrlForPreview: vi.fn(),
23
+ uploadContent: vi.fn(),
24
+ getFullFileUrl: vi.fn(),
25
+ }),
26
+ }));
27
+
28
+ vi.mock('@/database/models/file');
29
+
30
+ vi.mock('@/server/utils/tempFileManager');
31
+
32
+ vi.mock('@/utils/uuid', () => ({
33
+ nanoid: () => 'test-id',
34
+ }));
35
+
36
+ describe('FileService', () => {
37
+ let service: FileService;
38
+ const mockDb = {} as any;
39
+ const mockUserId = 'test-user';
40
+ let mockFileModel: any;
41
+ let mockTempManager: any;
42
+
43
+ beforeEach(() => {
44
+ mockFileModel = {
45
+ findById: vi.fn(),
46
+ delete: vi.fn(),
47
+ };
48
+ mockTempManager = {
49
+ writeTempFile: vi.fn(),
50
+ cleanup: vi.fn(),
51
+ };
52
+ vi.mocked(FileModel).mockImplementation(() => mockFileModel);
53
+ vi.mocked(TempFileManager).mockImplementation(() => mockTempManager);
54
+ service = new FileService(mockDb, mockUserId);
55
+ });
56
+
57
+ afterEach(() => {
58
+ vi.clearAllMocks();
59
+ });
60
+
61
+ describe('downloadFileToLocal', () => {
62
+ const mockFile = {
63
+ id: 'test-file-id',
64
+ name: 'test.txt',
65
+ url: 'test-url',
66
+ };
67
+
68
+ it('should throw error if file not found', async () => {
69
+ mockFileModel.findById.mockResolvedValue(undefined);
70
+
71
+ await expect(service.downloadFileToLocal('test-file-id')).rejects.toThrow(
72
+ new TRPCError({ code: 'BAD_REQUEST', message: 'File not found' }),
73
+ );
74
+ });
75
+
76
+ it('should throw error if file content is empty', async () => {
77
+ mockFileModel.findById.mockResolvedValue(mockFile);
78
+ vi.mocked(service['impl'].getFileByteArray).mockResolvedValue(undefined as any);
79
+
80
+ await expect(service.downloadFileToLocal('test-file-id')).rejects.toThrow(
81
+ new TRPCError({ code: 'BAD_REQUEST', message: 'File content is empty' }),
82
+ );
83
+ });
84
+
85
+ it('should delete file from db and throw error if file not found in storage', async () => {
86
+ mockFileModel.findById.mockResolvedValue(mockFile);
87
+ vi.mocked(service['impl'].getFileByteArray).mockRejectedValue({ Code: 'NoSuchKey' });
88
+
89
+ await expect(service.downloadFileToLocal('test-file-id')).rejects.toThrow(
90
+ new TRPCError({ code: 'BAD_REQUEST', message: 'File not found' }),
91
+ );
92
+
93
+ expect(mockFileModel.delete).toHaveBeenCalledWith('test-file-id', false);
94
+ });
95
+
96
+ it('should successfully download file to local', async () => {
97
+ const mockContent = new Uint8Array([1, 2, 3]);
98
+ const mockFilePath = '/tmp/test.txt';
99
+
100
+ mockFileModel.findById.mockResolvedValue(mockFile);
101
+ vi.mocked(service['impl'].getFileByteArray).mockResolvedValue(mockContent);
102
+ mockTempManager.writeTempFile.mockResolvedValue(mockFilePath);
103
+
104
+ const result = await service.downloadFileToLocal('test-file-id');
105
+
106
+ expect(result).toEqual({
107
+ cleanup: expect.any(Function),
108
+ file: mockFile,
109
+ filePath: mockFilePath,
110
+ });
111
+
112
+ expect(mockTempManager.writeTempFile).toHaveBeenCalledWith(mockContent, mockFile.name);
113
+ });
114
+ });
115
+ });
@@ -15,10 +15,6 @@ class MCPService {
15
15
  // Store instances of the custom MCPClient, keyed by serialized MCPClientParams
16
16
  private clients: Map<string, MCPClient> = new Map();
17
17
 
18
- constructor() {
19
- log('MCPService initialized');
20
- }
21
-
22
18
  // --- MCP Interaction ---
23
19
 
24
20
  // listTools now accepts MCPClientParams
@@ -0,0 +1,94 @@
1
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+
6
+ import { TempFileManager } from '../tempFileManager';
7
+
8
+ // Mock node modules
9
+ vi.mock('node:fs');
10
+ vi.mock('node:os');
11
+ vi.mock('node:path', () => ({
12
+ join: (...args: string[]) => args.join('/'),
13
+ default: {
14
+ join: (...args: string[]) => args.join('/'),
15
+ },
16
+ }));
17
+
18
+ describe('TempFileManager', () => {
19
+ const mockTmpDir = '/tmp';
20
+ const mockDirname = 'test-';
21
+ const mockFullTmpDir = '/tmp/test-xyz';
22
+
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ vi.mocked(tmpdir).mockReturnValue(mockTmpDir);
26
+ vi.mocked(mkdtempSync).mockReturnValue(mockFullTmpDir);
27
+ vi.mocked(existsSync).mockReturnValue(true);
28
+ });
29
+
30
+ it('should create temp directory on initialization', () => {
31
+ new TempFileManager(mockDirname);
32
+
33
+ expect(tmpdir).toHaveBeenCalled();
34
+ expect(mkdtempSync).toHaveBeenCalledWith(`${mockTmpDir}/${mockDirname}`);
35
+ });
36
+
37
+ it('should write temp file successfully', async () => {
38
+ const manager = new TempFileManager(mockDirname);
39
+ const testData = new Uint8Array([1, 2, 3]);
40
+ const fileName = 'test.txt';
41
+
42
+ const filePath = await manager.writeTempFile(testData, fileName);
43
+
44
+ expect(writeFileSync).toHaveBeenCalledWith(`${mockFullTmpDir}/${fileName}`, testData);
45
+ expect(filePath).toBe(`${mockFullTmpDir}/${fileName}`);
46
+ });
47
+
48
+ it('should cleanup on write failure', async () => {
49
+ const manager = new TempFileManager(mockDirname);
50
+ const testData = new Uint8Array([1, 2, 3]);
51
+ const fileName = 'test.txt';
52
+
53
+ vi.mocked(writeFileSync).mockImplementation(() => {
54
+ throw new Error('Write failed');
55
+ });
56
+
57
+ await expect(manager.writeTempFile(testData, fileName)).rejects.toThrow(
58
+ 'Failed to write temp file: Write failed',
59
+ );
60
+
61
+ expect(existsSync).toHaveBeenCalledWith(mockFullTmpDir);
62
+ expect(rmSync).toHaveBeenCalledWith(mockFullTmpDir, { force: true, recursive: true });
63
+ });
64
+
65
+ it('should cleanup temp directory', () => {
66
+ const manager = new TempFileManager(mockDirname);
67
+ vi.mocked(existsSync).mockReturnValue(true);
68
+
69
+ manager.cleanup();
70
+
71
+ expect(existsSync).toHaveBeenCalledWith(mockFullTmpDir);
72
+ expect(rmSync).toHaveBeenCalledWith(mockFullTmpDir, { force: true, recursive: true });
73
+ });
74
+
75
+ it('should skip cleanup if directory does not exist', () => {
76
+ const manager = new TempFileManager(mockDirname);
77
+ vi.mocked(existsSync).mockReturnValue(false);
78
+
79
+ manager.cleanup();
80
+
81
+ expect(existsSync).toHaveBeenCalledWith(mockFullTmpDir);
82
+ expect(rmSync).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it('should register cleanup hooks on process events', () => {
86
+ const processOnSpy = vi.spyOn(process, 'on');
87
+ new TempFileManager(mockDirname);
88
+
89
+ expect(processOnSpy).toHaveBeenCalledWith('exit', expect.any(Function));
90
+ expect(processOnSpy).toHaveBeenCalledWith('uncaughtException', expect.any(Function));
91
+ expect(processOnSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
92
+ expect(processOnSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
93
+ });
94
+ });
@@ -2,6 +2,10 @@ import { lambdaClient } from '@/libs/trpc/client';
2
2
  import { SemanticSearchSchemaType } from '@/types/rag';
3
3
 
4
4
  class RAGService {
5
+ parseFileContent = async (id: string, skipExist?: boolean) => {
6
+ return lambdaClient.document.parseFileContent.mutate({ id, skipExist });
7
+ };
8
+
5
9
  createParseFileTask = async (id: string, skipExist?: boolean) => {
6
10
  return lambdaClient.chunk.createParseFileTask.mutate({ id, skipExist });
7
11
  };
@@ -197,13 +197,13 @@ describe('chatRAG actions', () => {
197
197
  expect(result.current.internal_shouldUseRAG()).toBe(true);
198
198
  });
199
199
 
200
- it('should return true if has user files', () => {
200
+ it('should return false if has user files', () => {
201
201
  const { result } = renderHook(() => useChatStore());
202
202
 
203
203
  vi.spyOn(agentSelectors, 'hasEnabledKnowledge').mockReturnValue(false);
204
204
  vi.spyOn(chatSelectors, 'currentUserFiles').mockReturnValue([{ id: 'file-1' }] as any);
205
205
 
206
- expect(result.current.internal_shouldUseRAG()).toBe(true);
206
+ expect(result.current.internal_shouldUseRAG()).toBeFalsy();
207
207
  });
208
208
 
209
209
  it('should return false if no knowledge or files', () => {
@@ -130,9 +130,8 @@ export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [],
130
130
  return rewriteQuery;
131
131
  },
132
132
  internal_shouldUseRAG: () => {
133
- const userFiles = chatSelectors.currentUserFiles(get()).map((f) => f.id);
134
- // if there is relative files or enabled knowledge, try with ragQuery
135
- return hasEnabledKnowledge() || userFiles.length > 0;
133
+ // if there is enabled knowledge, try with ragQuery
134
+ return hasEnabledKnowledge();
136
135
  },
137
136
 
138
137
  internal_toggleMessageRAGLoading: (loading, id) => {
@@ -7,14 +7,10 @@ import { fileService } from '@/services/file';
7
7
  import { ServerService } from '@/services/file/server';
8
8
  import { ragService } from '@/services/rag';
9
9
  import { UPLOAD_NETWORK_ERROR } from '@/services/upload';
10
- import { userService } from '@/services/user';
11
- import { useAgentStore } from '@/store/agent';
12
10
  import {
13
11
  UploadFileListDispatch,
14
12
  uploadFileListReducer,
15
13
  } from '@/store/file/reducers/uploadFileList';
16
- import { useUserStore } from '@/store/user';
17
- import { preferenceSelectors } from '@/store/user/selectors';
18
14
  import { FileListItem } from '@/types/files';
19
15
  import { UploadFileItem } from '@/types/files/upload';
20
16
  import { isChunkingUnsupported } from '@/utils/isChunkingUnsupported';
@@ -97,7 +93,7 @@ export const createFileSlice: StateCreator<
97
93
  },
98
94
 
99
95
  uploadChatFiles: async (rawFiles) => {
100
- const { dispatchChatUploadFileList, startAsyncTask } = get();
96
+ const { dispatchChatUploadFileList } = get();
101
97
  // 0. skip file in blacklist
102
98
  const files = rawFiles.filter((file) => !FILE_UPLOAD_BLACKLIST.includes(file.name));
103
99
  // 1. add files with base64
@@ -154,52 +150,8 @@ export const createFileSlice: StateCreator<
154
150
  // image don't need to be chunked and embedding
155
151
  if (isChunkingUnsupported(file.type)) return;
156
152
 
157
- // 3. auto chunk and embedding
158
- dispatchChatUploadFileList({
159
- id: fileResult.id,
160
- type: 'updateFile',
161
- // make the taks empty to hint the user that the task is starting but not triggered
162
- value: { tasks: {} },
163
- });
164
-
165
- await startAsyncTask(
166
- fileResult.id,
167
- async (id) => {
168
- const data = await ragService.createParseFileTask(id);
169
- if (!data || !data.id) throw new Error('failed to createParseFileTask');
170
-
171
- // run the assignment
172
- useAgentStore
173
- .getState()
174
- .addFilesToAgent([id], false)
175
- .then(() => {
176
- // trigger the tip if it's the first time
177
- if (!preferenceSelectors.shouldTriggerFileInKnowledgeBaseTip(useUserStore.getState()))
178
- return;
179
-
180
- userService.updateGuide({ uploadFileInKnowledgeBase: true });
181
- });
182
-
183
- return data.id;
184
- },
185
-
186
- (fileItem) => {
187
- dispatchChatUploadFileList({
188
- id: fileResult.id,
189
- type: 'updateFile',
190
- value: {
191
- tasks: {
192
- chunkCount: fileItem.chunkCount,
193
- chunkingError: fileItem.chunkingError,
194
- chunkingStatus: fileItem.chunkingStatus,
195
- embeddingError: fileItem.embeddingError,
196
- embeddingStatus: fileItem.embeddingStatus,
197
- finishEmbedding: fileItem.finishEmbedding,
198
- },
199
- },
200
- });
201
- },
202
- );
153
+ const data = await ragService.parseFileContent(fileResult.id);
154
+ console.log(data);
203
155
  });
204
156
 
205
157
  await Promise.all(pools);