@lobehub/lobehub 2.1.23 → 2.1.25

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/.github/workflows/claude-auto-e2e-testing.yml +131 -0
  2. package/CHANGELOG.md +42 -0
  3. package/changelog/v2.json +14 -0
  4. package/docs/development/database-schema.dbml +43 -14
  5. package/package.json +1 -1
  6. package/packages/database/migrations/0078_added_id_nanoid_for_replacing_id.sql +7 -0
  7. package/packages/database/migrations/0079_update_id_nanoid_from_casted_id.sql +7 -0
  8. package/packages/database/migrations/0080_add_constraint_unique_not_null_to_id_nanoid.sql +27 -0
  9. package/packages/database/migrations/0081_switch_forgien_key_to_id_nanoid.sql +37 -0
  10. package/packages/database/migrations/0082_set_id_nanoid_as_primary.sql +20 -0
  11. package/packages/database/migrations/0083_remove_id_seq_identity_column.sql +7 -0
  12. package/packages/database/migrations/0084_rename_id_nanoid_to_id.sql +53 -0
  13. package/packages/database/migrations/0085_remove_id_unique_constraint.sql +7 -0
  14. package/packages/database/migrations/meta/0078_snapshot.json +11515 -0
  15. package/packages/database/migrations/meta/0079_snapshot.json +11515 -0
  16. package/packages/database/migrations/meta/0080_snapshot.json +11554 -0
  17. package/packages/database/migrations/meta/0081_snapshot.json +11554 -0
  18. package/packages/database/migrations/meta/0082_snapshot.json +11554 -0
  19. package/packages/database/migrations/meta/0083_snapshot.json +11435 -0
  20. package/packages/database/migrations/meta/0084_snapshot.json +11435 -0
  21. package/packages/database/migrations/meta/0085_snapshot.json +11396 -0
  22. package/packages/database/migrations/meta/_journal.json +56 -0
  23. package/packages/database/src/models/__tests__/apiKey.test.ts +18 -6
  24. package/packages/database/src/models/apiKey.ts +5 -5
  25. package/packages/database/src/schemas/apiKey.ts +6 -2
  26. package/packages/database/src/schemas/ragEvals.ts +27 -20
  27. package/packages/database/src/schemas/rbac.ts +15 -15
  28. package/packages/database/src/server/models/ragEval/dataset.ts +3 -3
  29. package/packages/database/src/server/models/ragEval/datasetRecord.ts +5 -5
  30. package/packages/database/src/server/models/ragEval/evaluation.ts +3 -3
  31. package/packages/database/src/server/models/ragEval/evaluationRecord.ts +6 -6
  32. package/packages/memory-user-memory/src/prompts/layers/activity.ts +19 -18
  33. package/packages/memory-user-memory/src/prompts/layers/context.ts +39 -38
  34. package/packages/memory-user-memory/src/prompts/layers/experience.ts +40 -39
  35. package/packages/memory-user-memory/src/prompts/layers/identity.ts +55 -48
  36. package/packages/memory-user-memory/src/prompts/layers/preference.ts +42 -41
  37. package/packages/types/src/apiKey.ts +1 -1
  38. package/packages/types/src/eval/dataset.ts +2 -2
  39. package/packages/types/src/eval/evaluation.ts +3 -3
  40. package/src/app/[variants]/(main)/settings/apikey/features/ApiKey.tsx +2 -2
  41. package/src/server/routers/async/ragEval.ts +1 -1
  42. package/src/server/routers/lambda/apiKey.ts +3 -3
  43. package/src/server/routers/lambda/ragEval.ts +10 -10
  44. package/src/server/routers/tools/_helpers/scheduleToolCallReport.test.ts +589 -0
  45. package/src/services/ragEval.ts +10 -10
  46. package/src/store/chat/slices/aiChat/actions/StreamingHandler.ts +6 -5
  47. package/src/store/chat/slices/aiChat/actions/__tests__/StreamingHandler.test.ts +302 -0
  48. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +168 -0
  49. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +11 -2
  50. package/src/store/chat/slices/aiChat/actions/types/streaming.ts +12 -8
  51. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +1 -1
  52. package/src/store/library/slices/ragEval/actions/dataset.ts +3 -3
  53. package/src/store/library/slices/ragEval/actions/evaluation.ts +3 -3
