@lobehub/lobehub 2.0.0-next.264 → 2.0.0-next.266

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 (157) hide show
  1. package/.github/workflows/manual-build-desktop.yml +16 -37
  2. package/CHANGELOG.md +52 -0
  3. package/apps/desktop/native-deps.config.mjs +19 -3
  4. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +13 -0
  5. package/apps/desktop/src/main/utils/permissions.ts +86 -22
  6. package/changelog/v1.json +18 -0
  7. package/locales/ar/chat.json +1 -0
  8. package/locales/ar/modelProvider.json +20 -0
  9. package/locales/ar/models.json +33 -10
  10. package/locales/ar/plugin.json +1 -0
  11. package/locales/ar/providers.json +1 -0
  12. package/locales/ar/setting.json +2 -0
  13. package/locales/bg-BG/chat.json +1 -0
  14. package/locales/bg-BG/modelProvider.json +20 -0
  15. package/locales/bg-BG/models.json +27 -7
  16. package/locales/bg-BG/plugin.json +1 -0
  17. package/locales/bg-BG/providers.json +1 -0
  18. package/locales/bg-BG/setting.json +2 -0
  19. package/locales/de-DE/chat.json +1 -0
  20. package/locales/de-DE/modelProvider.json +20 -0
  21. package/locales/de-DE/models.json +44 -10
  22. package/locales/de-DE/plugin.json +1 -0
  23. package/locales/de-DE/providers.json +1 -0
  24. package/locales/de-DE/setting.json +2 -0
  25. package/locales/en-US/chat.json +1 -0
  26. package/locales/en-US/modelProvider.json +20 -0
  27. package/locales/en-US/models.json +10 -10
  28. package/locales/en-US/providers.json +1 -0
  29. package/locales/en-US/setting.json +2 -1
  30. package/locales/es-ES/chat.json +1 -0
  31. package/locales/es-ES/modelProvider.json +20 -0
  32. package/locales/es-ES/models.json +53 -10
  33. package/locales/es-ES/plugin.json +1 -0
  34. package/locales/es-ES/providers.json +1 -0
  35. package/locales/es-ES/setting.json +2 -0
  36. package/locales/fa-IR/chat.json +1 -0
  37. package/locales/fa-IR/modelProvider.json +20 -0
  38. package/locales/fa-IR/models.json +33 -10
  39. package/locales/fa-IR/plugin.json +1 -0
  40. package/locales/fa-IR/providers.json +1 -0
  41. package/locales/fa-IR/setting.json +2 -0
  42. package/locales/fr-FR/chat.json +1 -0
  43. package/locales/fr-FR/modelProvider.json +20 -0
  44. package/locales/fr-FR/models.json +27 -7
  45. package/locales/fr-FR/plugin.json +1 -0
  46. package/locales/fr-FR/providers.json +1 -0
  47. package/locales/fr-FR/setting.json +2 -0
  48. package/locales/it-IT/chat.json +1 -0
  49. package/locales/it-IT/modelProvider.json +20 -0
  50. package/locales/it-IT/models.json +10 -10
  51. package/locales/it-IT/plugin.json +1 -0
  52. package/locales/it-IT/providers.json +1 -0
  53. package/locales/it-IT/setting.json +2 -0
  54. package/locales/ja-JP/chat.json +1 -0
  55. package/locales/ja-JP/modelProvider.json +20 -0
  56. package/locales/ja-JP/models.json +5 -10
  57. package/locales/ja-JP/plugin.json +1 -0
  58. package/locales/ja-JP/providers.json +1 -0
  59. package/locales/ja-JP/setting.json +2 -0
  60. package/locales/ko-KR/chat.json +1 -0
  61. package/locales/ko-KR/modelProvider.json +20 -0
  62. package/locales/ko-KR/models.json +36 -10
  63. package/locales/ko-KR/plugin.json +1 -0
  64. package/locales/ko-KR/providers.json +1 -0
  65. package/locales/ko-KR/setting.json +2 -0
  66. package/locales/nl-NL/chat.json +1 -0
  67. package/locales/nl-NL/modelProvider.json +20 -0
  68. package/locales/nl-NL/models.json +35 -4
  69. package/locales/nl-NL/plugin.json +1 -0
  70. package/locales/nl-NL/providers.json +1 -0
  71. package/locales/nl-NL/setting.json +2 -0
  72. package/locales/pl-PL/chat.json +1 -0
  73. package/locales/pl-PL/modelProvider.json +20 -0
  74. package/locales/pl-PL/models.json +37 -7
  75. package/locales/pl-PL/plugin.json +1 -0
  76. package/locales/pl-PL/providers.json +1 -0
  77. package/locales/pl-PL/setting.json +2 -0
  78. package/locales/pt-BR/chat.json +1 -0
  79. package/locales/pt-BR/modelProvider.json +20 -0
  80. package/locales/pt-BR/models.json +51 -9
  81. package/locales/pt-BR/plugin.json +1 -0
  82. package/locales/pt-BR/providers.json +1 -0
  83. package/locales/pt-BR/setting.json +2 -0
  84. package/locales/ru-RU/chat.json +1 -0
  85. package/locales/ru-RU/modelProvider.json +20 -0
  86. package/locales/ru-RU/models.json +48 -7
  87. package/locales/ru-RU/plugin.json +1 -0
  88. package/locales/ru-RU/providers.json +1 -0
  89. package/locales/ru-RU/setting.json +2 -0
  90. package/locales/tr-TR/chat.json +1 -0
  91. package/locales/tr-TR/modelProvider.json +20 -0
  92. package/locales/tr-TR/models.json +48 -7
  93. package/locales/tr-TR/plugin.json +1 -0
  94. package/locales/tr-TR/providers.json +1 -0
  95. package/locales/tr-TR/setting.json +2 -0
  96. package/locales/vi-VN/chat.json +1 -0
  97. package/locales/vi-VN/modelProvider.json +20 -0
  98. package/locales/vi-VN/models.json +5 -5
  99. package/locales/vi-VN/plugin.json +1 -0
  100. package/locales/vi-VN/providers.json +1 -0
  101. package/locales/vi-VN/setting.json +2 -0
  102. package/locales/zh-CN/modelProvider.json +20 -20
  103. package/locales/zh-CN/models.json +49 -8
  104. package/locales/zh-CN/providers.json +1 -0
  105. package/locales/zh-CN/setting.json +2 -1
  106. package/locales/zh-TW/chat.json +1 -0
  107. package/locales/zh-TW/modelProvider.json +20 -0
  108. package/locales/zh-TW/models.json +29 -10
  109. package/locales/zh-TW/plugin.json +1 -0
  110. package/locales/zh-TW/providers.json +1 -0
  111. package/locales/zh-TW/setting.json +2 -0
  112. package/package.json +2 -2
  113. package/packages/database/src/models/__tests__/agent.test.ts +165 -4
  114. package/packages/database/src/models/agent.ts +46 -0
  115. package/packages/database/src/repositories/agentGroup/index.test.ts +498 -0
  116. package/packages/database/src/repositories/agentGroup/index.ts +150 -0
  117. package/packages/database/src/repositories/home/__tests__/index.test.ts +113 -1
  118. package/packages/database/src/repositories/home/index.ts +48 -67
  119. package/pnpm-workspace.yaml +1 -0
  120. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Body.tsx +1 -1
  121. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +84 -0
  122. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/CronTopicItem.tsx +1 -1
  123. package/src/app/[variants]/(main)/agent/_layout/Sidebar/{Topic/CronTopicList → Cron}/index.tsx +23 -33
  124. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/Editing.tsx +12 -49
  125. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/index.tsx +3 -1
  126. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/TopicListContent/ThreadList/ThreadItem/Editing.tsx +12 -40
  127. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +5 -1
  128. package/src/app/[variants]/(main)/agent/features/Conversation/MainChatInput/index.tsx +2 -2
  129. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobCards.tsx +1 -1
  130. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/CronJobForm.tsx +1 -1
  131. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/AvailableAgentList.tsx +0 -1
  132. package/src/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal/index.tsx +5 -1
  133. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +2 -6
  134. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/useDropdownMenu.tsx +100 -0
  135. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -4
  136. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/useDropdownMenu.tsx +149 -0
  137. package/src/app/[variants]/(main)/home/_layout/hooks/index.ts +0 -1
  138. package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +1 -1
  139. package/src/components/InlineRename/index.tsx +121 -0
  140. package/src/features/ChatInput/InputEditor/index.tsx +1 -0
  141. package/src/features/EditorCanvas/DiffAllToolbar.tsx +1 -1
  142. package/src/features/NavPanel/components/NavItem.tsx +1 -1
  143. package/src/locales/default/setting.ts +2 -0
  144. package/src/server/routers/lambda/agent.ts +15 -0
  145. package/src/server/routers/lambda/agentGroup.ts +16 -0
  146. package/src/services/agent.ts +11 -0
  147. package/src/services/chatGroup/index.ts +11 -0
  148. package/src/store/agent/slices/cron/action.ts +108 -0
  149. package/src/store/agent/slices/cron/index.ts +1 -0
  150. package/src/store/agent/store.ts +3 -0
  151. package/src/store/home/slices/sidebarUI/action.test.ts +23 -22
  152. package/src/store/home/slices/sidebarUI/action.ts +37 -9
  153. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +0 -74
  154. package/src/app/[variants]/(main)/group/features/ChangelogModal.tsx +0 -11
  155. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/Item/useDropdownMenu.tsx +0 -62
  156. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionItemMenuItems.tsx +0 -238
  157. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +0 -56
