@lobehub/lobehub 2.0.0-next.376 → 2.0.0-next.378

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 (62) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/changelog/v1.json +21 -0
  3. package/docs/development/database-schema.dbml +51 -0
  4. package/docs/self-hosting/advanced/auth/providers/casdoor.mdx +1 -1
  5. package/docs/self-hosting/advanced/auth/providers/casdoor.zh-CN.mdx +1 -1
  6. package/docs/self-hosting/advanced/auth/providers/logto.mdx +1 -1
  7. package/docs/self-hosting/advanced/auth/providers/logto.zh-CN.mdx +1 -1
  8. package/package.json +1 -2
  9. package/packages/database/migrations/0075_add_user_memory_persona.sql +51 -0
  10. package/packages/database/migrations/meta/0075_snapshot.json +11957 -0
  11. package/packages/database/migrations/meta/_journal.json +8 -1
  12. package/packages/database/src/schemas/userMemories/persona.ts +81 -0
  13. package/scripts/_shared/checkDeprecatedAuth.js +46 -16
  14. package/scripts/_shared/checkDeprecatedAuth.test.ts +180 -0
  15. package/src/app/(backend)/api/webhooks/casdoor/route.ts +1 -2
  16. package/src/app/(backend)/api/webhooks/logto/route.ts +2 -3
  17. package/src/app/(backend)/trpc/async/[trpc]/route.ts +1 -2
  18. package/src/app/(backend)/trpc/mobile/[trpc]/route.ts +1 -2
  19. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +1 -0
  20. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +9 -0
  21. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +27 -2
  22. package/src/app/[variants]/(main)/home/_layout/Body/Agent/Actions.tsx +1 -1
  23. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +40 -32
  24. package/src/app/[variants]/(main)/memory/(home)/index.tsx +1 -1
  25. package/src/app/[variants]/(main)/memory/contexts/index.tsx +2 -0
  26. package/src/app/[variants]/(main)/memory/experiences/index.tsx +2 -0
  27. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/Action.tsx +13 -2
  28. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/AnalysisTrigger.tsx +26 -13
  29. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/index.tsx +10 -1
  30. package/src/app/[variants]/(main)/memory/identities/index.tsx +2 -0
  31. package/src/app/[variants]/(main)/memory/preferences/index.tsx +2 -0
  32. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +7 -3
  33. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +30 -30
  34. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +31 -31
  35. package/src/app/[variants]/(main)/settings/skill/index.tsx +2 -2
  36. package/src/components/FileParsingStatus/EmbeddingStatus.tsx +3 -16
  37. package/src/components/FileParsingStatus/index.tsx +2 -15
  38. package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +1 -3
  39. package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +4 -0
  40. package/src/features/ChatInput/ActionBar/Tools/index.tsx +1 -10
  41. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +41 -16
  42. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +2 -1
  43. package/src/features/Conversation/ChatItem/components/Title.tsx +6 -2
  44. package/src/features/ModelSelect/index.tsx +10 -3
  45. package/src/features/ProfileEditor/AgentTool.tsx +52 -33
  46. package/src/features/ProfileEditor/PopoverContent.tsx +28 -61
  47. package/src/features/SharePopover/index.tsx +3 -3
  48. package/src/features/SkillStore/CommunityList/Item.tsx +2 -1
  49. package/src/features/SkillStore/CommunityList/index.tsx +16 -22
  50. package/src/features/SkillStore/CustomList/Item.tsx +2 -1
  51. package/src/features/SkillStore/CustomList/index.tsx +11 -31
  52. package/src/features/SkillStore/LobeHubList/Item.tsx +4 -3
  53. package/src/features/SkillStore/LobeHubList/index.tsx +2 -18
  54. package/src/features/SkillStore/Search/index.tsx +1 -1
  55. package/src/features/SkillStore/index.tsx +6 -3
  56. package/src/features/SkillStore/style.ts +34 -1
  57. package/src/libs/next/config/define-config.ts +0 -3
  58. package/src/server/routers/lambda/agent.ts +1 -2
  59. package/src/server/services/user/index.ts +1 -2
  60. package/src/server/services/webhookUser/index.test.ts +290 -0
  61. package/src/server/services/webhookUser/index.ts +29 -12
  62. package/src/libs/logger/index.ts +0 -5