@@ -546,6 +546,62 @@
546
546
  "when": 1770392264696,
547
547
  "tag": "0077_add_agent_skills",
548
548
  "breakpoints": true
549
+ },
550
+ {
551
+ "idx": 78,
552
+ "version": "7",
553
+ "when": 1770621740632,
554
+ "tag": "0078_added_id_nanoid_for_replacing_id",
555
+ "breakpoints": true
556
+ },
557
+ {
558
+ "idx": 79,
559
+ "version": "7",
560
+ "when": 1770621892194,
561
+ "tag": "0079_update_id_nanoid_from_casted_id",
562
+ "breakpoints": true
563
+ },
564
+ {
565
+ "idx": 80,
566
+ "version": "7",
567
+ "when": 1770624987735,
568
+ "tag": "0080_add_constraint_unique_not_null_to_id_nanoid",
569
+ "breakpoints": true
570
+ },
571
+ {
572
+ "idx": 81,
573
+ "version": "7",
574
+ "when": 1770626135088,
575
+ "tag": "0081_switch_forgien_key_to_id_nanoid",
576
+ "breakpoints": true
577
+ },
578
+ {
579
+ "idx": 82,
580
+ "version": "7",
581
+ "when": 1770628390927,
582
+ "tag": "0082_set_id_nanoid_as_primary",
583
+ "breakpoints": true
584
+ },
585
+ {
586
+ "idx": 83,
587
+ "version": "7",
588
+ "when": 1770628799077,
589
+ "tag": "0083_remove_id_seq_identity_column",
590
+ "breakpoints": true
591
+ },
592
+ {
593
+ "idx": 84,
594
+ "version": "7",
595
+ "when": 1770631019385,
596
+ "tag": "0084_rename_id_nanoid_to_id",
597
+ "breakpoints": true
598
+ },
599
+ {
600
+ "idx": 85,
601
+ "version": "7",
602
+ "when": 1770632176750,
603
+ "tag": "0085_remove_id_unique_constraint",
604
+ "breakpoints": true
549
605
  }
550
606
  ],
551
607
  "version": "6"
@@ -2,10 +2,10 @@
2
2
  import { eq } from 'drizzle-orm';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
+ import { getTestDB } from '../../core/getTestDB';
5
6
  import { apiKeys, users } from '../../schemas';
6
7
  import { LobeChatDatabase } from '../../type';
7
8
  import { ApiKeyModel } from '../apiKey';
8
- import { getTestDB } from '../../core/getTestDB';
9
9
 
10
10
  const serverDB: LobeChatDatabase = await getTestDB();
11
11
 
