@lobehub/chat 0.162.25 → 0.163.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.
Files changed (84) hide show
  1. package/.github/workflows/release.yml +21 -2
  2. package/.github/workflows/sync.yml +1 -1
  3. package/.github/workflows/test.yml +35 -4
  4. package/CHANGELOG.md +25 -0
  5. package/LICENSE +38 -21
  6. package/codecov.yml +11 -0
  7. package/drizzle.config.ts +29 -0
  8. package/next.config.mjs +3 -0
  9. package/package.json +24 -4
  10. package/scripts/migrateServerDB/index.ts +30 -0
  11. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +2 -1
  12. package/src/app/(main)/chat/@session/features/SessionListContent/List/Item/Actions.tsx +95 -88
  13. package/src/app/(main)/chat/settings/features/HeaderContent.tsx +37 -31
  14. package/src/app/api/webhooks/clerk/__tests__/fixtures/createUser.json +73 -0
  15. package/src/app/api/webhooks/clerk/route.ts +159 -0
  16. package/src/app/api/webhooks/clerk/validateRequest.ts +22 -0
  17. package/src/app/trpc/edge/[trpc]/route.ts +1 -1
  18. package/src/app/trpc/lambda/[trpc]/route.ts +26 -0
  19. package/src/config/auth.ts +2 -0
  20. package/src/config/db.ts +13 -1
  21. package/src/database/server/core/db.ts +44 -0
  22. package/src/database/server/core/dbForTest.ts +45 -0
  23. package/src/database/server/index.ts +1 -0
  24. package/src/database/server/migrations/0000_init.sql +439 -0
  25. package/src/database/server/migrations/0001_add_client_id.sql +9 -0
  26. package/src/database/server/migrations/0002_amusing_puma.sql +9 -0
  27. package/src/database/server/migrations/meta/0000_snapshot.json +1583 -0
  28. package/src/database/server/migrations/meta/0001_snapshot.json +1636 -0
  29. package/src/database/server/migrations/meta/0002_snapshot.json +1630 -0
  30. package/src/database/server/migrations/meta/_journal.json +27 -0
  31. package/src/database/server/models/__tests__/file.test.ts +140 -0
  32. package/src/database/server/models/__tests__/message.test.ts +847 -0
  33. package/src/database/server/models/__tests__/plugin.test.ts +172 -0
  34. package/src/database/server/models/__tests__/session.test.ts +595 -0
  35. package/src/database/server/models/__tests__/topic.test.ts +623 -0
  36. package/src/database/server/models/__tests__/user.test.ts +173 -0
  37. package/src/database/server/models/_template.ts +44 -0
  38. package/src/database/server/models/file.ts +51 -0
  39. package/src/database/server/models/message.ts +378 -0
  40. package/src/database/server/models/plugin.ts +63 -0
  41. package/src/database/server/models/session.ts +290 -0
  42. package/src/database/server/models/sessionGroup.ts +69 -0
  43. package/src/database/server/models/topic.ts +265 -0
  44. package/src/database/server/models/user.ts +138 -0
  45. package/src/database/server/modules/DataImporter/__tests__/fixtures/messages.json +1101 -0
  46. package/src/database/server/modules/DataImporter/__tests__/index.test.ts +954 -0
  47. package/src/database/server/modules/DataImporter/index.ts +333 -0
  48. package/src/database/server/schemas/_id.ts +15 -0
  49. package/src/database/server/schemas/lobechat.ts +601 -0
  50. package/src/database/server/utils/idGenerator.test.ts +39 -0
  51. package/src/database/server/utils/idGenerator.ts +26 -0
  52. package/src/features/User/UserPanel/useMenu.tsx +43 -37
  53. package/src/libs/trpc/client.ts +52 -3
  54. package/src/server/files/s3.ts +21 -1
  55. package/src/server/keyVaultsEncrypt/index.test.ts +62 -0
  56. package/src/server/keyVaultsEncrypt/index.ts +93 -0
  57. package/src/server/mock.ts +1 -1
  58. package/src/server/routers/{index.ts → edge/index.ts} +3 -3
  59. package/src/server/routers/lambda/file.ts +49 -0
  60. package/src/server/routers/lambda/importer.ts +54 -0
  61. package/src/server/routers/lambda/index.ts +28 -0
  62. package/src/server/routers/lambda/message.ts +165 -0
  63. package/src/server/routers/lambda/plugin.ts +100 -0
  64. package/src/server/routers/lambda/session.ts +194 -0
  65. package/src/server/routers/lambda/sessionGroup.ts +77 -0
  66. package/src/server/routers/lambda/topic.ts +134 -0
  67. package/src/server/routers/lambda/user.ts +57 -0
  68. package/src/services/file/index.ts +4 -7
  69. package/src/services/file/server.ts +45 -0
  70. package/src/services/import/index.ts +4 -1
  71. package/src/services/import/server.ts +115 -0
  72. package/src/services/message/index.ts +4 -8
  73. package/src/services/message/server.ts +93 -0
  74. package/src/services/plugin/index.ts +4 -9
  75. package/src/services/plugin/server.ts +46 -0
  76. package/src/services/session/index.ts +4 -8
  77. package/src/services/session/server.ts +148 -0
  78. package/src/services/topic/index.ts +4 -9
  79. package/src/services/topic/server.ts +68 -0
  80. package/src/services/user/index.ts +4 -9
  81. package/src/services/user/server.ts +28 -0
  82. package/tests/setup-db.ts +7 -0
  83. package/vitest.config.ts +2 -1
  84. package/vitest.server.config.ts +23 -0
