@lobehub/chat 1.116.0 → 1.116.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.
@@ -210,6 +210,13 @@
210
210
  "when": 1753201379817,
211
211
  "tag": "0029_add_apikey_manage",
212
212
  "breakpoints": true
213
+ },
214
+ {
215
+ "idx": 30,
216
+ "version": "7",
217
+ "when": 1756298669289,
218
+ "tag": "0030_add_group_chat",
219
+ "breakpoints": true
213
220
  }
214
221
  ],
215
222
  "version": "6"
@@ -559,5 +559,24 @@
559
559
  "bps": true,
560
560
  "folderMillis": 1753201379817,
561
561
  "hash": "fe5c0d7c2768189771c42ef93693fc1d58586b468c4bdde7fb6f2dc58cc9931c"
562
+ },
563
+ {
564
+ "sql": [
565
+ "CREATE TABLE IF NOT EXISTS \"chat_groups\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"title\" text,\n\t\"description\" text,\n\t\"config\" jsonb,\n\t\"client_id\" text,\n\t\"user_id\" text NOT NULL,\n\t\"pinned\" boolean DEFAULT false,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL\n);\n",
566
+ "\nCREATE TABLE IF NOT EXISTS \"chat_groups_agents\" (\n\t\"chat_group_id\" text NOT NULL,\n\t\"agent_id\" text NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"enabled\" boolean DEFAULT true,\n\t\"order\" integer DEFAULT 0,\n\t\"role\" text DEFAULT 'participant',\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\tCONSTRAINT \"chat_groups_agents_chat_group_id_agent_id_pk\" PRIMARY KEY(\"chat_group_id\",\"agent_id\")\n);\n",
567
+ "\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
568
+ "\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"target_id\" text;",
569
+ "\nALTER TABLE \"topics\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
570
+ "\nALTER TABLE \"chat_groups\" ADD CONSTRAINT \"chat_groups_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
571
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_chat_group_id_chat_groups_id_fk\" FOREIGN KEY (\"chat_group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;",
572
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_agent_id_agents_id_fk\" FOREIGN KEY (\"agent_id\") REFERENCES \"public\".\"agents\"(\"id\") ON DELETE cascade ON UPDATE no action;",
573
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
574
+ "\nCREATE UNIQUE INDEX \"chat_groups_client_id_user_id_unique\" ON \"chat_groups\" USING btree (\"client_id\",\"user_id\");",
575
+ "\nALTER TABLE \"messages\" ADD CONSTRAINT \"messages_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE set null ON UPDATE no action;",
576
+ "\nALTER TABLE \"topics\" ADD CONSTRAINT \"topics_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;\n"
577
+ ],
578
+ "bps": true,
579
+ "folderMillis": 1756298669289,
580
+ "hash": "3af468ca75761ca4ca4e32ba8704728f0499aa935bfe00ae5aabfa8405a18bd4"
562
581
  }
563
582
  ]
@@ -1,8 +1,8 @@
1
1
  import { eq, inArray } from 'drizzle-orm';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { LobeChatDatabase } from '../../type';
5
4
  import { messages, sessions, topics, users } from '../../schemas';
5
+ import { LobeChatDatabase } from '../../type';
6
6
  import { CreateTopicParams, TopicModel } from '../topic';
7
7
  import { getTestDB } from './_util';
8
8
 