@@ -95,7 +95,10 @@ describe('ApiKeyModel', () => {
95
95
  const { id: key1 } = await apiKeyModel.create({ name: 'User 1 Key', enabled: true });
96
96
 
97
97
  const anotherApiKeyModel = new ApiKeyModel(serverDB, 'user2');
98
- const { id: key2 } = await anotherApiKeyModel.create({ name: 'User 2 Key', enabled: true });
98
+ const { id: key2 } = await anotherApiKeyModel.create({
99
+ name: 'User 2 Key',
100
+ enabled: true,
101
+ });
99
102
 
100
103
  await apiKeyModel.delete(key2);
101
104
 
@@ -346,7 +349,10 @@ describe('ApiKeyModel', () => {
346
349
  const { id: key1 } = await apiKeyModel.create({ name: 'User 1 Key', enabled: true });
347
350
 
348
351
  const anotherApiKeyModel = new ApiKeyModel(serverDB, 'user2');
349
- const { id: key2 } = await anotherApiKeyModel.create({ name: 'User 2 Key', enabled: true });
352
+ const { id: key2 } = await anotherApiKeyModel.create({
353
+ name: 'User 2 Key',
354
+ enabled: true,
355
+ });
350
356
 
351
357
  await apiKeyModel.update(key2, { name: 'Attempted Update' });
352
358
 
@@ -372,14 +378,17 @@ describe('ApiKeyModel', () => {
372
378
  });
373
379
 
374
380
  it('should return undefined for non-existent id', async () => {
375
- const found = await apiKeyModel.findById(999_999);
381
+ const found = await apiKeyModel.findById('999_999');
376
382
 
377
383
  expect(found).toBeUndefined();
378
384
  });
379
385
 
380
386
  it('should only find API keys for the current user', async () => {
381
387
  const anotherApiKeyModel = new ApiKeyModel(serverDB, 'user2');
382
- const { id: key2 } = await anotherApiKeyModel.create({ name: 'User 2 Key', enabled: true });
388
+ const { id: key2 } = await anotherApiKeyModel.create({
389
+ name: 'User 2 Key',
390
+ enabled: true,
391
+ });
383
392
 
384
393
  const found = await apiKeyModel.findById(key2);
385
394
 
@@ -406,7 +415,10 @@ describe('ApiKeyModel', () => {
406
415
 
407
416
  it('should only update API keys for the current user', async () => {
408
417
  const anotherApiKeyModel = new ApiKeyModel(serverDB, 'user2');
409
- const { id: key2 } = await anotherApiKeyModel.create({ name: 'User 2 Key', enabled: true });
418
+ const { id: key2 } = await anotherApiKeyModel.create({
419
+ name: 'User 2 Key',
420
+ enabled: true,
421
+ });
410
422
 
411
423
  await apiKeyModel.updateLastUsed(key2);
412
424
 
@@ -1,9 +1,9 @@
1
1
  import { and, desc, eq } from 'drizzle-orm';
2
2
 
3
- import { LobeChatDatabase } from '../type';
4
3
  import { generateApiKey, isApiKeyExpired, validateApiKeyFormat } from '@/utils/apiKey';
5
4
 
6
5
  import { ApiKeyItem, NewApiKeyItem, apiKeys } from '../schemas';
6
+ import { LobeChatDatabase } from '../type';
7
7
 
8
8
  type EncryptAPIKeyVaults = (keyVaults: string) => Promise<string>;
9
9
  type DecryptAPIKeyVaults = (keyVaults: string) => Promise<{ plaintext: string }>;
@@ -37,7 +37,7 @@ export class ApiKeyModel {
37
37
  return result;
38
38
  };
39
39
 
40
- delete = async (id: number) => {
40
+ delete = async (id: string) => {
41
41
  return this.db.delete(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.userId, this.userId)));
42
42
  };
43
43
 
@@ -94,20 +94,20 @@ export class ApiKeyModel {
94
94
  return true;
95
95
  };
96
96
 
97
- update = async (id: number, value: Partial<ApiKeyItem>) => {
97
+ update = async (id: string, value: Partial<ApiKeyItem>) => {
98
98
  return this.db
99
99
  .update(apiKeys)
100
100
  .set({ ...value, updatedAt: new Date() })
101
101
  .where(and(eq(apiKeys.id, id), eq(apiKeys.userId, this.userId)));
102
102
  };
103
103
 
104
- findById = async (id: number) => {
104
+ findById = async (id: string) => {
105
105
  return this.db.query.apiKeys.findFirst({
106
106
  where: and(eq(apiKeys.id, id), eq(apiKeys.userId, this.userId)),
107
107
  });
108
108
  };
109
109
 
110
- updateLastUsed = async (id: number) => {
110
+ updateLastUsed = async (id: string) => {
111
111
  return this.db
112
112
  .update(apiKeys)
113
113
  .set({ lastUsedAt: new Date() })
@@ -1,14 +1,18 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
- import { boolean, index, integer, pgTable, text, varchar } from 'drizzle-orm/pg-core';
2
+ import { boolean, index, pgTable, text, varchar } from 'drizzle-orm/pg-core';
3
3
  import { createInsertSchema } from 'drizzle-zod';
4
4
 
5
+ import { createNanoId } from '../utils/idGenerator';
5
6
  import { timestamps, timestamptz } from './_helpers';
6
7
  import { users } from './user';
7
8
 
8
9
  export const apiKeys = pgTable(
9
10
  'api_keys',
10
11
  {
11
- id: integer('id').primaryKey().generatedByDefaultAsIdentity(), // auto-increment primary key
12
+ id: text('id')
13
+ .$defaultFn(() => createNanoId(16)())
14
+ .notNull()
15
+ .primaryKey(),
12
16
  name: varchar('name', { length: 256 }).notNull(), // name of the API key
13
17
  key: varchar('key', { length: 256 }).notNull().unique(), // API key
14
18
  enabled: boolean('enabled').default(true), // whether the API key is enabled
@@ -3,6 +3,7 @@ import { DEFAULT_MODEL } from '@lobechat/const';
3
3
  import { EvalEvaluationStatus } from '@lobechat/types';
4
4
  import { index, integer, jsonb, pgTable, text, uuid } from 'drizzle-orm/pg-core';
5
5
 
6
+ import { createNanoId } from '../utils/idGenerator';
6
7
  import { timestamps } from './_helpers';
7
8
  import { knowledgeBases } from './file';
8
9
  import { embeddings } from './rag';
@@ -11,7 +12,10 @@ import { users } from './user';
11
12
  export const evalDatasets = pgTable(
12
13
  'rag_eval_datasets',
13
14
  {
14
- id: integer('id').generatedAlwaysAsIdentity({ startWith: 30_000 }).primaryKey(),
15
+ id: text('id')
16
+ .$defaultFn(() => createNanoId(16)())
17
+ .notNull()
18
+ .primaryKey(),
15
19
 
16
20
  description: text('description'),
17
21
  name: text('name').notNull(),
@@ -23,9 +27,7 @@ export const evalDatasets = pgTable(
23
27
 
24
28
  ...timestamps,
25
29
  },
26
- (t) => ({
27
- userIdIdx: index('rag_eval_datasets_user_id_idx').on(t.userId),
28
- }),
30
+ (t) => [index('rag_eval_datasets_user_id_idx').on(t.userId)],
29
31
  );
30
32
 
31
33
  export type NewEvalDatasetsItem = typeof evalDatasets.$inferInsert;
@@ -34,8 +36,12 @@ export type EvalDatasetsSelectItem = typeof evalDatasets.$inferSelect;
34
36
  export const evalDatasetRecords = pgTable(
35
37
  'rag_eval_dataset_records',
36
38
  {
37
- id: integer('id').generatedAlwaysAsIdentity().primaryKey(),
38
- datasetId: integer('dataset_id')
39
+ id: text('id')
40
+ .$defaultFn(() => createNanoId(32)())
41
+ .notNull()
42
+ .primaryKey(),
43
+
44
+ datasetId: text('dataset_id')
39
45
  .references(() => evalDatasets.id, { onDelete: 'cascade' })
40
46
  .notNull(),
41
47
 
@@ -47,9 +53,7 @@ export const evalDatasetRecords = pgTable(
47
53
  userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
48
54
  ...timestamps,
49
55
  },
50
- (t) => ({
51
- userIdIdx: index('rag_eval_dataset_records_user_id_idx').on(t.userId),
52
- }),
56
+ (t) => [index('rag_eval_dataset_records_user_id_idx').on(t.userId)],
53
57
  );
54
58
 
55
59
  export type NewEvalDatasetRecordsItem = typeof evalDatasetRecords.$inferInsert;
@@ -58,7 +62,11 @@ export type EvalDatasetRecordsSelectItem = typeof evalDatasetRecords.$inferSelec
58
62
  export const evalEvaluation = pgTable(
59
63
  'rag_eval_evaluations',
60
64
  {
61
- id: integer('id').generatedAlwaysAsIdentity().primaryKey(),
65
+ id: text('id')
66
+ .$defaultFn(() => createNanoId(32)())
67
+ .notNull()
68
+ .primaryKey(),
69
+
62
70
  name: text('name').notNull(),
63
71
  description: text('description'),
64
72
 
@@ -66,7 +74,7 @@ export const evalEvaluation = pgTable(
66
74
  status: text('status').$defaultFn(() => EvalEvaluationStatus.Pending),
67
75
  error: jsonb('error'),
68
76
 
69
- datasetId: integer('dataset_id')
77
+ datasetId: text('dataset_id')
70
78
  .references(() => evalDatasets.id, { onDelete: 'cascade' })
71
79
  .notNull(),
72
80
  knowledgeBaseId: text('knowledge_base_id').references(() => knowledgeBases.id, {
@@ -78,9 +86,7 @@ export const evalEvaluation = pgTable(
78
86
  userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
79
87
  ...timestamps,
80
88
  },
81
- (t) => ({
82
- userIdIdx: index('rag_eval_evaluations_user_id_idx').on(t.userId),
83
- }),
89
+ (t) => [index('rag_eval_evaluations_user_id_idx').on(t.userId)],
84
90
  );
85
91
 
86
92
  export type NewEvalEvaluationItem = typeof evalEvaluation.$inferInsert;
@@ -89,7 +95,10 @@ export type EvalEvaluationSelectItem = typeof evalEvaluation.$inferSelect;
89
95
  export const evaluationRecords = pgTable(
90
96
  'rag_eval_evaluation_records',
91
97
  {
92
- id: integer('id').generatedAlwaysAsIdentity().primaryKey(),
98
+ id: text('id')
99
+ .$defaultFn(() => createNanoId(32)())
100
+ .notNull()
101
+ .primaryKey(),
93
102
 
94
103
  question: text('question').notNull(),
95
104
  answer: text('answer'),
@@ -107,19 +116,17 @@ export const evaluationRecords = pgTable(
107
116
  }),
108
117
 
109
118
  duration: integer('duration'),
110
- datasetRecordId: integer('dataset_record_id')
119
+ datasetRecordId: text('dataset_record_id')
111
120
  .references(() => evalDatasetRecords.id, { onDelete: 'cascade' })
112
121
  .notNull(),
113
- evaluationId: integer('evaluation_id')
122
+ evaluationId: text('evaluation_id')
114
123
  .references(() => evalEvaluation.id, { onDelete: 'cascade' })
115
124
  .notNull(),
116
125
 
117
126
  userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
118
127
  ...timestamps,
119
128
  },
120
- (t) => ({
121
- userIdIdx: index('rag_eval_evaluation_records_user_id_idx').on(t.userId),
122
- }),
129
+ (t) => [index('rag_eval_evaluation_records_user_id_idx').on(t.userId)],
123
130
  );
124
131
 
125
132
  export type NewEvaluationRecordsItem = typeof evaluationRecords.$inferInsert;
@@ -1,21 +1,17 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
- import {
3
- boolean,
4
- index,
5
- integer,
6
- jsonb,
7
- pgTable,
8
- primaryKey,
9
- text,
10
- timestamp,
11
- } from 'drizzle-orm/pg-core';
2
+ import { boolean, index, jsonb, pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
12
3
 
4
+ import { createNanoId } from '../utils/idGenerator';
13
5
  import { timestamps } from './_helpers';
14
6
  import { users } from './user';
15
7
 
16
8
  // Roles table
17
9
  export const roles = pgTable('rbac_roles', {
18
- id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
10
+ id: text('id')
11
+ .$defaultFn(() => createNanoId(16)())
12
+ .notNull()
13
+ .primaryKey(),
14
+
19
15
  name: text('name').notNull().unique(), // Role name, e.g.: admin, user, guest
20
16
  displayName: text('display_name').notNull(), // Display name
21
17
  description: text('description'), // Role description
@@ -31,7 +27,11 @@ export type RoleItem = typeof roles.$inferSelect;
31
27
 
32
28
  // Permissions table
33
29
  export const permissions = pgTable('rbac_permissions', {
34
- id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
30
+ id: text('id')
31
+ .$defaultFn(() => createNanoId(16)())
32
+ .notNull()
33
+ .primaryKey(),
34
+
35
35
  code: text('code').notNull().unique(), // Permission code, e.g.: chat:create, file:upload
36
36
  name: text('name').notNull(), // Permission name
37
37
  description: text('description'), // Permission description
@@ -48,10 +48,10 @@ export type PermissionItem = typeof permissions.$inferSelect;
48
48
  export const rolePermissions = pgTable(
49
49
  'rbac_role_permissions',
50
50
  {
51
- roleId: integer('role_id')
51
+ roleId: text('role_id')
52
52
  .references(() => roles.id, { onDelete: 'cascade' })
53
53
  .notNull(),
54
- permissionId: integer('permission_id')
54
+ permissionId: text('permission_id')
55
55
  .references(() => permissions.id, { onDelete: 'cascade' })
56
56
  .notNull(),
57
57
 
@@ -74,7 +74,7 @@ export const userRoles = pgTable(
74
74
  userId: text('user_id')
75
75
  .references(() => users.id, { onDelete: 'cascade' })
76
76
  .notNull(),
77
- roleId: integer('role_id')
77
+ roleId: text('role_id')
78
78
  .references(() => roles.id, { onDelete: 'cascade' })
79
79
  .notNull(),
80
80
 
@@ -21,7 +21,7 @@ export class EvalDatasetModel {
21
21
  return result;
22
22
  };
23
23
 
24
- delete = async (id: number) => {
24
+ delete = async (id: string) => {
25
25
  return this.db
26
26
  .delete(evalDatasets)
27
27
  .where(and(eq(evalDatasets.id, id), eq(evalDatasets.userId, this.userId)));
@@ -46,13 +46,13 @@ export class EvalDatasetModel {
46
46
  .orderBy(desc(evalDatasets.createdAt));
47
47
  };
48
48
 
49
- findById = async (id: number) => {
49
+ findById = async (id: string) => {
50
50
  return this.db.query.evalDatasets.findFirst({
51
51
  where: and(eq(evalDatasets.id, id), eq(evalDatasets.userId, this.userId)),
52
52
  });
53
53
  };
54
54
 
55
- update = async (id: number, value: Partial<NewEvalDatasetsItem>) => {
55
+ update = async (id: string, value: Partial<NewEvalDatasetsItem>) => {
56
56
  return this.db
57
57
  .update(evalDatasets)
58
58
  .set({ ...value, updatedAt: new Date() })
@@ -30,13 +30,13 @@ export class EvalDatasetRecordModel {
30
30
  return result;
31
31
  };
32
32
 
33
- delete = async (id: number) => {
33
+ delete = async (id: string) => {
34
34
  return this.db
35
35
  .delete(evalDatasetRecords)
36
36
  .where(and(eq(evalDatasetRecords.id, id), eq(evalDatasetRecords.userId, this.userId)));
37
37
  };
38
38
 
39
- query = async (datasetId: number) => {
39
+ query = async (datasetId: string) => {
40
40
  const list = await this.db.query.evalDatasetRecords.findMany({
41
41
  where: and(
42
42
  eq(evalDatasetRecords.datasetId, datasetId),
@@ -60,7 +60,7 @@ export class EvalDatasetRecordModel {
60
60
  });
61
61
  };
62
62
 
63
- findByDatasetId = async (datasetId: number) => {
63
+ findByDatasetId = async (datasetId: string) => {
64
64
  return this.db.query.evalDatasetRecords.findMany({
65
65
  where: and(
66
66
  eq(evalDatasetRecords.datasetId, datasetId),
@@ -69,13 +69,13 @@ export class EvalDatasetRecordModel {
69
69
  });
70
70
  };
71
71
 
72
- findById = async (id: number) => {
72
+ findById = async (id: string) => {
73
73
  return this.db.query.evalDatasetRecords.findFirst({
74
74
  where: and(eq(evalDatasetRecords.id, id), eq(evalDatasetRecords.userId, this.userId)),
75
75
  });
76
76
  };
77
77
 
78
- update = async (id: number, value: Partial<NewEvalDatasetRecordsItem>) => {
78
+ update = async (id: string, value: Partial<NewEvalDatasetRecordsItem>) => {
79
79
  return this.db
80
80
  .update(evalDatasetRecords)
81
81
  .set(value)
@@ -26,7 +26,7 @@ export class EvalEvaluationModel {
26
26
  return result;
27
27
  };
28
28
 
29
- delete = async (id: number) => {
29
+ delete = async (id: string) => {
30
30
  return this.db
31
31
  .delete(evalEvaluation)
32
32
  .where(and(eq(evalEvaluation.id, id), eq(evalEvaluation.userId, this.userId)));
@@ -83,13 +83,13 @@ export class EvalEvaluationModel {
83
83
  });
84
84
  };
85
85
 
86
- findById = async (id: number) => {
86
+ findById = async (id: string) => {
87
87
  return this.db.query.evalEvaluation.findFirst({
88
88
  where: and(eq(evalEvaluation.id, id), eq(evalEvaluation.userId, this.userId)),
89
89
  });
90
90
  };
91
91
 
92
- update = async (id: number, value: Partial<NewEvalEvaluationItem>) => {
92
+ update = async (id: string, value: Partial<NewEvalEvaluationItem>) => {
93
93
  return this.db
94
94
  .update(evalEvaluation)
95
95
  .set(value)
@@ -1,7 +1,7 @@
1
1
  import { and, eq } from 'drizzle-orm';
2
2
 
3
- import { LobeChatDatabase } from '../../../type';
4
3
  import { NewEvaluationRecordsItem, evaluationRecords } from '../../../schemas';
4
+ import { LobeChatDatabase } from '../../../type';
5
5
 
6
6
  export class EvaluationRecordModel {
7
7
  private userId: string;
@@ -27,13 +27,13 @@ export class EvaluationRecordModel {
27
27
  .returning();
28
28
  };
29
29
 
30
- delete = async (id: number) => {
30
+ delete = async (id: string) => {
31
31
  return this.db
32
32
  .delete(evaluationRecords)
33
33
  .where(and(eq(evaluationRecords.id, id), eq(evaluationRecords.userId, this.userId)));
34
34
  };
35
35
 
36
- query = async (reportId: number) => {
36
+ query = async (reportId: string) => {
37
37
  return this.db.query.evaluationRecords.findMany({
38
38
  where: and(
39
39
  eq(evaluationRecords.evaluationId, reportId),
@@ -42,13 +42,13 @@ export class EvaluationRecordModel {
42
42
  });
43
43
  };
44
44
 
45
- findById = async (id: number) => {
45
+ findById = async (id: string) => {
46
46
  return this.db.query.evaluationRecords.findFirst({
47
47
  where: and(eq(evaluationRecords.id, id), eq(evaluationRecords.userId, this.userId)),
48
48
  });
49
49
  };
50
50
 
51
- findByEvaluationId = async (evaluationId: number) => {
51
+ findByEvaluationId = async (evaluationId: string) => {
52
52
  return this.db.query.evaluationRecords.findMany({
53
53
  where: and(
54
54
  eq(evaluationRecords.evaluationId, evaluationId),
@@ -57,7 +57,7 @@ export class EvaluationRecordModel {
57
57
  });
58
58
  };
59
59
 
60
- update = async (id: number, value: Partial<NewEvaluationRecordsItem>) => {
60
+ update = async (id: string, value: Partial<NewEvaluationRecordsItem>) => {
61
61
  return this.db
62
62
  .update(evaluationRecords)
63
63
  .set(value)
@@ -1,39 +1,39 @@
1
1
  export const activityPrompt = [
2
2
  'You are a focused memory extraction assistant specialized in the **activity** layer.',
3
3
  'When extracting, ensure all the content is using {{ language }}.',
4
- '',
4
+ '\n',
5
5
  '\\<user_context>',
6
6
  'Current user: {{ username }}',
7
7
  'Session date: {{ sessionDate }}',
8
8
  'Available memory categories: {{ availableCategories }}',
9
9
  'Target layer: activity',
10
10
  '\\</user_context>',
11
- '',
11
+ '\n',
12
12
  '## Retrieved Memory (Top {{ topK }})',
13
- '',
13
+ '\n',
14
14
  'Use the list below to de-duplicate and decide whether you need to extract anything new.',
15
15
  'Do not copy these verbatim; treat them as comparison references.',
16
- '',
16
+ '\n',
17
17
  '{{ retrievedContext }}',
18
- '',
18
+ '\n',
19
19
  '## Your Task',
20
- '',
20
+ '\n',
21
21
  'Extract **ALL** activity layer information from the conversation. Activities are time-bound events',
22
22
  'with people, places, status, and a factual narrative of what happened. Capture how it felt via',
23
23
  'feedback. Temporal (startsAt, endsAt, timezone) and association fields are OPTIONAL—only include',
24
24
  'them when explicitly provided. If missing, **omit the fields entirely** (do not set null/empty) instead of guessing.',
25
- '',
25
+ '\n',
26
26
  '**CRITICAL**: Return an **array** of activity memory items. Extract each distinct activity as a separate item.',
27
27
  'Before extracting, review the retrieved similar memories (top {{ topK }} items shown above). Extract items',
28
28
  'that are NEW or MATERIALLY UPDATED compared to retrieved entries. Avoid duplicates or near-duplicates.',
29
- '',
29
+ '\n',
30
30
  '## Name Handling and Neutrality',
31
31
  '- Always refer to the user with the exact placeholder token "User".',
32
32
  "- Do not infer, invent, or translate the user's real name.",
33
33
  '- Avoid gendered terms or honorifics; keep references neutral.',
34
- '',
34
+ '\n',
35
35
  '## Output Format',
36
- '',
36
+ '\n',
37
37
  'Return structured JSON data according to the provided schema:',
38
38
  '- Basic fields: title, summary, details, memoryLayer, memoryType, memoryCategory, tags',
39
39
  '- Activity-specific fields in withActivity: type (enum), status (planned/completed/cancelled/ongoing/on_hold/pending),',
@@ -42,26 +42,27 @@ export const activityPrompt = [
42
42
  ' associatedLocations (places; capture any mentioned venue/address), notes (prep/agenda), narrative (factual story),',
43
43
  ' feedback (how it felt), metadata (JSON), tags (activity facets).',
44
44
  '- Do not fabricate dates, timezones, or locations. Only include them when explicitly mentioned or derivable.',
45
- '',
45
+ '\n',
46
46
  '## Memory Formatting Guidelines',
47
- '',
47
+ '\n',
48
48
  '> ALL MEMORY ITEMS MUST BE SELF-CONTAINED',
49
+ '- Do not append date or time information to the `title`; keep timing in temporal fields or narrative instead.',
49
50
  '- Use concrete names/entities—avoid pronouns.',
50
51
  '- Preserve the original language from user input—do not translate.',
51
52
  '- Include relevant details, participants, places, timing if present, and outcomes.',
52
53
  '- Keep narratives factual; avoid speculative embellishment.',
53
- '',
54
+ '\n',
54
55
  '✓ **Good Examples:**',
55
56
  '- "User met with Alice at ACME HQ on 2024-05-03 14:00-15:00 America/New_York to review the Q2 renewal,',
56
57
  ' aligned on reduced scope, and agreed to send revised pricing; User felt the call was positive and collaborative."',
57
58
  '- "User visited Dr. Kim for a migraine check-up, discussed triggers and sleep, and started a low-dose preventive;',
58
59
  ' User felt reassured."',
59
- '',
60
+ '\n',
60
61
  '✗ **Bad Examples:**',
61
62
  '- "They had a meeting" → Missing who, when, where, and outcome.',
62
63
  '- "Felt good" → Missing context of the activity.',
63
64
  '- "Probably in New York" → Do not guess locations or timezones.',
64
- '',
65
+ '\n',
65
66
  '## Layer-Specific Guidance',
66
67
  '- Focus on discrete events (meetings, calls, trips, workouts, meals, appointments).',
67
68
  '- Always capture mentioned locations in associatedLocations. If none are stated, omit the field.',
@@ -73,15 +74,15 @@ export const activityPrompt = [
73
74
  '- Feedback captures how the activity felt (effort, mood, satisfaction); narrative captures the factual sequence.',
74
75
  '- Location and temporal fields are optional—only include when stated.',
75
76
  '- Temporal anchoring: when the conversation uses relative time phrases (yesterday, tomorrow, last week), anchor calculations to the specific message timestamp (created_at) if present; otherwise fall back to sessionDate. Keep the anchor timezone/time-of-day when converting relative references and do not invent new times if none are given.',
76
- '',
77
+ '\n',
77
78
  '## Few-shot Hints',
78
79
  '- Meeting with known person, time stated → include type=meeting, startsAt/endsAt/timezone, associatedSubjects for participants, location name if given.',
79
80
  '- Workout described without time/place → type=exercise, omit time/timezone/location, keep narrative/feedback.',
80
81
  '- Dinner with friends mentioned without exact time → type=meal or social, include attendees if named, omit time/place if absent.',
81
- '',
82
+ '\n',
82
83
  '## Security Considerations',
83
84
  '**NEVER extract or store sensitive information:** passwords, API keys, financial account numbers, or private encryption keys.',
84
- '',
85
+ '\n',
85
86
  '---',
86
87
  'Final instructions:',
87
88
  '1. Extract each distinct activity memory as a separate item.',