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

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 (136) hide show
  1. package/.cursor/rules/microcopy-cn.mdc +75 -63
  2. package/.cursor/rules/microcopy-en.mdc +4 -8
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +8 -8
  5. package/README.zh-CN.md +8 -8
  6. package/apps/desktop/src/main/locales/default/common.ts +2 -2
  7. package/changelog/v1.json +10 -0
  8. package/docs/development/database-schema.dbml +4 -0
  9. package/e2e/CLAUDE.md +43 -81
  10. package/e2e/cucumber.config.js +1 -0
  11. package/e2e/docs/local-setup.md +67 -219
  12. package/e2e/scripts/setup.ts +529 -0
  13. package/e2e/src/features/home/sidebarAgent.feature +62 -0
  14. package/e2e/src/features/home/sidebarGroup.feature +62 -0
  15. package/e2e/src/features/page/README.md +118 -0
  16. package/e2e/src/features/page/crud.feature +62 -0
  17. package/e2e/src/features/page/editor-content.feature +93 -0
  18. package/e2e/src/features/page/editor-meta.feature +60 -0
  19. package/e2e/src/steps/agent/conversation.steps.ts +4 -4
  20. package/e2e/src/steps/home/sidebarAgent.steps.ts +370 -0
  21. package/e2e/src/steps/home/sidebarGroup.steps.ts +168 -0
  22. package/e2e/src/steps/hooks.ts +4 -0
  23. package/e2e/src/steps/page/editor-content.steps.ts +344 -0
  24. package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
  25. package/e2e/src/steps/page/page-crud.steps.ts +363 -0
  26. package/e2e/src/support/world.ts +12 -0
  27. package/locales/ar/file.json +2 -0
  28. package/locales/bg-BG/file.json +2 -0
  29. package/locales/de-DE/file.json +2 -0
  30. package/locales/en-US/auth.json +1 -1
  31. package/locales/en-US/file.json +2 -0
  32. package/locales/en-US/metadata.json +2 -2
  33. package/locales/es-ES/file.json +2 -0
  34. package/locales/fa-IR/file.json +2 -0
  35. package/locales/fr-FR/file.json +2 -0
  36. package/locales/it-IT/file.json +2 -0
  37. package/locales/ja-JP/file.json +2 -0
  38. package/locales/ko-KR/file.json +2 -0
  39. package/locales/nl-NL/file.json +2 -0
  40. package/locales/pl-PL/file.json +2 -0
  41. package/locales/pt-BR/file.json +2 -0
  42. package/locales/ru-RU/file.json +2 -0
  43. package/locales/tr-TR/file.json +2 -0
  44. package/locales/vi-VN/file.json +2 -0
  45. package/locales/zh-CN/file.json +2 -0
  46. package/locales/zh-TW/file.json +2 -0
  47. package/package.json +3 -3
  48. package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
  49. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
  50. package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
  51. package/packages/const/src/settings/group.ts +0 -10
  52. package/packages/database/migrations/0068_update_group_data.sql +4 -0
  53. package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
  54. package/packages/database/migrations/meta/_journal.json +7 -0
  55. package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
  56. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
  57. package/packages/database/src/models/knowledgeBase.ts +67 -3
  58. package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
  59. package/packages/database/src/repositories/agentGroup/index.ts +4 -9
  60. package/packages/database/src/repositories/knowledge/index.ts +3 -3
  61. package/packages/database/src/schemas/chatGroup.ts +4 -3
  62. package/packages/database/src/types/chatGroup.ts +0 -7
  63. package/packages/types/src/agentGroup/index.ts +30 -9
  64. package/packages/utils/src/multimodalContent.test.ts +302 -0
  65. package/packages/utils/src/server/__tests__/sse.test.ts +353 -0
  66. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/Editing.tsx +4 -11
  67. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentGroupItem/index.tsx +3 -3
  68. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
  69. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
  70. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
  71. package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
  72. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  73. package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
  74. package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
  75. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
  76. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
  77. package/src/components/DragUpload/index.tsx +24 -27
  78. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
  79. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +42 -7
  80. package/src/features/CommandMenu/useCommandMenu.ts +4 -14
  81. package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
  82. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
  83. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
  84. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
  85. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
  86. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
  87. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
  88. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
  89. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
  90. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
  91. package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
  92. package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
  93. package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
  94. package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
  95. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
  96. package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
  97. package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
  98. package/src/features/ResourceManager/index.tsx +3 -0
  99. package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
  100. package/src/locales/default/auth.ts +1 -1
  101. package/src/locales/default/file.ts +2 -0
  102. package/src/locales/default/metadata.ts +2 -2
  103. package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
  104. package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
  105. package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
  106. package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
  107. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
  108. package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
  109. package/src/server/modules/AgentRuntime/types.ts +21 -21
  110. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
  111. package/src/server/routers/lambda/agentGroup.ts +10 -12
  112. package/src/server/services/document/index.ts +1 -0
  113. package/src/store/agentGroup/slices/curd.test.ts +4 -4
  114. package/src/store/file/slices/fileManager/action.ts +12 -4
  115. package/src/store/home/slices/homeInput/action.ts +0 -3
  116. package/src/store/home/slices/sidebarUI/action.ts +9 -0
  117. package/src/store/session/slices/session/action.ts +5 -9
  118. package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
  119. package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
  120. package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
  121. package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
  122. package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
  123. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
  124. package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
  125. package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
  126. package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
  127. package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
  128. package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
  129. package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
  130. package/src/features/GroupChatSettings/index.ts +0 -16
  131. package/src/features/GroupChatSettings/store/action.ts +0 -105
  132. package/src/features/GroupChatSettings/store/index.ts +0 -18
  133. package/src/features/GroupChatSettings/store/initialState.ts +0 -23
  134. package/src/features/GroupChatSettings/store/selectors.ts +0 -13
  135. package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
  136. /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * E2E Test Environment Setup Script