@@ -525,7 +525,14 @@
525
525
  "when": 1769341100106,
526
526
  "tag": "0074_add_fk_indexes_for_cascade_delete",
527
527
  "breakpoints": true
528
+ },
529
+ {
530
+ "idx": 75,
531
+ "version": "7",
532
+ "when": 1769362978088,
533
+ "tag": "0075_add_user_memory_persona",
534
+ "breakpoints": true
528
535
  }
529
536
  ],
530
537
  "version": "6"
531
- }
538
+ }
@@ -0,0 +1,81 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import { index, integer, jsonb, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core';
3
+
4
+ import { createNanoId } from '../../utils/idGenerator';
5
+ import { timestamps, timestamptz, varchar255 } from '../_helpers';
6
+ import { users } from '../user';
7
+
8
+ export const userPersonaDocuments = pgTable(
9
+ 'user_memory_persona_documents',
10
+ {
11
+ id: varchar255('id')
12
+ .$defaultFn(() => createNanoId(18)())
13
+ .primaryKey(),
14
+
15
+ userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
16
+ profile: varchar255('profile').default('default').notNull(),
17
+
18
+ tagline: text('tagline'),
19
+ persona: text('persona'),
20
+
21
+ memoryIds: jsonb('memory_ids').$type<string[]>(),
22
+ sourceIds: jsonb('source_ids').$type<string[]>(),
23
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
24
+
25
+ version: integer('version').notNull().default(1),
26
+ capturedAt: timestamptz('captured_at').notNull().defaultNow(),
27
+
28
+ ...timestamps,
29
+ },
30
+ (table) => [
31
+ uniqueIndex('user_persona_documents_user_id_profile_unique').on(table.userId, table.profile),
32
+ index('user_persona_documents_user_id_index').on(table.userId),
33
+ ],
34
+ );
35
+
36
+ export const userPersonaDocumentHistories = pgTable(
37
+ 'user_memory_persona_document_histories',
38
+ {
39
+ id: varchar255('id')
40
+ .$defaultFn(() => createNanoId(18)())
41
+ .primaryKey(),
42
+
43
+ userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
44
+ personaId: varchar255('persona_id').references(() => userPersonaDocuments.id, {
45
+ onDelete: 'cascade',
46
+ }),
47
+ profile: varchar255('profile').default('default').notNull(),
48
+
49
+ snapshotPersona: text('snapshot_persona'),
50
+ snapshotTagline: text('snapshot_tagline'),
51
+ reasoning: text('reasoning'),
52
+ diffPersona: text('diff_persona'),
53
+ diffTagline: text('diff_tagline'),
54
+ snapshot: text('snapshot'),
55
+ summary: text('summary'),
56
+ editedBy: varchar255('edited_by').default('agent'),
57
+
58
+ memoryIds: jsonb('memory_ids').$type<string[]>(),
59
+ sourceIds: jsonb('source_ids').$type<string[]>(),
60
+ metadata: jsonb('metadata').$type<Record<string, unknown>>(),
61
+
62
+ previousVersion: integer('previous_version'),
63
+ nextVersion: integer('next_version'),
64
+
65
+ capturedAt: timestamptz('captured_at').notNull().defaultNow(),
66
+
67
+ ...timestamps,
68
+ },
69
+ (table) => [
70
+ index('user_persona_document_histories_persona_id_index').on(table.personaId),
71
+ index('user_persona_document_histories_user_id_index').on(table.userId),
72
+ index('user_persona_document_histories_profile_index').on(table.profile),
73
+ ],
74
+ );
75
+
76
+ export type UserPersonaDocument = typeof userPersonaDocuments.$inferSelect;
77
+ export type NewUserPersonaDocument = typeof userPersonaDocuments.$inferInsert;
78
+
79
+ export type UserPersonaDocumentHistoriesItem = typeof userPersonaDocumentHistories.$inferSelect;
80
+ export type NewUserPersonaDocumentHistoriesItem =
81
+ typeof userPersonaDocumentHistories.$inferInsert;
@@ -15,6 +15,7 @@ const MIGRATION_DOC_BASE = 'https://lobehub.com/docs/self-hosting/advanced/auth'
15
15
  * message: string;
16
16
  * docUrl?: string;
17
17
  * formatVar?: (envVar: string) => string;
18
+ * severity?: 'error' | 'warning';
18
19
  * }>}