@@ -423,6 +423,7 @@ describe('TopicModel', () => {
423
423
  userId,
424
424
  historySummary: null,
425
425
  metadata: null,
426
+ groupId: null,
426
427
  clientId: null,
427
428
  createdAt: expect.any(Date),
428
429
  updatedAt: expect.any(Date),
@@ -469,6 +470,7 @@ describe('TopicModel', () => {
469
470
  title: 'New Topic',
470
471
  favorite: false,
471
472
  clientId: null,
473
+ groupId: null,
472
474
  historySummary: null,
473
475
  metadata: null,
474
476
  sessionId,
@@ -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(60);
26
+ expect(result.length).toEqual(62);
27
27
  expect(result[0]).toEqual({ name: 'agents', count: 0, type: 'BASE TABLE' });
28
28
  });
29
29
 
@@ -0,0 +1,98 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import {
3
+ boolean,
4
+ integer,
5
+ jsonb,
6
+ pgTable,
7
+ primaryKey,
8
+ text,
9
+ uniqueIndex,
10
+ varchar,
11
+ } from 'drizzle-orm/pg-core';
12
+ import { createInsertSchema } from 'drizzle-zod';
13
+
14
+ import { idGenerator } from '@/database/utils/idGenerator';
15
+ import type { ChatGroupConfig } from '@/database/types/chatGroup';
16
+
17
+ import { timestamps } from './_helpers';
18
+ import { agents } from './agent';
19
+ import { users } from './user';
20
+
21
+ /**
22
+ * Chat groups table for multi-agent conversations
23
+ * Allows multiple agents to participate in a single chat session
24
+ */
25
+ export const chatGroups = pgTable(
26
+ 'chat_groups',
27
+ {
28
+ id: text('id')
29
+ .primaryKey()
30
+ .$defaultFn(() => idGenerator('chatGroups'))
31
+ .notNull(),
32
+ title: text('title'),
33
+ description: text('description'),
34
+
35
+ /**
36
+ * Group configuration
37
+ */
38
+ config: jsonb('config').$type<ChatGroupConfig>(),
39
+
40
+ clientId: text('client_id'),
41
+
42
+ userId: text('user_id')
43
+ .references(() => users.id, { onDelete: 'cascade' })
44
+ .notNull(),
45
+
46
+ pinned: boolean('pinned').default(false),
47
+
48
+ ...timestamps,
49
+ },
50
+ (t) => [uniqueIndex('chat_groups_client_id_user_id_unique').on(t.clientId, t.userId)],
51
+ );
52
+
53
+ export const insertChatGroupSchema = createInsertSchema(chatGroups);
54
+
55
+ export type NewChatGroup = typeof chatGroups.$inferInsert;
56
+ export type ChatGroupItem = typeof chatGroups.$inferSelect;
57
+
58
+ /**
59
+ * Junction table connecting chat groups with agents
60
+ * Defines which agents participate in each group chat
61
+ */
62
+ export const chatGroupsAgents = pgTable(
63
+ 'chat_groups_agents',
64
+ {
65
+ chatGroupId: text('chat_group_id')
66
+ .references(() => chatGroups.id, { onDelete: 'cascade' })
67
+ .notNull(),
68
+ agentId: text('agent_id')
69
+ .references(() => agents.id, { onDelete: 'cascade' })
70
+ .notNull(),
71
+ userId: text('user_id')
72
+ .references(() => users.id, { onDelete: 'cascade' })
73
+ .notNull(),
74
+
75
+ /**
76
+ * Whether this agent is active in the group
77
+ */
78
+ enabled: boolean('enabled').default(true),
79
+
80
+ /**
81
+ * Display or speaking order of the agent in the group
82
+ */
83
+ order: integer('order').default(0),
84
+
85
+ /**
86
+ * Role of the agent in the group (e.g., 'moderator', 'participant')
87
+ */
88
+ role: text('role').default('participant'),
89
+
90
+ ...timestamps,
91
+ },
92
+ (t) => ({
93
+ pk: primaryKey({ columns: [t.chatGroupId, t.agentId] }),
94
+ }),
95
+ );
96
+
97
+ export type NewChatGroupAgent = typeof chatGroupsAgents.$inferInsert;
98
+ export type ChatGroupAgentItem = typeof agents.$inferInsert
@@ -2,6 +2,7 @@ export * from './agent';
2
2
  export * from './aiInfra';
3
3
  export * from './apiKey';
4
4
  export * from './asyncTask';
5
+ export * from './chatGroup';
5
6
  export * from './document';
6
7
  export * from './file';
7
8
  export * from './generation';
@@ -23,6 +23,7 @@ import { chunks, embeddings } from './rag';
23
23
  import { sessions } from './session';
24
24
  import { threads, topics } from './topic';
25
25
  import { users } from './user';
26
+ import { chatGroups } from './chatGroup';
26
27
 
27
28
  // @ts-ignore
28
29
  export const messages = pgTable(
@@ -64,7 +65,9 @@ export const messages = pgTable(
64
65
 
65
66
  // used for group chat
66
67
  agentId: text('agent_id').references(() => agents.id, { onDelete: 'set null' }),
67
-
68
+ groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'set null' }),
69
+ // targetId can be an agent ID, "user", or null - no FK constraint
70
+ targetId: text('target_id'),
68
71
  ...timestamps,
69
72
  },
70
73
  (table) => ({
@@ -5,6 +5,7 @@ import { pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
5
5
  import { createdAt } from './_helpers';
6
6
  import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
7
7
  import { asyncTasks } from './asyncTask';
8
+ import { chatGroups, chatGroupsAgents } from './chatGroup';
8
9
  import { documentChunks, documents } from './document';
9
10
  import { files, knowledgeBases } from './file';
10
11
  import { generationBatches, generationTopics, generations } from './generation';
@@ -109,6 +110,7 @@ export const agentsRelations = relations(agents, ({ many }) => ({
109
110
  agentsToSessions: many(agentsToSessions),
110
111
  knowledgeBases: many(agentsKnowledgeBases),
111
112
  files: many(agentsFiles),
113
+ chatGroups: many(chatGroupsAgents),
112
114
  }));
113
115
 
114
116
  export const agentsToSessionsRelations = relations(agentsToSessions, ({ one }) => ({
@@ -280,3 +282,27 @@ export const generationsRelations = relations(generations, ({ one }) => ({
280
282
  references: [files.id],
281
283
  }),
282
284
  }));
285
+
286
+ // Chat Groups 相关关系定义
287
+ export const chatGroupsRelations = relations(chatGroups, ({ many, one }) => ({
288
+ user: one(users, {
289
+ fields: [chatGroups.userId],
290
+ references: [users.id],
291
+ }),
292
+ agents: many(chatGroupsAgents),
293
+ }));
294
+
295
+ export const chatGroupsAgentsRelations = relations(chatGroupsAgents, ({ one }) => ({
296
+ chatGroup: one(chatGroups, {
297
+ fields: [chatGroupsAgents.chatGroupId],
298
+ references: [chatGroups.id],
299
+ }),
300
+ agent: one(agents, {
301
+ fields: [chatGroupsAgents.agentId],
302
+ references: [agents.id],
303
+ }),
304
+ user: one(users, {
305
+ fields: [chatGroupsAgents.userId],
306
+ references: [users.id],
307
+ }),
308
+ }));
@@ -9,6 +9,7 @@ import { createdAt, timestamps, timestamptz } from './_helpers';
9
9
  import { documents } from './document';
10
10
  import { sessions } from './session';
11
11
  import { users } from './user';
12
+ import { chatGroups } from './chatGroup';
12
13
 
13
14
  export const topics = pgTable(
14
15
  'topics',
@@ -19,6 +20,7 @@ export const topics = pgTable(
19
20
  title: text('title'),
20
21
  favorite: boolean('favorite').default(false),
21
22
  sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
23
+ groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'cascade' }),
22
24
  userId: text('user_id')
23
25
  .references(() => users.id, { onDelete: 'cascade' })
24
26
  .notNull(),
@@ -0,0 +1,9 @@
1
+ export interface ChatGroupConfig {
2
+ maxResponseInRow?: number;
3
+ orchestratorModel?: string;
4
+ orchestratorProvider?: string;
5
+ responseOrder?: 'sequential' | 'natural';
6
+ responseSpeed?: 'slow' | 'medium' | 'fast';
7
+ revealDM?: boolean;
8
+ systemPrompt?: string;
9
+ }
@@ -7,6 +7,7 @@ export const createNanoId = (size = 8) =>
7
7
 
8
8
  const prefixes = {
9
9
  agents: 'agt',
10
+ chatGroups: 'cg',
10
11
  documents: 'docs',
11
12
  files: 'file',
12
13
  generationBatches: 'gb',
@@ -1,4 +1,4 @@
1
- import type { ChatModelCard } from '@/types/llm';
1
+ import type { ChatModelCard } from '@lobechat/types';
2
2
 
3
3
  import type { ModelProviderKey } from '../types';
4
4
 
@@ -168,11 +168,8 @@ const findKnownModelByProvider = async (
168
168
  const lowerModelId = modelId.toLowerCase();
169
169
 
170
170
  try {
171
- // 动态构建导入路径
172
- const modulePath = `@/config/aiModels/${provider}`;
173
-
174
171
  // 尝试动态导入对应的配置文件
175
- const moduleImport = await import(modulePath);
172
+ const moduleImport = await import(`@/config/aiModels/${provider}`);
176
173
  const providerModels = moduleImport.default;
177
174
 
178
175
  // 如果导入成功且有数据,进行查找
@@ -211,9 +208,21 @@ export const detectModelProvider = (modelId: string): keyof typeof MODEL_LIST_CO
211
208
  * @param timestamp 时间戳(秒)
212
209
  * @returns 格式化的日期字符串 (YYYY-MM-DD)
213
210
  */
214
- const formatTimestampToDate = (timestamp: number): string => {
215
- const date = new Date(timestamp * 1000); // 将秒转换为毫秒
216
- return date.toISOString().split('T')[0]; // 返回 YYYY-MM-DD 格式
211
+ const formatTimestampToDate = (timestamp: number): string | undefined => {
212
+ if (timestamp === null || timestamp === undefined || Number.isNaN(timestamp)) return undefined;
213
+
214
+ // 支持秒级或毫秒级时间戳:
215
+ // - 如果是毫秒级(>= 1e12),直接当作毫秒;
216
+ // - 否则视为秒,需要 *1000 转为毫秒
217
+ const msTimestamp = timestamp > 1e12 ? timestamp : timestamp * 1000;
218
+ const date = new Date(msTimestamp);
219
+
220
+ // 验证解析结果和年份范围(只接受 4 位年份,避免超出 varchar(10) 的 YYYY-MM-DD)
221
+ const year = date.getUTCFullYear();
222
+ if (year < 1000 || year > 9999) return undefined;
223
+
224
+ const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
225
+ return dateStr.length === 10 ? dateStr : undefined;
217
226
  };
218
227
 
219
228
  /**
@@ -17,8 +17,8 @@ import useResizeObserver from './useResizeObserver';
17
17
  pdfjs.GlobalWorkerOptions.workerSrc = `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/build/pdf.worker.min.mjs`;
18
18
 
19
19
  const options = {
20
- cMapUrl: '/cmaps/',
21
- standardFontDataUrl: '/standard_fonts/',
20
+ cMapUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/cmaps/`,
21
+ standardFontDataUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/standard_fonts/`,
22
22
  };
23
23
 
24
24
  const maxWidth = 1200;