@@ -0,0 +1,601 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
3
+ import { relations } from 'drizzle-orm';
4
+ import {
5
+ boolean,
6
+ index,
7
+ integer,
8
+ jsonb,
9
+ pgTable,
10
+ primaryKey,
11
+ serial,
12
+ text,
13
+ timestamp,
14
+ unique,
15
+ uniqueIndex,
16
+ varchar,
17
+ } from 'drizzle-orm/pg-core';
18
+ import { createInsertSchema } from 'drizzle-zod';
19
+
20
+ import { DEFAULT_PREFERENCE } from '@/const/user';
21
+ import { LobeAgentChatConfig, LobeAgentTTSConfig } from '@/types/agent';
22
+ import { CustomPluginParams } from '@/types/tool/plugin';
23
+
24
+ import { idGenerator, randomSlug } from '../utils/idGenerator';
25
+
26
+ const timestamptz = (name: string) => timestamp(name, { withTimezone: true });
27
+
28
+ const createdAt = () => timestamptz('created_at').notNull().defaultNow();
29
+ const updatedAt = () => timestamptz('updated_at').notNull().defaultNow();
30
+
31
+ /**
32
+ * This table stores users. Users are created in Clerk, then Clerk calls a
33
+ * webhook at /api/webhook/clerk to inform this application a user was created.
34
+ */
35
+ export const users = pgTable('users', {
36
+ // The ID will be the user's ID from Clerk
37
+ id: text('id').primaryKey().notNull(),
38
+ username: text('username').unique(),
39
+ email: text('email'),
40
+
41
+ avatar: text('avatar'),
42
+ phone: text('phone'),
43
+ firstName: text('first_name'),
44
+ lastName: text('last_name'),
45
+
46
+ isOnboarded: boolean('is_onboarded').default(false),
47
+ // Time user was created in Clerk
48
+ clerkCreatedAt: timestamptz('clerk_created_at'),
49
+
50
+ preference: jsonb('preference').default(DEFAULT_PREFERENCE),
51
+
52
+ createdAt: createdAt(),
53
+ updatedAt: updatedAt(),
54
+
55
+ key: text('key'),
56
+ });
57
+
58
+ export const userSettings = pgTable('user_settings', {
59
+ id: text('id')
60
+ .references(() => users.id, { onDelete: 'cascade' })
61
+ .primaryKey(),
62
+
63
+ tts: jsonb('tts'),
64
+ keyVaults: text('key_vaults'),
65
+ general: jsonb('general'),
66
+ languageModel: jsonb('language_model'),
67
+ systemAgent: jsonb('system_agent'),
68
+ defaultAgent: jsonb('default_agent'),
69
+ tool: jsonb('tool'),
70
+ });
71
+
72
+ export const tags = pgTable('tags', {
73
+ id: serial('id').primaryKey(),
74
+ slug: text('slug').notNull().unique(),
75
+ name: text('name'),
76
+
77
+ userId: text('user_id')
78
+ .references(() => users.id, { onDelete: 'cascade' })
79
+ .notNull(),
80
+
81
+ createdAt: createdAt(),
82
+ updatedAt: updatedAt(),
83
+ });
84
+
85
+ export type NewUser = typeof users.$inferInsert;
86
+ export type UserItem = typeof users.$inferSelect;
87
+
88
+ export const files = pgTable('files', {
89
+ id: text('id')
90
+ .$defaultFn(() => idGenerator('files'))
91
+ .primaryKey(),
92
+
93
+ userId: text('user_id')
94
+ .references(() => users.id, { onDelete: 'cascade' })
95
+ .notNull(),
96
+ fileType: varchar('file_type', { length: 255 }).notNull(),
97
+ name: text('name').notNull(),
98
+ size: integer('size').notNull(),
99
+ url: text('url').notNull(),
100
+
101
+ metadata: jsonb('metadata'),
102
+
103
+ createdAt: createdAt(),
104
+ updatedAt: updatedAt(),
105
+ });
106
+
107
+ export type NewFile = typeof files.$inferInsert;
108
+ export type FileItem = typeof files.$inferSelect;
109
+
110
+ export const plugins = pgTable('plugins', {
111
+ id: serial('id').primaryKey(),
112
+ identifier: text('identifier').notNull().unique(),
113
+
114
+ title: text('title').notNull(),
115
+ description: text('description'),
116
+ avatar: text('avatar'),
117
+ author: text('author'),
118
+
119
+ manifest: text('manifest').notNull(),
120
+ locale: text('locale').notNull(),
121
+ createdAt: createdAt(),
122
+ updatedAt: updatedAt(),
123
+ });
124
+
125
+ export const installedPlugins = pgTable(
126
+ 'user_installed_plugins',
127
+ {
128
+ userId: text('user_id')
129
+ .references(() => users.id, { onDelete: 'cascade' })
130
+ .notNull(),
131
+
132
+ identifier: text('identifier').notNull(),
133
+ type: text('type', { enum: ['plugin', 'customPlugin'] }).notNull(),
134
+ manifest: jsonb('manifest').$type<LobeChatPluginManifest>(),
135
+ settings: jsonb('settings'),
136
+ customParams: jsonb('custom_params').$type<CustomPluginParams>(),
137
+
138
+ createdAt: createdAt(),
139
+ updatedAt: updatedAt(),
140
+ },
141
+ (self) => ({
142
+ id: primaryKey({ columns: [self.userId, self.identifier] }),
143
+ }),
144
+ );
145
+
146
+ export type NewInstalledPlugin = typeof installedPlugins.$inferInsert;
147
+ export type InstalledPluginItem = typeof installedPlugins.$inferSelect;
148
+
149
+ export const pluginsTags = pgTable(
150
+ 'plugins_tags',
151
+ {
152
+ pluginId: integer('plugin_id')
153
+ .notNull()
154
+ .references(() => plugins.id, { onDelete: 'cascade' }),
155
+ tagId: integer('tag_id')
156
+ .notNull()
157
+ .references(() => tags.id, { onDelete: 'cascade' }),
158
+ },
159
+ (t) => ({
160
+ pk: primaryKey({ columns: [t.pluginId, t.tagId] }),
161
+ }),
162
+ );
163
+
164
+ // ======= agents ======= //
165
+ export const agents = pgTable('agents', {
166
+ id: text('id')
167
+ .primaryKey()
168
+ .$defaultFn(() => idGenerator('agents'))
169
+ .notNull(),
170
+ slug: varchar('slug', { length: 100 })
171
+ .$defaultFn(() => randomSlug())
172
+ .unique(),
173
+ title: text('title'),
174
+ description: text('description'),
175
+ tags: jsonb('tags').$type<string[]>().default([]),
176
+ avatar: text('avatar'),
177
+ backgroundColor: text('background_color'),
178
+
179
+ plugins: jsonb('plugins').$type<string[]>().default([]),
180
+ userId: text('user_id')
181
+ .references(() => users.id, { onDelete: 'cascade' })
182
+ .notNull(),
183
+
184
+ chatConfig: jsonb('chat_config').$type<LobeAgentChatConfig>(),
185
+
186
+ fewShots: jsonb('few_shots'),
187
+ model: text('model'),
188
+ params: jsonb('params').default({}),
189
+ provider: text('provider'),
190
+ systemRole: text('system_role'),
191
+ tts: jsonb('tts').$type<LobeAgentTTSConfig>(),
192
+
193
+ createdAt: createdAt(),
194
+ updatedAt: updatedAt(),
195
+ });
196
+
197
+ export const agentsTags = pgTable(
198
+ 'agents_tags',
199
+ {
200
+ agentId: text('agent_id')
201
+ .notNull()
202
+ .references(() => agents.id, { onDelete: 'cascade' }),
203
+ tagId: integer('tag_id')
204
+ .notNull()
205
+ .references(() => tags.id, { onDelete: 'cascade' }),
206
+ },
207
+ (t) => ({
208
+ pk: primaryKey({ columns: [t.agentId, t.tagId] }),
209
+ }),
210
+ );
211
+ export const insertAgentSchema = createInsertSchema(agents);
212
+
213
+ export type NewAgent = typeof agents.$inferInsert;
214
+ export type AgentItem = typeof agents.$inferSelect;
215
+
216
+ // ======= market ======= //
217
+
218
+ export const market = pgTable('market', {
219
+ id: serial('id').primaryKey(),
220
+
221
+ agentId: text('agent_id').references(() => agents.id, { onDelete: 'cascade' }),
222
+ pluginId: integer('plugin_id').references(() => plugins.id, { onDelete: 'cascade' }),
223
+
224
+ type: text('type', { enum: ['plugin', 'model', 'agent', 'group'] }).notNull(),
225
+
226
+ view: integer('view').default(0),
227
+ like: integer('like').default(0),
228
+ used: integer('used').default(0),
229
+
230
+ userId: text('user_id')
231
+ .references(() => users.id, { onDelete: 'cascade' })
232
+ .notNull(),
233
+
234
+ createdAt: createdAt(),
235
+ updatedAt: updatedAt(),
236
+ });
237
+
238
+ // ======= sessionGroups ======= //
239
+
240
+ export const sessionGroups = pgTable(
241
+ 'session_groups',
242
+ {
243
+ id: text('id')
244
+ .$defaultFn(() => idGenerator('sessionGroups'))
245
+ .primaryKey(),
246
+ name: text('name').notNull(),
247
+ sort: integer('sort'),
248
+
249
+ userId: text('user_id')
250
+ .references(() => users.id, { onDelete: 'cascade' })
251
+ .notNull(),
252
+
253
+ clientId: text('client_id'),
254
+ createdAt: createdAt(),
255
+ updatedAt: updatedAt(),
256
+ },
257
+ (table) => ({
258
+ clientIdUnique: unique('session_group_client_id_user_unique').on(table.clientId, table.userId),
259
+ }),
260
+ );
261
+
262
+ export const insertSessionGroupSchema = createInsertSchema(sessionGroups);
263
+
264
+ export type NewSessionGroup = typeof sessionGroups.$inferInsert;
265
+ export type SessionGroupItem = typeof sessionGroups.$inferSelect;
266
+
267
+ // ======= sessions ======= //
268
+
269
+ export const sessions = pgTable(
270
+ 'sessions',
271
+ {
272
+ id: text('id')
273
+ .$defaultFn(() => idGenerator('sessions'))
274
+ .primaryKey(),
275
+ slug: varchar('slug', { length: 100 })
276
+ .notNull()
277
+ .$defaultFn(() => randomSlug()),
278
+ title: text('title'),
279
+ description: text('description'),
280
+ avatar: text('avatar'),
281
+ backgroundColor: text('background_color'),
282
+
283
+ type: text('type', { enum: ['agent', 'group'] }).default('agent'),
284
+
285
+ userId: text('user_id')
286
+ .references(() => users.id, { onDelete: 'cascade' })
287
+ .notNull(),
288
+ groupId: text('group_id').references(() => sessionGroups.id, { onDelete: 'set null' }),
289
+ clientId: text('client_id'),
290
+ pinned: boolean('pinned').default(false),
291
+
292
+ createdAt: createdAt(),
293
+ updatedAt: updatedAt(),
294
+ },
295
+ (t) => ({
296
+ slugUserIdUnique: uniqueIndex('slug_user_id_unique').on(t.slug, t.userId),
297
+
298
+ clientIdUnique: unique('sessions_client_id_user_id_unique').on(t.clientId, t.userId),
299
+ }),
300
+ );
301
+
302
+ export const insertSessionSchema = createInsertSchema(sessions);
303
+ // export const selectSessionSchema = createSelectSchema(sessions);
304
+
305
+ export type NewSession = typeof sessions.$inferInsert;
306
+ export type SessionItem = typeof sessions.$inferSelect;
307
+
308
+ // ======== topics ======= //
309
+ export const topics = pgTable(
310
+ 'topics',
311
+ {
312
+ id: text('id')
313
+ .$defaultFn(() => idGenerator('topics'))
314
+ .primaryKey(),
315
+ sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
316
+ userId: text('user_id')
317
+ .references(() => users.id, { onDelete: 'cascade' })
318
+ .notNull(),
319
+ favorite: boolean('favorite').default(false),
320
+ title: text('title'),
321
+ clientId: text('client_id'),
322
+
323
+ createdAt: createdAt(),
324
+ updatedAt: updatedAt(),
325
+ },
326
+ (t) => ({
327
+ clientIdUnique: unique('topic_client_id_user_id_unique').on(t.clientId, t.userId),
328
+ }),
329
+ );
330
+
331
+ export type NewTopic = typeof topics.$inferInsert;
332
+ export type TopicItem = typeof topics.$inferSelect;
333
+
334
+ // ======== messages ======= //
335
+ // @ts-ignore
336
+ export const messages = pgTable(
337
+ 'messages',
338
+ {
339
+ id: text('id')
340
+ .$defaultFn(() => idGenerator('messages'))
341
+ .primaryKey(),
342
+
343
+ role: text('role', { enum: ['user', 'system', 'assistant', 'tool'] }).notNull(),
344
+ content: text('content'),
345
+
346
+ model: text('model'),
347
+ provider: text('provider'),
348
+
349
+ favorite: boolean('favorite').default(false),
350
+ error: jsonb('error'),
351
+
352
+ tools: jsonb('tools'),
353
+
354
+ traceId: text('trace_id'),
355
+ observationId: text('observation_id'),
356
+
357
+ clientId: text('client_id'),
358
+
359
+ // foreign keys
360
+ userId: text('user_id')
361
+ .references(() => users.id, { onDelete: 'cascade' })
362
+ .notNull(),
363
+ sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
364
+ topicId: text('topic_id').references(() => topics.id, { onDelete: 'cascade' }),
365
+ parentId: text('parent_id').references(() => messages.id, { onDelete: 'set null' }),
366
+ quotaId: text('quota_id').references(() => messages.id, { onDelete: 'set null' }),
367
+
368
+ // used for group chat
369
+ agentId: text('agent_id').references(() => agents.id, { onDelete: 'set null' }),
370
+
371
+ createdAt: createdAt(),
372
+ updatedAt: updatedAt(),
373
+ },
374
+ (table) => ({
375
+ createdAtIdx: index('messages_created_at_idx').on(table.createdAt),
376
+ messageClientIdUnique: uniqueIndex('message_client_id_user_unique').on(
377
+ table.clientId,
378
+ table.userId,
379
+ ),
380
+ }),
381
+ );
382
+
383
+ export type NewMessage = typeof messages.$inferInsert;
384
+ export type MessageItem = typeof messages.$inferSelect;
385
+
386
+ export const messagePlugins = pgTable('message_plugins', {
387
+ id: text('id')
388
+ .references(() => messages.id, { onDelete: 'cascade' })
389
+ .primaryKey(),
390
+
391
+ toolCallId: text('tool_call_id'),
392
+ type: text('type', {
393
+ enum: ['default', 'markdown', 'standalone', 'builtin'],
394
+ }).default('default'),
395
+
396
+ apiName: text('api_name'),
397
+ arguments: text('arguments'),
398
+ identifier: text('identifier'),
399
+ state: jsonb('state'),
400
+ error: jsonb('error'),
401
+ });
402
+
403
+ export const messageTTS = pgTable('message_tts', {
404
+ id: text('id')
405
+ .references(() => messages.id, { onDelete: 'cascade' })
406
+ .primaryKey(),
407
+ contentMd5: text('content_md5'),
408
+ fileId: text('file_id').references(() => files.id, { onDelete: 'cascade' }),
409
+ voice: text('voice'),
410
+ });
411
+
412
+ export const messageTranslates = pgTable('message_translates', {
413
+ id: text('id')
414
+ .references(() => messages.id, { onDelete: 'cascade' })
415
+ .primaryKey(),
416
+ content: text('content'),
417
+ from: text('from'),
418
+ to: text('to'),
419
+ });
420
+
421
+ export const agentsToSessions = pgTable(
422
+ 'agents_to_sessions',
423
+ {
424
+ agentId: text('agent_id')
425
+ .notNull()
426
+ .references(() => agents.id, { onDelete: 'cascade' }),
427
+ sessionId: text('session_id')
428
+ .notNull()
429
+ .references(() => sessions.id, { onDelete: 'cascade' }),
430
+ },
431
+ (t) => ({
432
+ pk: primaryKey({ columns: [t.agentId, t.sessionId] }),
433
+ }),
434
+ );
435
+
436
+ export const filesToMessages = pgTable(
437
+ 'files_to_messages',
438
+ {
439
+ fileId: text('file_id')
440
+ .notNull()
441
+ .references(() => files.id, { onDelete: 'cascade' }),
442
+ messageId: text('message_id')
443
+ .notNull()
444
+ .references(() => messages.id, { onDelete: 'cascade' }),
445
+ },
446
+ (t) => ({
447
+ pk: primaryKey({ columns: [t.fileId, t.messageId] }),
448
+ }),
449
+ );
450
+
451
+ export const filesToSessions = pgTable(
452
+ 'files_to_sessions',
453
+ {
454
+ fileId: text('file_id')
455
+ .notNull()
456
+ .references(() => files.id, { onDelete: 'cascade' }),
457
+ sessionId: text('session_id')
458
+ .notNull()
459
+ .references(() => sessions.id, { onDelete: 'cascade' }),
460
+ },
461
+ (t) => ({
462
+ pk: primaryKey({ columns: [t.fileId, t.sessionId] }),
463
+ }),
464
+ );
465
+
466
+ export const filesToAgents = pgTable(
467
+ 'files_to_agents',
468
+ {
469
+ fileId: text('file_id')
470
+ .notNull()
471
+ .references(() => files.id, { onDelete: 'cascade' }),
472
+ agentId: text('agent_id')
473
+ .notNull()
474
+ .references(() => agents.id, { onDelete: 'cascade' }),
475
+ },
476
+ (t) => ({
477
+ pk: primaryKey({ columns: [t.fileId, t.agentId] }),
478
+ }),
479
+ );
480
+
481
+ export const filesRelations = relations(files, ({ many }) => ({
482
+ filesToMessages: many(filesToMessages),
483
+ filesToSessions: many(filesToSessions),
484
+ filesToAgents: many(filesToAgents),
485
+ }));
486
+
487
+ export const topicRelations = relations(topics, ({ one }) => ({
488
+ session: one(sessions, {
489
+ fields: [topics.sessionId],
490
+ references: [sessions.id],
491
+ }),
492
+ }));
493
+
494
+ export const pluginsRelations = relations(plugins, ({ many }) => ({
495
+ pluginsTags: many(pluginsTags),
496
+ }));
497
+
498
+ export const pluginsTagsRelations = relations(pluginsTags, ({ one }) => ({
499
+ plugin: one(plugins, {
500
+ fields: [pluginsTags.pluginId],
501
+ references: [plugins.id],
502
+ }),
503
+ tag: one(tags, {
504
+ fields: [pluginsTags.tagId],
505
+ references: [tags.id],
506
+ }),
507
+ }));
508
+
509
+ export const tagsRelations = relations(tags, ({ many }) => ({
510
+ agentsTags: many(agentsTags),
511
+ pluginsTags: many(pluginsTags),
512
+ }));
513
+
514
+ export const messagesRelations = relations(messages, ({ many, one }) => ({
515
+ filesToMessages: many(filesToMessages),
516
+
517
+ session: one(sessions, {
518
+ fields: [messages.sessionId],
519
+ references: [sessions.id],
520
+ }),
521
+
522
+ parent: one(messages, {
523
+ fields: [messages.parentId],
524
+ references: [messages.id],
525
+ }),
526
+
527
+ topic: one(topics, {
528
+ fields: [messages.topicId],
529
+ references: [topics.id],
530
+ }),
531
+ }));
532
+
533
+ export const agentsRelations = relations(agents, ({ many }) => ({
534
+ agentsToSessions: many(agentsToSessions),
535
+ filesToAgents: many(filesToAgents),
536
+ agentsTags: many(agentsTags),
537
+ }));
538
+
539
+ export const agentsToSessionsRelations = relations(agentsToSessions, ({ one }) => ({
540
+ session: one(sessions, {
541
+ fields: [agentsToSessions.sessionId],
542
+ references: [sessions.id],
543
+ }),
544
+ agent: one(agents, {
545
+ fields: [agentsToSessions.agentId],
546
+ references: [agents.id],
547
+ }),
548
+ }));
549
+
550
+ export const filesToAgentsRelations = relations(filesToAgents, ({ one }) => ({
551
+ agent: one(agents, {
552
+ fields: [filesToAgents.agentId],
553
+ references: [agents.id],
554
+ }),
555
+ file: one(files, {
556
+ fields: [filesToAgents.fileId],
557
+ references: [files.id],
558
+ }),
559
+ }));
560
+
561
+ export const filesToMessagesRelations = relations(filesToMessages, ({ one }) => ({
562
+ file: one(files, {
563
+ fields: [filesToMessages.fileId],
564
+ references: [files.id],
565
+ }),
566
+ message: one(messages, {
567
+ fields: [filesToMessages.messageId],
568
+ references: [messages.id],
569
+ }),
570
+ }));
571
+
572
+ export const filesToSessionsRelations = relations(filesToSessions, ({ one }) => ({
573
+ file: one(files, {
574
+ fields: [filesToSessions.fileId],
575
+ references: [files.id],
576
+ }),
577
+ session: one(sessions, {
578
+ fields: [filesToSessions.sessionId],
579
+ references: [sessions.id],
580
+ }),
581
+ }));
582
+
583
+ export const agentsTagsRelations = relations(agentsTags, ({ one }) => ({
584
+ agent: one(agents, {
585
+ fields: [agentsTags.agentId],
586
+ references: [agents.id],
587
+ }),
588
+ tag: one(tags, {
589
+ fields: [agentsTags.tagId],
590
+ references: [tags.id],
591
+ }),
592
+ }));
593
+
594
+ export const sessionsRelations = relations(sessions, ({ many, one }) => ({
595
+ filesToSessions: many(filesToSessions),
596
+ agentsToSessions: many(agentsToSessions),
597
+ group: one(sessionGroups, {
598
+ fields: [sessions.groupId],
599
+ references: [sessionGroups.id],
600
+ }),
601
+ }));
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { idGenerator } from './idGenerator';
4
+
5
+ describe('idGenerator', () => {
6
+ it('should generate an ID with the correct prefix and length', () => {
7
+ const fileId = idGenerator('files');
8
+ expect(fileId).toMatch(/^file_[a-zA-Z0-9]{12}$/);
9
+
10
+ const messageId = idGenerator('messages');
11
+ expect(messageId).toMatch(/^msg_[a-zA-Z0-9]{12}$/);
12
+
13
+ const pluginId = idGenerator('plugins');
14
+ expect(pluginId).toMatch(/^plg_[a-zA-Z0-9]{12}$/);
15
+
16
+ const sessionGroupId = idGenerator('sessionGroups');
17
+ expect(sessionGroupId).toMatch(/^sg_[a-zA-Z0-9]{12}$/);
18
+
19
+ const sessionId = idGenerator('sessions');
20
+ expect(sessionId).toMatch(/^ssn_[a-zA-Z0-9]{12}$/);
21
+
22
+ const topicId = idGenerator('topics');
23
+ expect(topicId).toMatch(/^tpc_[a-zA-Z0-9]{12}$/);
24
+
25
+ const userId = idGenerator('user');
26
+ expect(userId).toMatch(/^user_[a-zA-Z0-9]{12}$/);
27
+ });
28
+
29
+ it('should generate an ID with custom size', () => {
30
+ const fileId = idGenerator('files', 12);
31
+ expect(fileId).toMatch(/^file_[a-zA-Z0-9]{12}$/);
32
+ });
33
+
34
+ it('should throw an error for invalid namespace', () => {
35
+ expect(() => idGenerator('invalid' as any)).toThrowError(
36
+ 'Invalid namespace: invalid, please check your code.',
37
+ );
38
+ });
39
+ });
@@ -0,0 +1,26 @@
1
+ import { generate } from 'random-words';
2
+
3
+ import { createNanoId } from '@/utils/uuid';
4
+
5
+ const prefixes = {
6
+ agents: 'agt',
7
+ files: 'file',
8
+ messages: 'msg',
9
+ plugins: 'plg',
10
+ sessionGroups: 'sg',
11
+ sessions: 'ssn',
12
+ topics: 'tpc',
13
+ user: 'user',
14
+ } as const;
15
+
16
+ export const idGenerator = (namespace: keyof typeof prefixes, size = 12) => {
17
+ const hash = createNanoId(size);
18
+ const prefix = prefixes[namespace];
19
+
20
+ if (!prefix) throw new Error(`Invalid namespace: ${namespace}, please check your code.`);
21
+
22
+ return `${prefix}_${hash()}`;
23
+ };
24
+ export const randomSlug = () => (generate(2) as string[]).join('-');
25
+
26
+ export const inboxSessionId = (userId: string) => `ssn_inbox_${userId}`;