19
20
  */
20
21
  const DEPRECATED_CHECKS = [
@@ -144,8 +145,9 @@ const DEPRECATED_CHECKS = [
144
145
  return [];
145
146
  },
146
147
  message:
147
- 'Casdoor webhook is required for syncing user data (email, avatar, etc.) to LobeChat. Without it, users without email configured in Casdoor cannot login. Please configure CASDOOR_WEBHOOK_SECRET following the documentation.',
148
+ 'Casdoor webhook is recommended for syncing user data (email, avatar, etc.) to LobeChat. This is especially important for users migrating from NextAuth to Better Auth - users without email configured in Casdoor will not be able to login. Consider configuring CASDOOR_WEBHOOK_SECRET following the documentation.',
148
149
  name: 'Casdoor Webhook',
150
+ severity: 'warning',
149
151
  },
150
152
  {
151
153
  docUrl: `${MIGRATION_DOC_BASE}/providers/logto`,
@@ -157,8 +159,9 @@ const DEPRECATED_CHECKS = [
157
159
  return [];
158
160
  },
159
161
  message:
160
- 'Logto webhook is required for syncing user data (email, avatar, etc.) to LobeChat. Without it, users without email configured in Logto cannot login. Please configure LOGTO_WEBHOOK_SIGNING_KEY following the documentation.',
162
+ 'Logto webhook is recommended for syncing user data (email, avatar, etc.) to LobeChat. This is especially important for users migrating from NextAuth to Better Auth - users without email configured in Logto will not be able to login. Consider configuring LOGTO_WEBHOOK_SIGNING_KEY following the documentation.',
161
163
  name: 'Logto Webhook',
164
+ severity: 'warning',
162
165
  },