@@ -3,7 +3,9 @@ import { INBOX_SESSION_ID } from '@lobechat/const';
3
3
  import { eq } from 'drizzle-orm';
4
4
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
5
 
6
+ import { getTestDB } from '../../core/getTestDB';
6
7
  import {
8
+ NewAgent,
7
9
  agents,
8
10
  agentsFiles,
9
11
  agentsKnowledgeBases,
@@ -11,12 +13,12 @@ import {
11
13
  documents,
12
14
  files,
13
15
  knowledgeBases,
16
+ sessionGroups,
14
17
  sessions,
15
18
  users,
16
19
  } from '../../schemas';
17
20
  import { LobeChatDatabase } from '../../type';
18
21
  import { AgentModel } from '../agent';
19
- import { getTestDB } from '../../core/getTestDB';
20
22
 
21
23
  const serverDB: LobeChatDatabase = await getTestDB();
22
24
 
@@ -290,9 +292,7 @@ describe('AgentModel', () => {
290
292
  // Create agent and session for user2
291
293
  await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
292
294
  await serverDB.insert(sessions).values({ id: sessionId, userId: userId2 });
293
- await serverDB
294
- .insert(agentsToSessions)
295
- .values({ agentId, sessionId, userId: userId2 });
295
+ await serverDB.insert(agentsToSessions).values({ agentId, sessionId, userId: userId2 });
296
296
 
297
297
  // Try to access with user1's model
298
298
  const result = await agentModel.findBySessionId(sessionId);
@@ -1390,6 +1390,167 @@ describe('AgentModel', () => {
1390
1390
  });
1391
1391
  });
1392
1392
 
1393
+ describe('duplicate', () => {
1394
+ it('should duplicate an agent with all config fields', async () => {
1395
+ // Create source agent with full config
1396
+ const [sourceAgent] = await serverDB
1397
+ .insert(agents)
1398
+ .values({
1399
+ userId,
1400
+ title: 'Original Agent',
1401
+ description: 'Original description',
1402
+ tags: ['tag1', 'tag2'],
1403
+ avatar: 'avatar-url',
1404
+ backgroundColor: '#ffffff',
1405
+ plugins: ['plugin1'],
1406
+ model: 'gpt-4',
1407
+ provider: 'openai',
1408
+ systemRole: 'You are helpful',
1409
+ openingMessage: 'Hello!',
1410
+ openingQuestions: ['Q1', 'Q2'],
1411
+ chatConfig: { historyCount: 10 },
1412
+ fewShots: [{ role: 'user', content: 'test' }],
1413
+ params: { temperature: 0.7 },
1414
+ tts: { showAllLocaleVoice: true },
1415
+ } as NewAgent)
1416
+ .returning();
1417
+
1418
+ const result = await agentModel.duplicate(sourceAgent.id);
1419
+
1420
+ expect(result).toBeDefined();
1421
+ expect(result?.agentId).toBeDefined();
1422
+ expect(result?.agentId).not.toBe(sourceAgent.id);
1423
+
1424
+ // Verify the duplicated agent
1425
+ const duplicatedAgent = await serverDB.query.agents.findFirst({
1426
+ where: eq(agents.id, result!.agentId),
1427
+ });
1428
+
1429
+ expect(duplicatedAgent).toEqual(
1430
+ expect.objectContaining({
1431
+ // Should be copied
1432
+ title: 'Original Agent (Copy)',
1433
+ description: 'Original description',
1434
+ tags: ['tag1', 'tag2'],
1435
+ avatar: 'avatar-url',
1436
+ backgroundColor: '#ffffff',
1437
+ plugins: ['plugin1'],
1438
+ model: 'gpt-4',
1439
+ provider: 'openai',
1440
+ systemRole: 'You are helpful',
1441
+ openingMessage: 'Hello!',
1442
+ openingQuestions: ['Q1', 'Q2'],
1443
+ chatConfig: { historyCount: 10 },
1444
+ fewShots: [{ role: 'user', content: 'test' }],
1445
+ params: { temperature: 0.7 },
1446
+ tts: { showAllLocaleVoice: true },
1447
+ sessionGroupId: null,
1448
+ userId,
1449
+ // Should NOT be copied (new values)
1450
+ virtual: false,
1451
+ pinned: null,
1452
+ clientId: null,
1453
+ editorData: null,
1454
+ marketIdentifier: null,
1455
+ }),
1456
+ );
1457
+
1458
+ // Verify these are NOT copied from source
1459
+ expect(duplicatedAgent?.id).not.toBe(sourceAgent.id);
1460
+ expect(duplicatedAgent?.slug).not.toBe(sourceAgent.slug);
1461
+ });
1462
+
1463
+ it('should use provided title when duplicating', async () => {
1464
+ const [sourceAgent] = await serverDB
1465
+ .insert(agents)
1466
+ .values({ userId, title: 'Original' })
1467
+ .returning();
1468
+
1469
+ const result = await agentModel.duplicate(sourceAgent.id, 'Custom Title');
1470
+
1471
+ const duplicatedAgent = await serverDB.query.agents.findFirst({
1472
+ where: eq(agents.id, result!.agentId),
1473
+ });
1474
+
1475
+ expect(duplicatedAgent?.title).toBe('Custom Title');
1476
+ });
1477
+
1478
+ it('should return null for non-existent agent', async () => {
1479
+ const result = await agentModel.duplicate('non-existent-id');
1480
+
1481
+ expect(result).toBeNull();
1482
+ });
1483
+
1484
+ it('should not duplicate another user agent', async () => {
1485
+ const [sourceAgent] = await serverDB
1486
+ .insert(agents)
1487
+ .values({ userId: userId2, title: 'User2 Agent' })
1488
+ .returning();
1489
+
1490
+ const result = await agentModel.duplicate(sourceAgent.id);
1491
+
1492
+ expect(result).toBeNull();
1493
+ });
1494
+
1495
+ it('should not copy marketIdentifier, slug, or id', async () => {
1496
+ const [sourceAgent] = await serverDB
1497
+ .insert(agents)
1498
+ .values({
1499
+ userId,
1500
+ title: 'Original',
1501
+ slug: 'original-slug',
1502
+ marketIdentifier: 'market-123',
1503
+ })
1504
+ .returning();
1505
+
1506
+ const result = await agentModel.duplicate(sourceAgent.id);
1507
+
1508
+ const duplicatedAgent = await serverDB.query.agents.findFirst({
1509
+ where: eq(agents.id, result!.agentId),
1510
+ });
1511
+
1512
+ expect(duplicatedAgent?.id).not.toBe(sourceAgent.id);
1513
+ expect(duplicatedAgent?.slug).not.toBe('original-slug');
1514
+ expect(duplicatedAgent?.marketIdentifier).toBeNull();
1515
+ });
1516
+
1517
+ it('should preserve sessionGroupId when duplicating', async () => {
1518
+ // Create a session group
1519
+ const [sessionGroup] = await serverDB
1520
+ .insert(sessionGroups)
1521
+ .values({ userId, name: 'Test Group' })
1522
+ .returning();
1523
+
1524
+ const [sourceAgent] = await serverDB
1525
+ .insert(agents)
1526
+ .values({ userId, title: 'Agent in Group', sessionGroupId: sessionGroup.id })
1527
+ .returning();
1528
+
1529
+ const result = await agentModel.duplicate(sourceAgent.id);
1530
+
1531
+ const duplicatedAgent = await serverDB.query.agents.findFirst({
1532
+ where: eq(agents.id, result!.agentId),
1533
+ });
1534
+
1535
+ expect(duplicatedAgent?.sessionGroupId).toBe(sessionGroup.id);
1536
+ });
1537
+
1538
+ it('should handle agent with null title', async () => {
1539
+ const [sourceAgent] = await serverDB
1540
+ .insert(agents)
1541
+ .values({ userId, title: null })
1542
+ .returning();
1543
+
1544
+ const result = await agentModel.duplicate(sourceAgent.id);
1545
+
1546
+ const duplicatedAgent = await serverDB.query.agents.findFirst({
1547
+ where: eq(agents.id, result!.agentId),
1548
+ });
1549
+
1550
+ expect(duplicatedAgent?.title).toBe('Copy');
1551
+ });
1552
+ });
1553
+
1393
1554
  describe('queryAgents', () => {
1394
1555
  it('should return non-virtual agents for the user', async () => {
1395
1556
  // Create non-virtual agents
@@ -452,6 +452,52 @@ export class AgentModel {
452
452
  return result[0];
453
453
  };
454
454
 
455
+ /**
456
+ * Duplicate an agent.
457
+ * Returns the new agent ID.
458
+ */
459
+ duplicate = async (agentId: string, newTitle?: string): Promise<{ agentId: string } | null> => {
460
+ // Get the source agent
461
+ const sourceAgent = await this.db.query.agents.findFirst({
462
+ where: and(eq(agents.id, agentId), eq(agents.userId, this.userId)),
463
+ });
464
+
465
+ if (!sourceAgent) return null;
466
+
467
+ // Create new agent with explicit include fields
468
+ const [newAgent] = await this.db
469
+ .insert(agents)
470
+ .values({
471
+ avatar: sourceAgent.avatar,
472
+ backgroundColor: sourceAgent.backgroundColor,
473
+ chatConfig: sourceAgent.chatConfig,
474
+ description: sourceAgent.description,
475
+ fewShots: sourceAgent.fewShots,
476
+ model: sourceAgent.model,
477
+ openingMessage: sourceAgent.openingMessage,
478
+ openingQuestions: sourceAgent.openingQuestions,
479
+ params: sourceAgent.params,
480
+ pinned: sourceAgent.pinned,
481
+ // Config
482
+ plugins: sourceAgent.plugins,
483
+ provider: sourceAgent.provider,
484
+
485
+ // Session group
486
+ sessionGroupId: sourceAgent.sessionGroupId,
487
+ systemRole: sourceAgent.systemRole,
488
+
489
+ tags: sourceAgent.tags,
490
+ // Metadata
491
+ title: newTitle || (sourceAgent.title ? `${sourceAgent.title} (Copy)` : 'Copy'),
492
+ tts: sourceAgent.tts,
493
+ // User
494
+ userId: this.userId,
495
+ })
496
+ .returning();
497
+
498
+ return { agentId: newAgent.id };
499
+ };
500
+
455
501
  /**
456
502
  * Get a builtin agent by slug, creating it if it doesn't exist.
457
503
  * Builtin agents are standalone agents not bound to sessions.