4
+ *
5
+ * One-click setup for E2E testing environment.
6
+ *
7
+ * Usage:
8
+ * bun e2e/scripts/setup.ts [options]
9
+ *
10
+ * Options:
11
+ * --clean Clean up existing containers and processes
12
+ * --skip-db Skip database setup (use existing)
13
+ * --skip-migrate Skip database migration
14
+ * --build Build the application before starting
15
+ * --start Start the server after setup
16
+ * --port <port> Server port (default: 3006)
17
+ * --help Show help message
18
+ */
19
+
20
+ import { type ChildProcess, spawn, spawnSync } from 'node:child_process';
21
+ import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
22
+ import { resolve } from 'node:path';
23
+
24
+ // ============================================================================
25
+ // Configuration
26
+ // ============================================================================
27
+
28
+ const CONFIG = {
29
+ containerName: 'postgres-e2e',
30
+ databaseDriver: 'node',
31
+ databaseUrl: 'postgresql://postgres:postgres@localhost:5433/postgres',
32
+ dbPort: 5433,
33
+ defaultPort: 3006,
34
+ dockerImage: 'paradedb/paradedb:latest',
35
+ projectRoot: resolve(__dirname, '../..'),
36
+ serverTimeout: 120_000, // 2 minutes
37
+
38
+ // Secrets (for e2e testing only)
39
+ secrets: {
40
+ betterAuthSecret: 'e2e-test-secret-key-for-better-auth-32chars!',
41
+ keyVaultsSecret: 'LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=',
42
+ },
43
+
44
+ // S3 Mock (required even if not testing file uploads)
45
+ s3Mock: {
46
+ accessKeyId: 'e2e-mock-access-key',
47
+ bucket: 'e2e-mock-bucket',
48
+ endpoint: 'https://e2e-mock-s3.localhost',
49
+ secretAccessKey: 'e2e-mock-secret-key',
50
+ },
51
+ };
52
+
53
+ // ============================================================================
54
+ // Utilities
55
+ // ============================================================================
56
+
57
+ const colors = {
58
+ cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
59
+ dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
60
+ green: (s: string) => `\x1b[32m${s}\x1b[0m`,
61
+ red: (s: string) => `\x1b[31m${s}\x1b[0m`,
62
+ yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
63
+ };
64
+
65
+ function log(emoji: string, message: string) {
66
+ console.log(`${emoji} ${message}`);
67
+ }
68
+
69
+ function logStep(step: number, total: number, message: string) {
70
+ console.log(`\n${colors.cyan(`[${step}/${total}]`)} ${message}`);
71
+ }
72
+
73
+ function exec(
74
+ command: string,
75
+ args: string[] = [],
76
+ options: { cwd?: string; silent?: boolean } = {}
77
+ ) {
78
+ const { cwd = CONFIG.projectRoot, silent = false } = options;
79
+ const result = spawnSync(command, args, {
80
+ cwd,
81
+ encoding: 'utf-8',
82
+ shell: true,
83
+ stdio: silent ? 'pipe' : 'inherit',
84
+ });
85
+ return result;
86
+ }
87
+
88
+ function execAsync(
89
+ command: string,
90
+ args: string[] = [],
91
+ env: Record<string, string> = {}
92
+ ): Promise<void> {
93
+ return new Promise((resolve, reject) => {
94
+ const child = spawn(command, args, {
95
+ cwd: CONFIG.projectRoot,
96
+ env: { ...process.env, ...env },
97
+ shell: true,
98
+ stdio: 'inherit',
99
+ });
100
+
101
+ child.on('close', (code) => {
102
+ if (code === 0) {
103
+ resolve();
104
+ } else {
105
+ reject(new Error(`Command failed with code ${code}`));
106
+ }
107
+ });
108
+
109
+ child.on('error', reject);
110
+ });
111
+ }
112
+
113
+ async function sleep(ms: number): Promise<void> {
114
+ return new Promise((resolve) => setTimeout(resolve, ms));
115
+ }
116
+
117
+ async function waitForCondition(
118
+ check: () => Promise<boolean>,
119
+ timeout: number,
120
+ interval: number = 1000,
121
+ onWait?: () => void
122
+ ): Promise<boolean> {
123
+ const startTime = Date.now();
124
+ while (Date.now() - startTime < timeout) {
125
+ if (await check()) {
126
+ return true;
127
+ }
128
+ onWait?.();
129
+ await sleep(interval);
130
+ }
131
+ return false;
132
+ }
133
+
134
+ // ============================================================================
135
+ // Docker Operations
136
+ // ============================================================================
137
+
138
+ function isDockerRunning(): boolean {
139
+ const result = exec('docker', ['info'], { silent: true });
140
+ return result.status === 0;
141
+ }
142
+
143
+ function isContainerRunning(name: string): boolean {
144
+ const result = exec('docker', ['ps', '-q', '-f', `name=${name}`], { silent: true });
145
+ return !!result.stdout?.trim();
146
+ }
147
+
148
+ function containerExists(name: string): boolean {
149
+ const result = exec('docker', ['ps', '-aq', '-f', `name=${name}`], { silent: true });
150
+ return !!result.stdout?.trim();
151
+ }
152
+
153
+ function stopContainer(name: string): void {
154
+ if (isContainerRunning(name)) {
155
+ log('🛑', `Stopping container: ${name}`);
156
+ exec('docker', ['stop', name], { silent: true });
157
+ }
158
+ }
159
+
160
+ function removeContainer(name: string): void {
161
+ if (containerExists(name)) {
162
+ log('🗑️ ', `Removing container: ${name}`);
163
+ exec('docker', ['rm', name], { silent: true });
164
+ }
165
+ }
166
+
167
+ async function startPostgres(): Promise<void> {
168
+ // Check Docker is running
169
+ if (!isDockerRunning()) {
170
+ throw new Error('Docker is not running. Please start Docker Desktop first.');
171
+ }
172
+
173
+ if (isContainerRunning(CONFIG.containerName)) {
174
+ log('✅', 'PostgreSQL container is already running');
175
+ return;
176
+ }
177
+
178
+ // Remove existing container if exists
179
+ removeContainer(CONFIG.containerName);
180
+
181
+ log('🐘', 'Starting PostgreSQL container...');
182
+ const result = exec('docker', [
183
+ 'run',
184
+ '-d',
185
+ '--name',
186
+ CONFIG.containerName,
187
+ '-e',
188
+ 'POSTGRES_PASSWORD=postgres',
189
+ '-p',
190
+ `${CONFIG.dbPort}:5432`,
191
+ CONFIG.dockerImage,
192
+ ]);
193
+
194
+ if (result.status !== 0) {
195
+ throw new Error('Failed to start PostgreSQL container');
196
+ }
197
+
198
+ // Wait for database to be ready
199
+ process.stdout.write(' Waiting for PostgreSQL to be ready');
200
+ const isReady = await waitForCondition(
201
+ async () => {
202
+ const result = exec('docker', ['exec', CONFIG.containerName, 'pg_isready'], { silent: true });
203
+ return result.status === 0;
204
+ },
205
+ 30_000,
206
+ 2000,
207
+ () => process.stdout.write('.')
208
+ );
209
+
210
+ console.log();
211
+
212
+ if (!isReady) {
213
+ throw new Error('PostgreSQL failed to start within 30 seconds');
214
+ }
215
+
216
+ log('✅', 'PostgreSQL is ready');
217
+ }
218
+
219
+ // ============================================================================
220
+ // Process Management
221
+ // ============================================================================
222
+
223
+ function killProcessOnPort(port: number): void {
224
+ const result = exec('lsof', ['-ti', `:${port}`], { silent: true });
225
+ const pids = result.stdout?.trim();
226
+ if (pids) {
227
+ log('🔪', `Killing processes on port ${port}`);
228
+ for (const pid of pids.split('\n')) {
229
+ if (pid) {
230
+ exec('kill', ['-9', pid], { silent: true });
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ // ============================================================================
237
+ // Database Operations
238
+ // ============================================================================
239
+
240
+ async function runMigration(): Promise<void> {
241
+ log('🔄', 'Running database migration...');
242
+
243
+ await execAsync('bun', ['run', 'db:migrate'], {
244
+ DATABASE_DRIVER: CONFIG.databaseDriver,
245
+ DATABASE_URL: CONFIG.databaseUrl,
246
+ KEY_VAULTS_SECRET: CONFIG.secrets.keyVaultsSecret,
247
+ });
248
+
249
+ log('✅', 'Database migration completed');
250
+ }
251
+
252
+ // ============================================================================
253
+ // Build Operations
254
+ // ============================================================================
255
+
256
+ async function buildApp(): Promise<void> {
257
+ log('🔨', 'Building application (this may take a few minutes)...');
258
+
259
+ await execAsync('bun', ['run', 'build'], {
260
+ BETTER_AUTH_SECRET: CONFIG.secrets.betterAuthSecret,
261
+ DATABASE_DRIVER: CONFIG.databaseDriver,
262
+ DATABASE_URL: CONFIG.databaseUrl,
263
+ KEY_VAULTS_SECRET: CONFIG.secrets.keyVaultsSecret,
264
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH: '1',
265
+ SKIP_LINT: '1',
266
+ });
267
+
268
+ log('✅', 'Application built successfully');
269
+ }
270
+
271
+ // ============================================================================
272
+ // Server Operations
273
+ // ============================================================================
274
+
275
+ async function isServerRunning(port: number): Promise<boolean> {
276
+ try {
277
+ const response = await fetch(`http://localhost:${port}/chat`, { method: 'HEAD' });
278
+ return response.ok;
279
+ } catch {
280
+ return false;
281
+ }
282
+ }
283
+
284
+ function getServerEnv(port: number): Record<string, string> {
285
+ return {
286
+ BETTER_AUTH_SECRET: CONFIG.secrets.betterAuthSecret,
287
+ DATABASE_DRIVER: CONFIG.databaseDriver,
288
+ DATABASE_URL: CONFIG.databaseUrl,
289
+ KEY_VAULTS_SECRET: CONFIG.secrets.keyVaultsSecret,
290
+ NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION: '0',
291
+ NEXT_PUBLIC_ENABLE_BETTER_AUTH: '1',
292
+ NODE_OPTIONS: '--max-old-space-size=6144',
293
+ PORT: String(port),
294
+ S3_ACCESS_KEY_ID: CONFIG.s3Mock.accessKeyId,
295
+ S3_BUCKET: CONFIG.s3Mock.bucket,
296
+ S3_ENDPOINT: CONFIG.s3Mock.endpoint,
297
+ S3_SECRET_ACCESS_KEY: CONFIG.s3Mock.secretAccessKey,
298
+ };
299
+ }
300
+
301
+ async function startServer(port: number): Promise<void> {
302
+ if (await isServerRunning(port)) {
303
+ log('✅', `Server is already running on port ${port}`);
304
+ return;
305
+ }
306
+
307
+ // Kill any process on the port first
308
+ killProcessOnPort(port);
309
+
310
+ log('🚀', `Starting server on port ${port}...`);
311
+
312
+ const env = getServerEnv(port);
313
+
314
+ // Start server in background
315
+ const child = spawn('bunx', ['next', 'start', '-p', String(port)], {
316
+ cwd: CONFIG.projectRoot,
317
+ detached: true,
318
+ env: { ...process.env, ...env },
319
+ stdio: 'ignore',
320
+ });
321
+
322
+ child.unref();
323
+
324
+ // Wait for server to be ready
325
+ process.stdout.write(' Waiting for server to be ready');
326
+ const isReady = await waitForCondition(
327
+ () => isServerRunning(port),
328
+ CONFIG.serverTimeout,
329
+ 2000,
330
+ () => process.stdout.write('.')
331
+ );
332
+
333
+ console.log();
334
+
335
+ if (!isReady) {
336
+ throw new Error(`Server failed to start within ${CONFIG.serverTimeout / 1000} seconds`);
337
+ }
338
+
339
+ log('✅', `Server is ready at http://localhost:${port}`);
340
+ }
341
+
342
+ // ============================================================================
343
+ // Cleanup
344
+ // ============================================================================
345
+
346
+ function cleanup(): void {
347
+ log('🧹', 'Cleaning up environment...');
348
+
349
+ stopContainer(CONFIG.containerName);
350
+ removeContainer(CONFIG.containerName);
351
+ killProcessOnPort(3006);
352
+ killProcessOnPort(3010);
353
+ killProcessOnPort(5433);
354
+
355
+ log('✅', 'Cleanup completed');
356
+ }
357
+
358
+ // ============================================================================
359
+ // CLI
360
+ // ============================================================================
361
+
362
+ function showHelp(): void {
363
+ console.log(`
364
+ ${colors.cyan('E2E Test Environment Setup Script')}
365
+
366
+ ${colors.dim('Usage:')}
367
+ bun e2e/scripts/setup.ts [options]
368
+
369
+ ${colors.dim('Options:')}
370
+ --clean Clean up existing containers and processes
371
+ --skip-db Skip database setup (use existing)
372
+ --skip-migrate Skip database migration
373
+ --build Build the application before starting
374
+ --start Start the server after setup
375
+ --port <port> Server port (default: ${CONFIG.defaultPort})
376
+ --help Show this help message
377
+
378
+ ${colors.dim('Examples:')}
379
+ ${colors.green('bun e2e/scripts/setup.ts')} # Setup DB only
380
+ ${colors.green('bun e2e/scripts/setup.ts --start')} # Setup DB and start server
381
+ ${colors.green('bun e2e/scripts/setup.ts --build --start')} # Full setup with build
382
+ ${colors.green('bun e2e/scripts/setup.ts --clean')} # Clean up environment
383
+
384
+ ${colors.dim('After setup, run tests with:')}
385
+ cd e2e
386
+ BASE_URL=http://localhost:3006 bun run test
387
+ `);
388
+ }
389
+
390
+ interface Options {
391
+ build: boolean;
392
+ clean: boolean;
393
+ help: boolean;
394
+ port: number;
395
+ skipDb: boolean;
396
+ skipMigrate: boolean;
397
+ start: boolean;
398
+ }
399
+
400
+ function parseArgs(): Options {
401
+ const args = process.argv.slice(2);
402
+ const options: Options = {
403
+ build: false,
404
+ clean: false,
405
+ help: false,
406
+ port: CONFIG.defaultPort,
407
+ skipDb: false,
408
+ skipMigrate: false,
409
+ start: false,
410
+ };
411
+
412
+ for (let i = 0; i < args.length; i++) {
413
+ switch (args[i]) {
414
+ case '--help':
415
+ case '-h':
416
+ options.help = true;
417
+ break;
418
+ case '--clean':
419
+ options.clean = true;
420
+ break;
421
+ case '--skip-db':
422
+ options.skipDb = true;
423
+ break;
424
+ case '--skip-migrate':
425
+ options.skipMigrate = true;
426
+ break;
427
+ case '--build':
428
+ options.build = true;
429
+ break;
430
+ case '--start':
431
+ options.start = true;
432
+ break;
433
+ case '--port':
434
+ options.port = parseInt(args[++i], 10) || CONFIG.defaultPort;
435
+ break;
436
+ }
437
+ }
438
+
439
+ return options;
440
+ }
441
+
442
+ // ============================================================================
443
+ // Main
444
+ // ============================================================================
445
+
446
+ async function main(): Promise<void> {
447
+ const options = parseArgs();
448
+
449
+ if (options.help) {
450
+ showHelp();
451
+ process.exit(0);
452
+ }
453
+
454
+ console.log(`
455
+ ${colors.cyan('🤯 LobeHub E2E Environment Setup')}
456
+ ${'─'.repeat(50)}
457
+ `);
458
+
459
+ try {
460
+ if (options.clean) {
461
+ cleanup();
462
+ process.exit(0);
463
+ }
464
+
465
+ // Calculate total steps
466
+ let totalSteps = 0;
467
+ if (!options.skipDb) totalSteps++;
468
+ if (!options.skipMigrate) totalSteps++;
469
+ if (options.build) totalSteps++;
470
+ if (options.start) totalSteps++;
471
+
472
+ let currentStep = 0;
473
+
474
+ // Step 1: Start database
475
+ if (!options.skipDb) {
476
+ logStep(++currentStep, totalSteps, 'Setting up PostgreSQL database');
477
+ await startPostgres();
478
+ }
479
+
480
+ // Step 2: Run migration
481
+ if (!options.skipMigrate) {
482
+ logStep(++currentStep, totalSteps, 'Running database migrations');
483
+ await runMigration();
484
+ }
485
+
486
+ // Step 3: Build (optional)
487
+ if (options.build) {
488
+ logStep(++currentStep, totalSteps, 'Building application');
489
+ await buildApp();
490
+ }
491
+
492
+ // Step 4: Start server (optional)
493
+ if (options.start) {
494
+ logStep(++currentStep, totalSteps, 'Starting application server');
495
+ await startServer(options.port);
496
+ }
497
+
498
+ console.log(`
499
+ ${'─'.repeat(50)}
500
+ ${colors.green('✅ E2E environment setup completed!')}
501
+ `);
502
+
503
+ // Print next steps
504
+ if (!options.start) {
505
+ console.log(`${colors.dim('Next steps:')}`);
506
+ console.log(`
507
+ 1. Start the server (in project root):
508
+ ${colors.cyan(`bun e2e/scripts/setup.ts --start`)}
509
+
510
+ 2. Or start manually:
511
+ ${colors.cyan(`bunx next start -p ${options.port}`)}
512
+ `);
513
+ }
514
+
515
+ console.log(`${colors.dim('Run tests:')}`);
516
+ console.log(`
517
+ cd e2e
518
+ ${colors.cyan(`BASE_URL=http://localhost:${options.port} bun run test`)}
519
+
520
+ ${colors.dim('# Debug mode (show browser)')}
521
+ ${colors.cyan(`HEADLESS=false BASE_URL=http://localhost:${options.port} bun run test`)}
522
+ `);
523
+ } catch (error) {
524
+ console.error(`\n${colors.red('❌ Setup failed:')}`, error);
525
+ process.exit(1);
526
+ }
527
+ }
528
+
529
+ main();
@@ -0,0 +1,62 @@
1
+ @journey @home @sidebar @agent
2
+ Feature: Home 页面 Agent 管理
3
+ 作为用户,我希望能够在 Home 页面管理 Agent
4
+
5
+ Background:
6
+ Given 用户已登录系统
7
+ And 用户在 Home 页面有一个 Agent
8
+
9
+ # ============================================
10
+ # 重命名
11
+ # ============================================
12
+
13
+ @HOME-AGENT-RENAME-001 @P0
14
+ Scenario: 通过右键菜单重命名 Agent
15
+ When 用户右键点击该 Agent
16
+ And 用户在菜单中选择重命名
17
+ And 用户输入新的名称 "My Renamed Agent"
18
+ Then 该项名称应该更新为 "My Renamed Agent"
19
+
20
+ @HOME-AGENT-RENAME-002 @P0
21
+ Scenario: 通过更多操作菜单重命名 Agent
22
+ When 用户悬停在该 Agent 上
23
+ And 用户点击更多操作按钮
24
+ And 用户在菜单中选择重命名
25
+ And 用户输入新的名称 "Agent From Menu"
26
+ Then 该项名称应该更新为 "Agent From Menu"
27
+
28
+ @HOME-AGENT-RENAME-003 @P1
29
+ Scenario: 重命名后按 Enter 确认
30
+ When 用户右键点击该 Agent
31
+ And 用户在菜单中选择重命名
32
+ And 用户输入新的名称 "Enter Confirmed" 并按 Enter
33
+ Then 该项名称应该更新为 "Enter Confirmed"
34
+
35
+ # ============================================
36
+ # 置顶
37
+ # ============================================
38
+
39
+ @HOME-AGENT-PIN-001 @P1
40
+ Scenario: 置顶 Agent
41
+ Given 该 Agent 未被置顶
42
+ When 用户右键点击该 Agent
43
+ And 用户在菜单中选择置顶
44
+ Then Agent 应该显示置顶图标
45
+
46
+ @HOME-AGENT-PIN-002 @P1
47
+ Scenario: 取消置顶 Agent
48
+ Given 该 Agent 已被置顶
49
+ When 用户右键点击该 Agent
50
+ And 用户在菜单中选择取消置顶
51
+ Then Agent 不应该显示置顶图标
52
+
53
+ # ============================================
54
+ # 删除
55
+ # ============================================
56
+
57
+ @HOME-AGENT-DELETE-001 @P0
58
+ Scenario: 删除 Agent
59
+ When 用户右键点击该 Agent
60
+ And 用户在菜单中选择删除
61
+ And 用户在弹窗中确认删除
62
+ Then Agent 应该从列表中移除
@@ -0,0 +1,62 @@
1
+ @journey @home @sidebar @group
2
+ Feature: Home 页面 Agent Group 管理
3
+ 作为用户,我希望能够在 Home 页面管理 Agent Group
4
+
5
+ Background:
6
+ Given 用户已登录系统
7
+ And 用户在 Home 页面有一个 Agent Group
8
+
9
+ # ============================================
10
+ # 重命名
11
+ # ============================================
12
+
13
+ @HOME-GROUP-RENAME-001 @P0
14
+ Scenario: 通过右键菜单重命名 Agent Group
15
+ When 用户右键点击该 Agent Group
16
+ And 用户在菜单中选择重命名
17
+ And 用户输入新的名称 "My Renamed Group"
18
+ Then 该项名称应该更新为 "My Renamed Group"
19
+
20
+ @HOME-GROUP-RENAME-002 @P0
21
+ Scenario: 通过更多操作菜单重命名 Agent Group
22
+ When 用户悬停在该 Agent Group 上
23
+ And 用户点击更多操作按钮
24
+ And 用户在菜单中选择重命名
25
+ And 用户输入新的名称 "Group From Menu"
26
+ Then 该项名称应该更新为 "Group From Menu"
27
+
28
+ @HOME-GROUP-RENAME-003 @P1
29
+ Scenario: 重命名后按 Enter 确认
30
+ When 用户右键点击该 Agent Group
31
+ And 用户在菜单中选择重命名
32
+ And 用户输入新的名称 "Enter Confirmed" 并按 Enter
33
+ Then 该项名称应该更新为 "Enter Confirmed"
34
+
35
+ # ============================================
36
+ # 置顶
37
+ # ============================================
38
+
39
+ @HOME-GROUP-PIN-001 @P1
40
+ Scenario: 置顶 Agent Group
41
+ Given 该 Agent Group 未被置顶
42
+ When 用户右键点击该 Agent Group
43
+ And 用户在菜单中选择置顶
44
+ Then Agent Group 应该显示置顶图标
45
+
46
+ @HOME-GROUP-PIN-002 @P1
47
+ Scenario: 取消置顶 Agent Group
48
+ Given 该 Agent Group 已被置顶
49
+ When 用户右键点击该 Agent Group
50
+ And 用户在菜单中选择取消置顶
51
+ Then Agent Group 不应该显示置顶图标
52
+
53
+ # ============================================
54
+ # 删除
55
+ # ============================================
56
+
57
+ @HOME-GROUP-DELETE-001 @P0
58
+ Scenario: 删除 Agent Group
59
+ When 用户右键点击该 Agent Group
60
+ And 用户在菜单中选择删除
61
+ And 用户在弹窗中确认删除
62
+ Then Agent Group 应该从列表中移除