@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +59 -1
- package/package.json +3 -2
- package/packages/file-loaders/package.json +5 -1
- package/packages/file-loaders/src/loadFile.ts +51 -1
- package/packages/file-loaders/src/loaders/docx/index.ts +16 -1
- package/packages/file-loaders/src/loaders/excel/index.ts +30 -2
- package/packages/file-loaders/src/loaders/pdf/__snapshots__/index.test.ts.snap +1 -1
- package/packages/file-loaders/src/loaders/pdf/index.ts +52 -12
- package/packages/file-loaders/src/loaders/pptx/index.ts +32 -1
- package/packages/file-loaders/src/loaders/text/index.test.ts +1 -1
- package/packages/file-loaders/src/loaders/text/index.ts +13 -1
- package/packages/file-loaders/test/__snapshots__/loaders.test.ts.snap +41 -0
- package/packages/file-loaders/test/loaders.test.ts +20 -0
- package/packages/file-loaders/test/setup.ts +17 -0
- package/packages/file-loaders/vitest.config.ts +14 -0
- package/src/config/aiModels/infiniai.ts +113 -9
- package/src/const/file.ts +8 -1
- package/src/database/client/migrations.json +23 -1
- package/src/database/migrations/0022_add_documents.sql +49 -0
- package/src/database/migrations/meta/0022_snapshot.json +5340 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/models/_template.ts +1 -1
- package/src/database/models/document.ts +54 -0
- package/src/database/models/message.ts +25 -0
- package/src/database/repositories/tableViewer/index.test.ts +1 -1
- package/src/database/schemas/document.ts +104 -0
- package/src/database/schemas/index.ts +1 -0
- package/src/database/schemas/relations.ts +34 -2
- package/src/database/schemas/topic.ts +31 -8
- package/src/database/utils/idGenerator.ts +1 -0
- package/src/features/ChatInput/Desktop/FilePreview/FileItem/Content.tsx +1 -1
- package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +10 -10
- package/src/features/ChatInput/components/UploadDetail/UploadStatus.tsx +2 -2
- package/src/features/Conversation/Actions/Error.tsx +2 -2
- package/src/libs/agent-runtime/infiniai/index.ts +1 -1
- package/src/libs/trpc/lambda/context.ts +7 -0
- package/src/prompts/files/file.ts +6 -4
- package/src/server/routers/lambda/__tests__/message.test.ts +213 -0
- package/src/server/routers/lambda/document.ts +36 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/services/document/index.ts +66 -0
- package/src/server/services/file/__tests__/index.test.ts +115 -0
- package/src/server/services/mcp/index.ts +0 -4
- package/src/server/utils/__tests__/tempFileManager.test.ts +94 -0
- package/src/services/rag.ts +4 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +2 -2
- package/src/store/chat/slices/aiChat/actions/rag.ts +2 -3
- package/src/store/file/slices/chat/action.ts +3 -51
- package/src/types/document/index.ts +172 -0
- package/src/types/message/chat.ts +1 -0
- package/src/features/ChatInput/Desktop/FilePreview/FileItem/style.ts +0 -4
@@ -154,6 +154,13 @@
|
|
154
154
|
"when": 1744602998656,
|
155
155
|
"tag": "0021_add_agent_opening_settings",
|
156
156
|
"breakpoints": true
|
157
|
+
},
|
158
|
+
{
|
159
|
+
"idx": 22,
|
160
|
+
"version": "7",
|
161
|
+
"when": 1746724476380,
|
162
|
+
"tag": "0022_add_documents",
|
163
|
+
"breakpoints": true
|
157
164
|
}
|
158
165
|
],
|
159
166
|
"version": "6"
|
@@ -13,7 +13,7 @@ export class TemplateModel {
|
|
13
13
|
this.db = db;
|
14
14
|
}
|
15
15
|
|
16
|
-
create = async (params: NewSessionGroup) => {
|
16
|
+
create = async (params: Omit<NewSessionGroup, 'userId'>) => {
|
17
17
|
const [result] = await this.db
|
18
18
|
.insert(sessionGroups)
|
19
19
|
.values({ ...params, userId: this.userId })
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import { and, desc, eq } from 'drizzle-orm/expressions';
|
2
|
+
|
3
|
+
import { LobeChatDatabase } from '@/database/type';
|
4
|
+
|
5
|
+
import { DocumentItem, NewDocument, documents } from '../schemas';
|
6
|
+
|
7
|
+
export class DocumentModel {
|
8
|
+
private userId: string;
|
9
|
+
private db: LobeChatDatabase;
|
10
|
+
|
11
|
+
constructor(db: LobeChatDatabase, userId: string) {
|
12
|
+
this.userId = userId;
|
13
|
+
this.db = db;
|
14
|
+
}
|
15
|
+
|
16
|
+
create = async (params: Omit<NewDocument, 'userId'>) => {
|
17
|
+
const [result] = await this.db
|
18
|
+
.insert(documents)
|
19
|
+
.values({ ...params, userId: this.userId })
|
20
|
+
.returning();
|
21
|
+
|
22
|
+
return result;
|
23
|
+
};
|
24
|
+
|
25
|
+
delete = async (id: string) => {
|
26
|
+
return this.db
|
27
|
+
.delete(documents)
|
28
|
+
.where(and(eq(documents.id, id), eq(documents.userId, this.userId)));
|
29
|
+
};
|
30
|
+
|
31
|
+
deleteAll = async () => {
|
32
|
+
return this.db.delete(documents).where(eq(documents.userId, this.userId));
|
33
|
+
};
|
34
|
+
|
35
|
+
query = async () => {
|
36
|
+
return this.db.query.documents.findMany({
|
37
|
+
orderBy: [desc(documents.updatedAt)],
|
38
|
+
where: eq(documents.userId, this.userId),
|
39
|
+
});
|
40
|
+
};
|
41
|
+
|
42
|
+
findById = async (id: string) => {
|
43
|
+
return this.db.query.documents.findFirst({
|
44
|
+
where: and(eq(documents.id, id), eq(documents.userId, this.userId)),
|
45
|
+
});
|
46
|
+
};
|
47
|
+
|
48
|
+
update = async (id: string, value: Partial<DocumentItem>) => {
|
49
|
+
return this.db
|
50
|
+
.update(documents)
|
51
|
+
.set({ ...value, updatedAt: new Date() })
|
52
|
+
.where(and(eq(documents.id, id), eq(documents.userId, this.userId)));
|
53
|
+
};
|
54
|
+
}
|
@@ -30,6 +30,7 @@ import { today } from '@/utils/time';
|
|
30
30
|
import {
|
31
31
|
MessagePluginItem,
|
32
32
|
chunks,
|
33
|
+
documents,
|
33
34
|
embeddings,
|
34
35
|
fileChunks,
|
35
36
|
files,
|
@@ -154,6 +155,29 @@ export class MessageModel {
|
|
154
155
|
})),
|
155
156
|
);
|
156
157
|
|
158
|
+
// 获取关联的文档内容
|
159
|
+
const fileIds = relatedFileList.map((file) => file.id).filter(Boolean);
|
160
|
+
|
161
|
+
let documentsMap: Record<string, string> = {};
|
162
|
+
|
163
|
+
if (fileIds.length > 0) {
|
164
|
+
const documentsList = await this.db
|
165
|
+
.select({
|
166
|
+
content: documents.content,
|
167
|
+
fileId: documents.fileId,
|
168
|
+
})
|
169
|
+
.from(documents)
|
170
|
+
.where(inArray(documents.fileId, fileIds));
|
171
|
+
|
172
|
+
documentsMap = documentsList.reduce(
|
173
|
+
(acc, doc) => {
|
174
|
+
if (doc.fileId) acc[doc.fileId] = doc.content as string;
|
175
|
+
return acc;
|
176
|
+
},
|
177
|
+
{} as Record<string, string>,
|
178
|
+
);
|
179
|
+
}
|
180
|
+
|
157
181
|
const imageList = relatedFileList.filter((i) => (i.fileType || '').startsWith('image'));
|
158
182
|
const fileList = relatedFileList.filter((i) => !(i.fileType || '').startsWith('image'));
|
159
183
|
|
@@ -214,6 +238,7 @@ export class MessageModel {
|
|
214
238
|
.filter((relation) => relation.messageId === item.id)
|
215
239
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
216
240
|
.map<ChatFileItem>(({ id, url, size, fileType, name }) => ({
|
241
|
+
content: documentsMap[id],
|
217
242
|
fileType: fileType!,
|
218
243
|
id,
|
219
244
|
name: name!,
|
@@ -23,7 +23,7 @@ describe('TableViewerRepo', () => {
|
|
23
23
|
it('should return all tables with counts', async () => {
|
24
24
|
const result = await repo.getAllTables();
|
25
25
|
|
26
|
-
expect(result.length).toEqual(
|
26
|
+
expect(result.length).toEqual(51);
|
27
27
|
expect(result[0]).toEqual({ name: 'agents', count: 0, type: 'BASE TABLE' });
|
28
28
|
});
|
29
29
|
|
@@ -0,0 +1,104 @@
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
2
|
+
import {
|
3
|
+
index,
|
4
|
+
integer,
|
5
|
+
jsonb,
|
6
|
+
pgTable,
|
7
|
+
primaryKey,
|
8
|
+
text,
|
9
|
+
uniqueIndex,
|
10
|
+
uuid,
|
11
|
+
varchar,
|
12
|
+
} from 'drizzle-orm/pg-core';
|
13
|
+
import { createInsertSchema } from 'drizzle-zod';
|
14
|
+
|
15
|
+
import { chunks } from '@/database/schemas/rag';
|
16
|
+
import { idGenerator } from '@/database/utils/idGenerator';
|
17
|
+
import { LobeDocumentPage } from '@/types/document';
|
18
|
+
|
19
|
+
import { createdAt, timestamps } from './_helpers';
|
20
|
+
import { files } from './file';
|
21
|
+
import { users } from './user';
|
22
|
+
|
23
|
+
/**
|
24
|
+
* 文档表 - 存储文件内容或网页搜索结果
|
25
|
+
*/
|
26
|
+
export const documents = pgTable(
|
27
|
+
'documents',
|
28
|
+
{
|
29
|
+
id: varchar('id', { length: 30 })
|
30
|
+
.$defaultFn(() => idGenerator('documents', 16))
|
31
|
+
.primaryKey(),
|
32
|
+
|
33
|
+
// 基本信息
|
34
|
+
title: text('title'),
|
35
|
+
content: text('content'),
|
36
|
+
fileType: varchar('file_type', { length: 255 }).notNull(),
|
37
|
+
filename: text('filename'),
|
38
|
+
|
39
|
+
// 统计信息
|
40
|
+
totalCharCount: integer('total_char_count').notNull(),
|
41
|
+
totalLineCount: integer('total_line_count').notNull(),
|
42
|
+
|
43
|
+
// 元数据
|
44
|
+
metadata: jsonb('metadata').$type<Record<string, any>>(),
|
45
|
+
|
46
|
+
// 页面/块数据
|
47
|
+
pages: jsonb('pages').$type<LobeDocumentPage[]>(),
|
48
|
+
|
49
|
+
// 来源类型
|
50
|
+
sourceType: text('source_type', { enum: ['file', 'web', 'api'] }).notNull(),
|
51
|
+
source: text('source').notNull(), // 文件路径或网页URL
|
52
|
+
|
53
|
+
// 关联文件(可选)
|
54
|
+
fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
|
55
|
+
|
56
|
+
// 用户关联
|
57
|
+
userId: text('user_id')
|
58
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
59
|
+
.notNull(),
|
60
|
+
clientId: text('client_id'),
|
61
|
+
|
62
|
+
// 时间戳
|
63
|
+
...timestamps,
|
64
|
+
},
|
65
|
+
(table) => [
|
66
|
+
index('documents_source_idx').on(table.source),
|
67
|
+
index('documents_file_type_idx').on(table.fileType),
|
68
|
+
index('documents_file_id_idx').on(table.fileId),
|
69
|
+
uniqueIndex('documents_client_id_user_id_unique').on(table.clientId, table.userId),
|
70
|
+
],
|
71
|
+
);
|
72
|
+
|
73
|
+
export type NewDocument = typeof documents.$inferInsert;
|
74
|
+
export type DocumentItem = typeof documents.$inferSelect;
|
75
|
+
export const insertDocumentSchema = createInsertSchema(documents);
|
76
|
+
|
77
|
+
/**
|
78
|
+
* 文档块表 - 将文档内容分割成块并关联到 chunks 表,用于向量检索
|
79
|
+
* 注意:此表可选,如果已经使用 pages 字段存储了文档块,可以不需要此表
|
80
|
+
*/
|
81
|
+
export const documentChunks = pgTable(
|
82
|
+
'document_chunks',
|
83
|
+
{
|
84
|
+
documentId: varchar('document_id', { length: 30 })
|
85
|
+
.references(() => documents.id, { onDelete: 'cascade' })
|
86
|
+
.notNull(),
|
87
|
+
|
88
|
+
chunkId: uuid('chunk_id')
|
89
|
+
.references(() => chunks.id, { onDelete: 'cascade' })
|
90
|
+
.notNull(),
|
91
|
+
|
92
|
+
pageIndex: integer('page_index'),
|
93
|
+
|
94
|
+
userId: text('user_id')
|
95
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
96
|
+
.notNull(),
|
97
|
+
|
98
|
+
createdAt: createdAt(),
|
99
|
+
},
|
100
|
+
(t) => [primaryKey({ columns: [t.documentId, t.chunkId] })],
|
101
|
+
);
|
102
|
+
|
103
|
+
export type NewDocumentChunk = typeof documentChunks.$inferInsert;
|
104
|
+
export type DocumentChunkItem = typeof documentChunks.$inferSelect;
|
@@ -6,11 +6,12 @@ import { createdAt } from '@/database/schemas/_helpers';
|
|
6
6
|
|
7
7
|
import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
|
8
8
|
import { asyncTasks } from './asyncTask';
|
9
|
+
import { documentChunks, documents } from './document';
|
9
10
|
import { files, knowledgeBases } from './file';
|
10
11
|
import { messages, messagesFiles } from './message';
|
11
12
|
import { chunks, unstructuredChunks } from './rag';
|
12
13
|
import { sessionGroups, sessions } from './session';
|
13
|
-
import { threads, topics } from './topic';
|
14
|
+
import { threads, topicDocuments, topics } from './topic';
|
14
15
|
import { users } from './user';
|
15
16
|
|
16
17
|
export const agentsToSessions = pgTable(
|
@@ -65,11 +66,12 @@ export const fileChunks = pgTable(
|
|
65
66
|
);
|
66
67
|
export type NewFileChunkItem = typeof fileChunks.$inferInsert;
|
67
68
|
|
68
|
-
export const topicRelations = relations(topics, ({ one }) => ({
|
69
|
+
export const topicRelations = relations(topics, ({ one, many }) => ({
|
69
70
|
session: one(sessions, {
|
70
71
|
fields: [topics.sessionId],
|
71
72
|
references: [sessions.id],
|
72
73
|
}),
|
74
|
+
documents: many(topicDocuments),
|
73
75
|
}));
|
74
76
|
|
75
77
|
export const threadsRelations = relations(threads, ({ one }) => ({
|
@@ -151,6 +153,7 @@ export const filesRelations = relations(files, ({ many, one }) => ({
|
|
151
153
|
messages: many(messagesFiles),
|
152
154
|
sessions: many(filesToSessions),
|
153
155
|
agents: many(agentsFiles),
|
156
|
+
documents: many(documents, { relationName: 'fileDocuments' }),
|
154
157
|
|
155
158
|
chunkingTask: one(asyncTasks, {
|
156
159
|
fields: [files.chunkTaskId],
|
@@ -161,3 +164,32 @@ export const filesRelations = relations(files, ({ many, one }) => ({
|
|
161
164
|
references: [asyncTasks.id],
|
162
165
|
}),
|
163
166
|
}));
|
167
|
+
|
168
|
+
// Document 相关关系定义
|
169
|
+
export const documentsRelations = relations(documents, ({ one, many }) => ({
|
170
|
+
file: one(files, {
|
171
|
+
fields: [documents.fileId],
|
172
|
+
references: [files.id],
|
173
|
+
relationName: 'fileDocuments',
|
174
|
+
}),
|
175
|
+
topics: many(topicDocuments),
|
176
|
+
chunks: many(documentChunks),
|
177
|
+
}));
|
178
|
+
|
179
|
+
export const topicDocumentsRelations = relations(topicDocuments, ({ one }) => ({
|
180
|
+
document: one(documents, {
|
181
|
+
fields: [topicDocuments.documentId],
|
182
|
+
references: [documents.id],
|
183
|
+
}),
|
184
|
+
topic: one(topics, {
|
185
|
+
fields: [topicDocuments.topicId],
|
186
|
+
references: [topics.id],
|
187
|
+
}),
|
188
|
+
}));
|
189
|
+
|
190
|
+
export const documentChunksRelations = relations(documentChunks, ({ one }) => ({
|
191
|
+
document: one(documents, {
|
192
|
+
fields: [documentChunks.documentId],
|
193
|
+
references: [documents.id],
|
194
|
+
}),
|
195
|
+
}));
|
@@ -1,11 +1,12 @@
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
2
|
-
import { boolean, jsonb, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core';
|
2
|
+
import { boolean, jsonb, pgTable, primaryKey, text, uniqueIndex } from 'drizzle-orm/pg-core';
|
3
3
|
import { createInsertSchema } from 'drizzle-zod';
|
4
4
|
|
5
|
+
import { documents } from '@/database/schemas/document';
|
5
6
|
import { idGenerator } from '@/database/utils/idGenerator';
|
6
7
|
import { ChatTopicMetadata } from '@/types/topic';
|
7
8
|
|
8
|
-
import { timestamps, timestamptz } from './_helpers';
|
9
|
+
import { createdAt, timestamps, timestamptz } from './_helpers';
|
9
10
|
import { sessions } from './session';
|
10
11
|
import { users } from './user';
|
11
12
|
|
@@ -26,9 +27,7 @@ export const topics = pgTable(
|
|
26
27
|
metadata: jsonb('metadata').$type<ChatTopicMetadata | undefined>(),
|
27
28
|
...timestamps,
|
28
29
|
},
|
29
|
-
(t) => (
|
30
|
-
clientIdUnique: uniqueIndex('topics_client_id_user_id_unique').on(t.clientId, t.userId),
|
31
|
-
}),
|
30
|
+
(t) => [uniqueIndex('topics_client_id_user_id_unique').on(t.clientId, t.userId)],
|
32
31
|
);
|
33
32
|
|
34
33
|
export type NewTopic = typeof topics.$inferInsert;
|
@@ -60,11 +59,35 @@ export const threads = pgTable(
|
|
60
59
|
lastActiveAt: timestamptz('last_active_at').defaultNow(),
|
61
60
|
...timestamps,
|
62
61
|
},
|
63
|
-
(t) => (
|
64
|
-
clientIdUnique: uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId),
|
65
|
-
}),
|
62
|
+
(t) => [uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId)],
|
66
63
|
);
|
67
64
|
|
68
65
|
export type NewThread = typeof threads.$inferInsert;
|
69
66
|
export type ThreadItem = typeof threads.$inferSelect;
|
70
67
|
export const insertThreadSchema = createInsertSchema(threads);
|
68
|
+
|
69
|
+
/**
|
70
|
+
* 文档与话题关联表 - 实现文档和话题的多对多关系
|
71
|
+
*/
|
72
|
+
export const topicDocuments = pgTable(
|
73
|
+
'topic_documents',
|
74
|
+
{
|
75
|
+
documentId: text('document_id')
|
76
|
+
.notNull()
|
77
|
+
.references(() => documents.id, { onDelete: 'cascade' }),
|
78
|
+
|
79
|
+
topicId: text('topic_id')
|
80
|
+
.notNull()
|
81
|
+
.references(() => topics.id, { onDelete: 'cascade' }),
|
82
|
+
|
83
|
+
userId: text('user_id')
|
84
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
85
|
+
.notNull(),
|
86
|
+
|
87
|
+
createdAt: createdAt(),
|
88
|
+
},
|
89
|
+
(t) => [primaryKey({ columns: [t.documentId, t.topicId] })],
|
90
|
+
);
|
91
|
+
|
92
|
+
export type NewTopicDocument = typeof topicDocuments.$inferInsert;
|
93
|
+
export type TopicDocumentItem = typeof topicDocuments.$inferSelect;
|
@@ -31,7 +31,7 @@ const Content = memo<UploadFileItem>(({ file, previewUrl }) => {
|
|
31
31
|
return <video className={styles.video} src={previewUrl} width={'100%'} />;
|
32
32
|
}
|
33
33
|
|
34
|
-
return <FileIcon fileName={file.name} fileType={file.type} size={
|
34
|
+
return <FileIcon fileName={file.name} fileType={file.type} size={48} />;
|
35
35
|
});
|
36
36
|
|
37
37
|
export default Content;
|
@@ -11,7 +11,6 @@ import { UploadFileItem } from '@/types/files/upload';
|
|
11
11
|
|
12
12
|
import UploadDetail from '../../../components/UploadDetail';
|
13
13
|
import Content from './Content';
|
14
|
-
import { FILE_ITEM_SIZE } from './style';
|
15
14
|
|
16
15
|
const useStyles = createStyles(({ css, token }) => ({
|
17
16
|
actions: css`
|
@@ -30,12 +29,15 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
30
29
|
container: css`
|
31
30
|
position: relative;
|
32
31
|
|
33
|
-
width:
|
34
|
-
|
35
|
-
height: ${FILE_ITEM_SIZE}px;
|
32
|
+
width: 180px;
|
33
|
+
height: 64px;
|
36
34
|
border-radius: 8px;
|
37
35
|
|
38
36
|
background: ${token.colorBgContainer};
|
37
|
+
|
38
|
+
:hover {
|
39
|
+
background: ${token.colorBgElevated};
|
40
|
+
}
|
39
41
|
`,
|
40
42
|
image: css`
|
41
43
|
margin-block: 0 !important;
|
@@ -50,8 +52,6 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
50
52
|
|
51
53
|
type FileItemProps = UploadFileItem;
|
52
54
|
|
53
|
-
const spacing = 8;
|
54
|
-
|
55
55
|
const FileItem = memo<FileItemProps>((props) => {
|
56
56
|
const { file, uploadState, status, id, tasks } = props;
|
57
57
|
const { t } = useTranslation(['chat', 'common']);
|
@@ -59,12 +59,12 @@ const FileItem = memo<FileItemProps>((props) => {
|
|
59
59
|
const [removeChatUploadFile] = useFileStore((s) => [s.removeChatUploadFile]);
|
60
60
|
|
61
61
|
return (
|
62
|
-
<Flexbox className={styles.container}
|
63
|
-
<Center flex={1} height={
|
62
|
+
<Flexbox align={'center'} className={styles.container} horizontal>
|
63
|
+
<Center flex={1} height={64} padding={4} style={{ maxWidth: 64 }}>
|
64
64
|
<Content {...props} />
|
65
65
|
</Center>
|
66
|
-
<Flexbox gap={4} style={{ paddingBottom: 4, paddingInline:
|
67
|
-
<Typography.Text ellipsis={{ tooltip: true }} style={{ fontSize: 12 }}>
|
66
|
+
<Flexbox flex={1} gap={4} style={{ paddingBottom: 4, paddingInline: 4 }}>
|
67
|
+
<Typography.Text ellipsis={{ tooltip: true }} style={{ fontSize: 12, maxWidth: 100 }}>
|
68
68
|
{file.name}
|
69
69
|
</Typography.Text>
|
70
70
|
|
@@ -38,7 +38,7 @@ const UploadStatus = memo<UploadStateProps>(({ status, size, uploadState }) => {
|
|
38
38
|
<Flexbox align={'center'} gap={4} horizontal>
|
39
39
|
<Progress percent={uploadState?.progress} size={14} type="circle" />
|
40
40
|
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
41
|
-
{formatSize(size * ((uploadState?.progress || 0) / 100),
|
41
|
+
{formatSize(size * ((uploadState?.progress || 0) / 100), 0)} / {formatSize(size)}
|
42
42
|
</Typography.Text>
|
43
43
|
</Flexbox>
|
44
44
|
);
|
@@ -49,7 +49,7 @@ const UploadStatus = memo<UploadStateProps>(({ status, size, uploadState }) => {
|
|
49
49
|
<Flexbox align={'center'} gap={4} horizontal>
|
50
50
|
<Progress percent={uploadState?.progress} size={14} type="circle" />
|
51
51
|
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
52
|
-
{formatSize(size)}
|
52
|
+
{formatSize(size)}
|
53
53
|
</Typography.Text>
|
54
54
|
</Flexbox>
|
55
55
|
);
|
@@ -5,13 +5,13 @@ import { memo } from 'react';
|
|
5
5
|
import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
|
6
6
|
|
7
7
|
export const ErrorActionsBar = memo<ChatActionsBarProps>(({ onActionClick }) => {
|
8
|
-
const { regenerate, copy, edit, del } = useChatListActionsBar();
|
8
|
+
const { regenerate, copy, edit, del, divider } = useChatListActionsBar();
|
9
9
|
|
10
10
|
return (
|
11
11
|
<ActionIconGroup
|
12
12
|
items={[regenerate, del]}
|
13
13
|
menu={{
|
14
|
-
items: [edit, copy],
|
14
|
+
items: [edit, copy, divider, del],
|
15
15
|
}}
|
16
16
|
onActionClick={onActionClick}
|
17
17
|
/>
|
@@ -44,7 +44,7 @@ export const LobeInfiniAI = LobeOpenAICompatibleFactory({
|
|
44
44
|
models: async ({ client }) => {
|
45
45
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
46
46
|
|
47
|
-
const reasoningKeywords = ['deepseek-r1', 'qwq'];
|
47
|
+
const reasoningKeywords = ['deepseek-r1', 'qwq', 'qwen3'];
|
48
48
|
const visionKeywords = ['qwen2.5-vl'];
|
49
49
|
|
50
50
|
const modelsPage = (await client.models.list()) as any;
|
@@ -57,6 +57,13 @@ export type LambdaContext = Awaited<ReturnType<typeof createContextInner>>;
|
|
57
57
|
* @link https://trpc.io/docs/v11/context
|
58
58
|
*/
|
59
59
|
export const createLambdaContext = async (request: NextRequest): Promise<LambdaContext> => {
|
60
|
+
// we have a special header to debug the api endpoint in development mode
|
61
|
+
// IT WON'T GO INTO PRODUCTION ANYMORE
|
62
|
+
const isDebugApi = request.headers.get('lobe-auth-dev-backend-api') === '1';
|
63
|
+
if (process.env.NODE_ENV === 'development' && isDebugApi) {
|
64
|
+
return { userId: process.env.MOCK_DEV_USER_ID };
|
65
|
+
}
|
66
|
+
|
60
67
|
log('createLambdaContext called for request');
|
61
68
|
// for API-response caching see https://trpc.io/docs/v11/caching
|
62
69
|
|
@@ -1,9 +1,11 @@
|
|
1
1
|
import { ChatFileItem } from '@/types/message';
|
2
2
|
|
3
|
-
const filePrompt = (item: ChatFileItem, addUrl: boolean) =>
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
const filePrompt = (item: ChatFileItem, addUrl: boolean) => {
|
4
|
+
const content = item.content || '';
|
5
|
+
return addUrl
|
6
|
+
? `<file id="${item.id}" name="${item.name}" type="${item.fileType}" size="${item.size}" url="${item.url}">${content}</file>`
|
7
|
+
: `<file id="${item.id}" name="${item.name}" type="${item.fileType}" size="${item.size}">${content}</file>`;
|
8
|
+
};
|
7
9
|
|
8
10
|
export const filePrompts = (fileList: ChatFileItem[], addUrl: boolean) => {
|
9
11
|
if (fileList.length === 0) return '';
|