163
166
  {
164
167
  docUrl: `${MIGRATION_DOC_BASE}/nextauth-to-betterauth`,
@@ -185,18 +188,22 @@ const DEPRECATED_CHECKS = [
185
188
  ];
186
189
 
187
190
  /**
188
- * Print a single deprecation error block
191
+ * Print a single deprecation block (error or warning)
189
192
  */
190
- function printErrorBlock(name, vars, message, docUrl, formatVar) {
191
- console.error(`\n❌ ${name}`);
192
- console.error(''.repeat(50));
193
- console.error('Detected deprecated environment variables:');
193
+ function printIssueBlock(name, vars, message, docUrl, formatVar, severity = 'error') {
194
+ const isWarning = severity === 'warning';
195
+ const icon = isWarning ? '⚠️' : '❌';
196
+ const log = isWarning ? console.warn : console.error;
197
+
198
+ log(`\n${icon} ${name}`);
199
+ log('─'.repeat(50));
200
+ log(isWarning ? 'Missing recommended environment variables:' : 'Detected deprecated environment variables:');
194
201
  for (const envVar of vars) {
195
- console.error(` • ${formatVar ? formatVar(envVar) : envVar}`);
202
+ log(` • ${formatVar ? formatVar(envVar) : envVar}`);
196
203
  }
197
- console.error(`\n${message}`);
204
+ log(`\n${message}`);
198
205
  if (docUrl) {
199
- console.error(`📖 Migration guide: ${docUrl}`);
206
+ log(`📖 Documentation: ${docUrl}`);
200
207
  }
201
208
  }
202
209
 
@@ -208,23 +215,46 @@ function printErrorBlock(name, vars, message, docUrl, formatVar) {
208
215
  function checkDeprecatedAuth(options = {}) {
209
216
  const { action = 'redeploy' } = options;
210
217
 
211
- const foundIssues = [];
218
+ const errors = [];
219
+ const warnings = [];
220
+
212
221
  for (const check of DEPRECATED_CHECKS) {
213
222
  const foundVars = check.getVars();
214
223
  if (foundVars.length > 0) {
215
- foundIssues.push({ ...check, foundVars });
224
+ const issue = { ...check, foundVars };
225
+ if (check.severity === 'warning') {
226
+ warnings.push(issue);
227
+ } else {
228
+ errors.push(issue);
229
+ }
230
+ }
231
+ }
232
+
233
+ // Print warnings (non-blocking)
234
+ if (warnings.length > 0) {
235
+ console.warn('\n' + '═'.repeat(70));
236
+ console.warn(`⚠️ WARNING: Found ${warnings.length} recommended configuration(s) missing`);
237
+ console.warn('═'.repeat(70));
238
+
239
+ for (const issue of warnings) {
240
+ printIssueBlock(issue.name, issue.foundVars, issue.message, issue.docUrl, issue.formatVar, 'warning');
216
241
  }
242
+
243
+ console.warn('\n' + '═'.repeat(70));
244
+ console.warn('These are recommendations. Your application will still run.');
245
+ console.warn('═'.repeat(70) + '\n');
217
246
  }
218
247
 
219
- if (foundIssues.length > 0) {
248
+ // Print errors and exit (blocking)
249
+ if (errors.length > 0) {
220
250
  console.error('\n' + '═'.repeat(70));
221
251
  console.error(
222
- `❌ ERROR: Found ${foundIssues.length} deprecated environment variable issue(s)!`,
252
+ `❌ ERROR: Found ${errors.length} deprecated environment variable issue(s)!`,
223
253
  );
224
254
  console.error('═'.repeat(70));
225
255
 
226
- for (const issue of foundIssues) {
227
- printErrorBlock(issue.name, issue.foundVars, issue.message, issue.docUrl, issue.formatVar);
256
+ for (const issue of errors) {
257
+ printIssueBlock(issue.name, issue.foundVars, issue.message, issue.docUrl, issue.formatVar, 'error');
228
258
  }
229
259
 
230
260
  console.error('\n' + '═'.repeat(70));
@@ -0,0 +1,180 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ // Store original env
4
+ const originalEnv = { ...process.env };
5
+
6
+ // Mock process.exit to prevent actual exit
7
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
8
+
9
+ // Mock console methods
10
+ const mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
11
+ const mockConsoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {});
12
+
13
+ describe('checkDeprecatedAuth', () => {
14
+ beforeEach(() => {
15
+ // Reset env before each test
16
+ process.env = { ...originalEnv };
17
+ vi.clearAllMocks();
18
+ // Clear module cache to ensure fresh import
19
+ vi.resetModules();
20
+ });
21
+
22
+ afterEach(() => {
23
+ // Restore original env
24
+ process.env = originalEnv;
25
+ });
26
+
27
+ it('should not exit when no deprecated env vars are set', async () => {
28
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
29
+ checkDeprecatedAuth();
30
+
31
+ expect(mockExit).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it('should exit with code 1 when NextAuth env vars are detected', async () => {
35
+ process.env.NEXT_AUTH_SECRET = 'test-secret';
36
+
37
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
38
+ checkDeprecatedAuth();
39
+
40
+ expect(mockExit).toHaveBeenCalledWith(1);
41
+ expect(mockConsoleError).toHaveBeenCalled();
42
+ });
43
+
44
+ it('should exit with code 1 when NEXTAUTH env vars are detected', async () => {
45
+ process.env.NEXTAUTH_SECRET = 'test-secret';
46
+
47
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
48
+ checkDeprecatedAuth();
49
+
50
+ expect(mockExit).toHaveBeenCalledWith(1);
51
+ });
52
+
53
+ it('should exit with code 1 when Clerk env vars are detected', async () => {
54
+ process.env.CLERK_SECRET_KEY = 'test-key';
55
+
56
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
57
+ checkDeprecatedAuth();
58
+
59
+ expect(mockExit).toHaveBeenCalledWith(1);
60
+ });
61
+
62
+ it('should exit with code 1 when ACCESS_CODE is set', async () => {
63
+ process.env.ACCESS_CODE = 'test-code';
64
+
65
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
66
+ checkDeprecatedAuth();
67
+
68
+ expect(mockExit).toHaveBeenCalledWith(1);
69
+ });
70
+
71
+ it('should exit with code 1 when APP_URL has trailing slash', async () => {
72
+ process.env.APP_URL = 'https://example.com/';
73
+
74
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
75
+ checkDeprecatedAuth();
76
+
77
+ expect(mockExit).toHaveBeenCalledWith(1);
78
+ });
79
+
80
+ it('should not exit when APP_URL has no trailing slash', async () => {
81
+ process.env.APP_URL = 'https://example.com';
82
+
83
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
84
+ checkDeprecatedAuth();
85
+
86
+ expect(mockExit).not.toHaveBeenCalled();
87
+ });
88
+
89
+ describe('webhook warnings (non-blocking)', () => {
90
+ it('should warn but not exit when Casdoor webhook is missing', async () => {
91
+ process.env.AUTH_SSO_PROVIDERS = 'casdoor';
92
+ // CASDOOR_WEBHOOK_SECRET is not set
93
+
94
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
95
+ checkDeprecatedAuth();
96
+
97
+ expect(mockExit).not.toHaveBeenCalled();
98
+ expect(mockConsoleWarn).toHaveBeenCalled();
99
+ });
100
+
101
+ it('should warn but not exit when Logto webhook is missing', async () => {
102
+ process.env.AUTH_SSO_PROVIDERS = 'logto';
103
+ // LOGTO_WEBHOOK_SIGNING_KEY is not set
104
+
105
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
106
+ checkDeprecatedAuth();
107
+
108
+ expect(mockExit).not.toHaveBeenCalled();
109
+ expect(mockConsoleWarn).toHaveBeenCalled();
110
+ });
111
+
112
+ it('should not warn when Casdoor webhook is configured', async () => {
113
+ process.env.AUTH_SSO_PROVIDERS = 'casdoor';
114
+ process.env.CASDOOR_WEBHOOK_SECRET = 'test-secret';
115
+
116
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
117
+ checkDeprecatedAuth();
118
+
119
+ expect(mockExit).not.toHaveBeenCalled();
120
+ expect(mockConsoleWarn).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it('should not warn when Logto webhook is configured', async () => {
124
+ process.env.AUTH_SSO_PROVIDERS = 'logto';
125
+ process.env.LOGTO_WEBHOOK_SIGNING_KEY = 'test-key';
126
+
127
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
128
+ checkDeprecatedAuth();
129
+
130
+ expect(mockExit).not.toHaveBeenCalled();
131
+ expect(mockConsoleWarn).not.toHaveBeenCalled();
132
+ });
133
+
134
+ it('should not warn when provider is not casdoor or logto', async () => {
135
+ process.env.AUTH_SSO_PROVIDERS = 'google';
136
+
137
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
138
+ checkDeprecatedAuth();
139
+
140
+ expect(mockExit).not.toHaveBeenCalled();
141
+ expect(mockConsoleWarn).not.toHaveBeenCalled();
142
+ });
143
+ });
144
+
145
+ describe('mixed errors and warnings', () => {
146
+ it('should exit when there are errors even if there are also warnings', async () => {
147
+ process.env.AUTH_SSO_PROVIDERS = 'logto'; // warning
148
+ process.env.ACCESS_CODE = 'test-code'; // error
149
+
150
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
151
+ checkDeprecatedAuth();
152
+
153
+ expect(mockExit).toHaveBeenCalledWith(1);
154
+ expect(mockConsoleWarn).toHaveBeenCalled();
155
+ expect(mockConsoleError).toHaveBeenCalled();
156
+ });
157
+ });
158
+
159
+ describe('action parameter', () => {
160
+ it('should use "redeploy" as default action', async () => {
161
+ process.env.ACCESS_CODE = 'test-code';
162
+
163
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
164
+ checkDeprecatedAuth();
165
+
166
+ const calls = mockConsoleError.mock.calls.flat().join(' ');
167
+ expect(calls).toContain('redeploy');
168
+ });
169
+
170
+ it('should use custom action when provided', async () => {
171
+ process.env.ACCESS_CODE = 'test-code';
172
+
173
+ const { checkDeprecatedAuth } = await import('./checkDeprecatedAuth.js');
174
+ checkDeprecatedAuth({ action: 'restart' });
175
+
176
+ const calls = mockConsoleError.mock.calls.flat().join(' ');
177
+ expect(calls).toContain('restart');
178
+ });
179
+ });
180
+ });
@@ -2,7 +2,6 @@ import { NextResponse } from 'next/server';
2
2
 
3
3
  import { serverDB } from '@/database/server';
4
4
  import { authEnv } from '@/envs/auth';
5
- import { pino } from '@/libs/logger';
6
5
  import { WebhookUserService } from '@/server/services/webhookUser';
7
6
 
8
7
  import { validateRequest } from './validateRequest';
@@ -36,7 +35,7 @@ export const POST = async (req: Request): Promise<NextResponse> => {
36
35
  }
37
36
 
38
37
  default: {
39
- pino.warn(
38
+ console.warn(
40
39
  `${req.url} received event type "${action}", but no handler is defined for this type`,
41
40
  );
42
41
  return NextResponse.json({ error: `unrecognised payload type: ${action}` }, { status: 400 });
@@ -2,7 +2,6 @@ import { NextResponse } from 'next/server';
2
2
 
3
3
  import { serverDB } from '@/database/server';
4
4
  import { authEnv } from '@/envs/auth';
5
- import { pino } from '@/libs/logger';
6
5
  import { WebhookUserService } from '@/server/services/webhookUser';
7
6
 
8
7
  import { validateRequest } from './validateRequest';
@@ -19,7 +18,7 @@ export const POST = async (req: Request): Promise<NextResponse> => {
19
18
 
20
19
  const { event, data } = payload;
21
20
 
22
- pino.trace(`logto webhook payload: ${{ data, event }}`);
21
+ console.log(`logto webhook payload: ${{ data, event }}`);
23
22
 
24
23
  const webhookUserService = new WebhookUserService(serverDB);
25
24
  switch (event) {
@@ -47,7 +46,7 @@ export const POST = async (req: Request): Promise<NextResponse> => {
47
46
  }
48
47
 
49
48
  default: {
50
- pino.warn(
49
+ console.warn(
51
50
  `${req.url} received event type "${event}", but no handler is defined for this type`,
52
51
  );
53
52
  return NextResponse.json({ error: `unrecognised payload type: ${event}` }, { status: 400 });
@@ -1,7 +1,6 @@
1
1
  import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
- import { pino } from '@/libs/logger';
5
4
  import { createAsyncRouteContext } from '@/libs/trpc/async/context';
6
5
  import { prepareRequestForTRPC } from '@/libs/trpc/utils/request-adapter';
7
6
  import { createResponseMeta } from '@/libs/trpc/utils/responseMeta';
@@ -25,7 +24,7 @@ const handler = (req: NextRequest) => {
25
24
  endpoint: '/trpc/async',
26
25
 
27
26
  onError: ({ error, path, type }) => {
28
- pino.info(`Error in tRPC handler (async) on path: ${path}, type: ${type}`);
27
+ console.log(`Error in tRPC handler (async) on path: ${path}, type: ${type}`);
29
28
  console.error(error);
30
29
  },
31
30
 
@@ -1,7 +1,6 @@
1
1
  import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
- import { pino } from '@/libs/logger';
5
4
  import { createLambdaContext } from '@/libs/trpc/lambda/context';
6
5
  import { prepareRequestForTRPC } from '@/libs/trpc/utils/request-adapter';
7
6
  import { createResponseMeta } from '@/libs/trpc/utils/responseMeta';
@@ -21,7 +20,7 @@ const handler = (req: NextRequest) => {
21
20
  endpoint: '/trpc/mobile',
22
21
 
23
22
  onError: ({ error, path, type }) => {
24
- pino.info(`Error in tRPC handler (mobile) on path: ${path}, type: ${type}`);
23
+ console.log(`Error in tRPC handler (mobile) on path: ${path}, type: ${type}`);
25
24
  console.error(error);
26
25
  },
27
26
 
@@ -58,6 +58,7 @@ const ProfileEditor = memo(() => {
58
58
  <ModelSelect
59
59
  initialWidth
60
60
  onChange={updateConfig}
61
+ popupWidth={400}
61
62
  value={{
62
63
  model: config.model,
63
64
  provider: config.provider,
@@ -65,6 +65,13 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
65
65
  color: ${cssVar.colorTextTertiary};
66
66
  text-transform: uppercase;
67
67
  `,
68
+ trigger: css`
69
+ border-radius: ${cssVar.borderRadius};
70
+
71
+ &[data-popup-open] {
72
+ background: ${cssVar.colorFillTertiary};
73
+ }
74
+ `,
68
75
  }));
69
76
 
70
77
  interface AgentProfilePopupProps extends PropsWithChildren {
@@ -169,7 +176,9 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
169
176
 
170
177
  return (
171
178
  <Popover
179
+ classNames={{ trigger: styles.trigger }}
172
180
  content={content}
181
+ nativeButton={false}
173
182
  onOpenChange={setOpen}
174
183
  open={open}
175
184
  placement="right"
@@ -1,9 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { ActionIcon, Flexbox } from '@lobehub/ui';
4
+ import { createStaticStyles } from 'antd-style';
4
5
  import { UserMinus } from 'lucide-react';
5
- import { memo, useState } from 'react';
6
+ import { memo, useMemo, useState } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
8
+ import { useLocation } from 'react-router-dom';
7
9
 
8
10
  import { DEFAULT_AVATAR } from '@/const/meta';
9
11
  import NavItem from '@/features/NavPanel/components/NavItem';
@@ -20,6 +22,18 @@ import AddGroupMemberModal from '../AddGroupMemberModal';
20
22
  import AgentProfilePopup from './AgentProfilePopup';
21
23
  import GroupMemberItem from './GroupMemberItem';
22
24
 
25
+ const styles = createStaticStyles(({ css, cssVar }) => ({
26
+ memberTrigger: css`
27
+ border-radius: ${cssVar.borderRadius};
28
+ transition: background 0.2s ${cssVar.motionEaseOut};
29
+
30
+ &[data-popup-open],
31
+ &[data-active='true'] {
32
+ background: ${cssVar.colorFillTertiary};
33
+ }
34
+ `,
35
+ }));
36
+
23
37
  interface GroupMemberProps {
24
38
  addModalOpen: boolean;
25
39
  groupId?: string;
@@ -32,6 +46,7 @@ interface GroupMemberProps {
32
46
  const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange, groupId }) => {
33
47
  const { t } = useTranslation('chat');
34
48
  const router = useQueryRoute();
49
+ const location = useLocation();
35
50
  const [nickname, username] = useUserStore((s) => [
36
51
  userProfileSelectors.nickName(s),
37
52
  userProfileSelectors.username(s),
@@ -43,6 +58,12 @@ const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange
43
58
 
44
59
  const groupMembers = useAgentGroupStore(agentGroupSelectors.getGroupMembers(groupId || ''));
45
60
 
61
+ const activeTab = useMemo(() => new URLSearchParams(location.search).get('tab'), [location.search]);
62
+ const isProfileRoute = useMemo(() => {
63
+ if (!groupId) return false;
64
+ return location.pathname === `/group/${groupId}/profile`;
65
+ }, [groupId, location.pathname]);
66
+
46
67
  const handleAddMembers = async (selectedAgents: string[]) => {
47
68
  if (!groupId) {
48
69
  console.error('No active group to add members to');
@@ -96,7 +117,11 @@ const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange
96
117
  key={item.id}
97
118
  onChat={() => handleMemberClick(item.id)}
98
119
  >
99
- <div onDoubleClick={() => handleMemberDoubleClick(item.id)}>
120
+ <div
121
+ className={styles.memberTrigger}
122
+ data-active={isProfileRoute && activeTab === item.id ? 'true' : undefined}
123
+ onDoubleClick={() => handleMemberDoubleClick(item.id)}
124
+ >
100
125
  <GroupMemberItem
101
126
  actions={
102
127
  <ActionIcon
@@ -9,7 +9,7 @@ interface ActionsProps {
9
9
 
10
10
  const Actions = memo<ActionsProps>(({ dropdownMenu, isLoading }) => {
11
11
  return (
12
- <DropdownMenu items={dropdownMenu}>
12
+ <DropdownMenu items={dropdownMenu} nativeButton={false}>
13
13
  <ActionIcon
14
14
  icon={MoreHorizontalIcon}
15
15
  loading={isLoading}