@lobehub/lobehub 2.0.0-next.267 → 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 (126) hide show
  1. package/.cursor/rules/microcopy-cn.mdc +75 -63
  2. package/.cursor/rules/microcopy-en.mdc +4 -8
  3. package/CHANGELOG.md +25 -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 +5 -0
  8. package/docs/development/database-schema.dbml +4 -0
  9. package/e2e/CLAUDE.md +9 -8
  10. package/e2e/cucumber.config.js +1 -0
  11. package/e2e/src/features/page/README.md +118 -0
  12. package/e2e/src/features/page/crud.feature +62 -0
  13. package/e2e/src/features/page/editor-content.feature +93 -0
  14. package/e2e/src/features/page/editor-meta.feature +60 -0
  15. package/e2e/src/steps/agent/conversation.steps.ts +4 -4
  16. package/e2e/src/steps/home/sidebarAgent.steps.ts +91 -94
  17. package/e2e/src/steps/home/sidebarGroup.steps.ts +4 -4
  18. package/e2e/src/steps/hooks.ts +2 -0
  19. package/e2e/src/steps/page/editor-content.steps.ts +344 -0
  20. package/e2e/src/steps/page/editor-meta.steps.ts +410 -0
  21. package/e2e/src/steps/page/page-crud.steps.ts +363 -0
  22. package/e2e/src/support/world.ts +12 -0
  23. package/locales/ar/file.json +2 -0
  24. package/locales/bg-BG/file.json +2 -0
  25. package/locales/de-DE/file.json +2 -0
  26. package/locales/en-US/auth.json +1 -1
  27. package/locales/en-US/file.json +2 -0
  28. package/locales/en-US/metadata.json +2 -2
  29. package/locales/es-ES/file.json +2 -0
  30. package/locales/fa-IR/file.json +2 -0
  31. package/locales/fr-FR/file.json +2 -0
  32. package/locales/it-IT/file.json +2 -0
  33. package/locales/ja-JP/file.json +2 -0
  34. package/locales/ko-KR/file.json +2 -0
  35. package/locales/nl-NL/file.json +2 -0
  36. package/locales/pl-PL/file.json +2 -0
  37. package/locales/pt-BR/file.json +2 -0
  38. package/locales/ru-RU/file.json +2 -0
  39. package/locales/tr-TR/file.json +2 -0
  40. package/locales/vi-VN/file.json +2 -0
  41. package/locales/zh-CN/file.json +2 -0
  42. package/locales/zh-TW/file.json +2 -0
  43. package/package.json +1 -1
  44. package/packages/builtin-agents/src/agents/agent-builder/index.ts +1 -1
  45. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +1 -1
  46. package/packages/builtin-agents/src/agents/page-agent/index.ts +1 -1
  47. package/packages/const/src/settings/group.ts +0 -10
  48. package/packages/database/migrations/0068_update_group_data.sql +4 -0
  49. package/packages/database/migrations/meta/0068_snapshot.json +9588 -0
  50. package/packages/database/migrations/meta/_journal.json +7 -0
  51. package/packages/database/src/models/__tests__/chatGroup.test.ts +5 -7
  52. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +185 -0
  53. package/packages/database/src/models/knowledgeBase.ts +67 -3
  54. package/packages/database/src/repositories/agentGroup/index.test.ts +23 -29
  55. package/packages/database/src/repositories/agentGroup/index.ts +4 -9
  56. package/packages/database/src/repositories/knowledge/index.ts +3 -3
  57. package/packages/database/src/schemas/chatGroup.ts +4 -3
  58. package/packages/database/src/types/chatGroup.ts +0 -7
  59. package/packages/types/src/agentGroup/index.ts +30 -9
  60. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +9 -32
  61. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +3 -37
  62. package/src/app/[variants]/(main)/home/_layout/hooks/useSessionGroupMenuItems.tsx +7 -53
  63. package/src/app/[variants]/(main)/home/features/RecentPage/List.tsx +2 -1
  64. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +1 -1
  65. package/src/app/[variants]/(main)/resource/library/_layout/Sidebar.tsx +2 -2
  66. package/src/app/[variants]/(main)/resource/library/features/LibraryMenu.tsx +2 -2
  67. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +2 -12
  68. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +5 -27
  69. package/src/components/DragUpload/index.tsx +24 -27
  70. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +2 -11
  71. package/src/features/CommandMenu/useCommandMenu.ts +4 -14
  72. package/src/features/ResourceManager/components/Editor/index.tsx +2 -3
  73. package/src/features/ResourceManager/components/Explorer/Header/index.tsx +13 -17
  74. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +1 -1
  75. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/TruncatedFileName.tsx +130 -0
  76. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +36 -4
  77. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +4 -3
  78. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +58 -2
  79. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +58 -6
  80. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +2 -5
  81. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +9 -5
  82. package/src/features/ResourceManager/components/Explorer/index.tsx +11 -56
  83. package/src/features/ResourceManager/components/Header/AddButton.tsx +5 -6
  84. package/src/features/ResourceManager/components/LibraryHierarchy/HierarchyNode.tsx +382 -0
  85. package/src/features/ResourceManager/components/LibraryHierarchy/index.tsx +396 -0
  86. package/src/features/ResourceManager/components/LibraryHierarchy/styles.ts +19 -0
  87. package/src/features/ResourceManager/components/LibraryHierarchy/treeState.ts +178 -0
  88. package/src/features/ResourceManager/components/LibraryHierarchy/types.ts +10 -0
  89. package/src/features/ResourceManager/index.tsx +3 -0
  90. package/src/layout/GlobalProvider/GroupWizardProvider.tsx +6 -29
  91. package/src/locales/default/auth.ts +1 -1
  92. package/src/locales/default/file.ts +2 -0
  93. package/src/locales/default/metadata.ts +2 -2
  94. package/src/server/modules/AgentRuntime/AgentRuntimeCoordinator.ts +30 -30
  95. package/src/server/modules/AgentRuntime/AgentStateManager.ts +23 -23
  96. package/src/server/modules/AgentRuntime/InMemoryAgentStateManager.ts +16 -16
  97. package/src/server/modules/AgentRuntime/InMemoryStreamEventManager.ts +13 -13
  98. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -2
  99. package/src/server/modules/AgentRuntime/StreamEventManager.ts +18 -18
  100. package/src/server/modules/AgentRuntime/types.ts +21 -21
  101. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +8 -8
  102. package/src/server/routers/lambda/agentGroup.ts +10 -12
  103. package/src/server/services/document/index.ts +1 -0
  104. package/src/store/agentGroup/slices/curd.test.ts +4 -4
  105. package/src/store/file/slices/fileManager/action.ts +12 -4
  106. package/src/store/home/slices/homeInput/action.ts +0 -3
  107. package/src/store/session/slices/session/action.ts +5 -9
  108. package/src/app/[variants]/(mobile)/chat/settings/features/AgentTeamSettings/index.tsx +0 -95
  109. package/src/features/GroupChatSettings/AgentCard.tsx +0 -154
  110. package/src/features/GroupChatSettings/AgentTeamChatSettings.tsx +0 -179
  111. package/src/features/GroupChatSettings/AgentTeamMembersSettings.tsx +0 -244
  112. package/src/features/GroupChatSettings/AgentTeamMetaSettings.tsx +0 -94
  113. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +0 -54
  114. package/src/features/GroupChatSettings/GroupCategory/index.tsx +0 -30
  115. package/src/features/GroupChatSettings/GroupCategory/useGroupCategory.tsx +0 -42
  116. package/src/features/GroupChatSettings/GroupChatSettingsProvider.tsx +0 -19
  117. package/src/features/GroupChatSettings/HostMemberCard.tsx +0 -113
  118. package/src/features/GroupChatSettings/StoreUpdater.tsx +0 -34
  119. package/src/features/GroupChatSettings/hooks/useGroupChatSettings.ts +0 -25
  120. package/src/features/GroupChatSettings/index.ts +0 -16
  121. package/src/features/GroupChatSettings/store/action.ts +0 -105
  122. package/src/features/GroupChatSettings/store/index.ts +0 -18
  123. package/src/features/GroupChatSettings/store/initialState.ts +0 -23
  124. package/src/features/GroupChatSettings/store/selectors.ts +0 -13
  125. package/src/features/ResourceManager/components/Tree/index.tsx +0 -883
  126. /package/src/features/ResourceManager/components/{Tree → LibraryHierarchy}/TreeSkeleton.tsx +0 -0
@@ -0,0 +1,93 @@
1
+ @journey @P0 @page
2
+ Feature: Page 编辑器富文本编辑
3
+
4
+ 作为用户,我希望能够使用富文本编辑器编写内容,
5
+ 以便创建格式丰富的文档
6
+
7
+ Background:
8
+ Given 用户已登录系统
9
+
10
+ # ============================================
11
+ # 基础文本编辑
12
+ # ============================================
13
+
14
+ @PAGE-CONTENT-001
15
+ Scenario: 输入基础文本内容
16
+ Given 用户打开一个文稿编辑器
17
+ When 用户点击编辑器内容区域
18
+ And 用户输入文本 "这是一段测试内容"
19
+ Then 编辑器应该显示输入的文本
20
+
21
+ @PAGE-CONTENT-002 @P1
22
+ Scenario: 编辑已有内容
23
+ Given 用户打开一个文稿编辑器
24
+ When 用户在编辑器中输入内容 "原始内容"
25
+ And 用户选中所有内容
26
+ And 用户输入文本 "修改后的内容"
27
+ Then 编辑器应该显示 "修改后的内容"
28
+
29
+ # ============================================
30
+ # 斜杠命令
31
+ # ============================================
32
+
33
+ @PAGE-SLASH-001 @P1
34
+ Scenario: 使用斜杠命令打开菜单
35
+ Given 用户打开一个文稿编辑器
36
+ When 用户点击编辑器内容区域
37
+ And 用户输入斜杠 "/"
38
+ Then 应该显示斜杠命令菜单
39
+
40
+ @PAGE-SLASH-002 @P1
41
+ Scenario: 通过斜杠命令插入一级标题
42
+ Given 用户打开一个文稿编辑器
43
+ When 用户点击编辑器内容区域
44
+ And 用户输入斜杠命令 "/h1"
45
+ And 用户按下 Enter 键
46
+ And 用户输入文本 "一级标题内容"
47
+ Then 编辑器应该包含一级标题
48
+
49
+ @PAGE-SLASH-003 @P1
50
+ Scenario: 通过斜杠命令插入无序列表
51
+ Given 用户打开一个文稿编辑器
52
+ When 用户点击编辑器内容区域
53
+ And 用户输入斜杠命令 "/ul"
54
+ And 用户按下 Enter 键
55
+ And 用户输入文本 "列表项一"
56
+ Then 编辑器应该包含无序列表
57
+
58
+ @PAGE-SLASH-004 @P2
59
+ Scenario: 通过斜杠命令插入任务列表
60
+ Given 用户打开一个文稿编辑器
61
+ When 用户点击编辑器内容区域
62
+ And 用户输入斜杠命令 "/tl"
63
+ And 用户按下 Enter 键
64
+ And 用户输入文本 "待办事项"
65
+ Then 编辑器应该包含任务列表
66
+
67
+ @PAGE-SLASH-005 @P2 @skip
68
+ Scenario: 通过斜杠命令插入代码块
69
+ Given 用户打开一个文稿编辑器
70
+ When 用户点击编辑器内容区域
71
+ And 用户输入斜杠命令 "/codeblock"
72
+ And 用户按下 Enter 键
73
+ Then 编辑器应该包含代码块
74
+
75
+ # ============================================
76
+ # 文本格式化
77
+ # ============================================
78
+
79
+ @PAGE-FORMAT-001 @P1
80
+ Scenario: 使用快捷键加粗文本
81
+ Given 用户打开一个文稿编辑器
82
+ When 用户在编辑器中输入内容 "加粗文本"
83
+ And 用户选中所有内容
84
+ And 用户按下快捷键 "Meta+B"
85
+ Then 选中的文本应该被加粗
86
+
87
+ @PAGE-FORMAT-002 @P2
88
+ Scenario: 使用快捷键斜体文本
89
+ Given 用户打开一个文稿编辑器
90
+ When 用户在编辑器中输入内容 "斜体文本"
91
+ And 用户选中所有内容
92
+ And 用户按下快捷键 "Meta+I"
93
+ Then 选中的文本应该变为斜体
@@ -0,0 +1,60 @@
1
+ @journey @P0 @page
2
+ Feature: Page 编辑器元数据编辑
3
+
4
+ 作为用户,我希望能够编辑文稿的标题和图标,
5
+ 以便更好地组织和识别我的文档
6
+
7
+ Background:
8
+ Given 用户已登录系统
9
+
10
+ # ============================================
11
+ # 标题编辑
12
+ # ============================================
13
+
14
+ @PAGE-TITLE-001
15
+ Scenario: 编辑文稿标题
16
+ Given 用户打开一个文稿编辑器
17
+ When 用户点击标题输入框
18
+ And 用户输入标题 "我的测试文稿"
19
+ And 用户按下 Enter 键
20
+ Then 文稿标题应该更新为 "我的测试文稿"
21
+
22
+ @PAGE-TITLE-002 @P1
23
+ Scenario: 编辑标题后点击其他区域保存
24
+ Given 用户打开一个文稿编辑器
25
+ When 用户点击标题输入框
26
+ And 用户输入标题 "Click Away Title"
27
+ And 用户点击编辑器内容区域
28
+ Then 文稿标题应该更新为 "Click Away Title"
29
+
30
+ @PAGE-TITLE-003 @P1
31
+ Scenario: 清空标题后显示占位符
32
+ Given 用户打开一个文稿编辑器
33
+ When 用户点击标题输入框
34
+ And 用户清空标题内容
35
+ Then 应该显示标题占位符
36
+
37
+ # ============================================
38
+ # Emoji 图标
39
+ # ============================================
40
+
41
+ @PAGE-EMOJI-001 @P1
42
+ Scenario: 为文稿添加 Emoji 图标
43
+ Given 用户打开一个文稿编辑器
44
+ When 用户点击选择图标按钮
45
+ And 用户选择一个 Emoji
46
+ Then 文稿应该显示所选的 Emoji 图标
47
+
48
+ @PAGE-EMOJI-002 @P1
49
+ Scenario: 更换文稿的 Emoji 图标
50
+ Given 用户打开一个带有 Emoji 的文稿
51
+ When 用户点击已有的 Emoji 图标
52
+ And 用户选择另一个 Emoji
53
+ Then 文稿图标应该更新为新的 Emoji
54
+
55
+ @PAGE-EMOJI-003 @P2
56
+ Scenario: 删除文稿的 Emoji 图标
57
+ Given 用户打开一个带有 Emoji 的文稿
58
+ When 用户点击已有的 Emoji 图标
59
+ And 用户点击删除图标按钮
60
+ Then 文稿不应该显示 Emoji 图标
@@ -7,7 +7,7 @@ import { Given, Then, When } from '@cucumber/cucumber';
7
7
  import { expect } from '@playwright/test';
8
8
 
9
9
  import { llmMockManager, presetResponses } from '../../mocks/llm';
10
- import { CustomWorld } from '../../support/world';
10
+ import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
11
11
 
12
12
  // ============================================
13
13
  // Given Steps
@@ -29,19 +29,19 @@ Given('用户进入 Lobe AI 对话页面', async function (this: CustomWorld) {
29
29
  console.log(' 📍 Step: 导航到首页...');
30
30
  // Navigate to home page first
31
31
  await this.page.goto('/');
32
- await this.page.waitForLoadState('networkidle', { timeout: 10_000 });
32
+ await this.page.waitForLoadState('networkidle', { timeout: WAIT_TIMEOUT });
33
33
 
34
34
  console.log(' 📍 Step: 查找 Lobe AI...');
35
35
  // Find and click on "Lobe AI" agent in the sidebar/home
36
36
  const lobeAIAgent = this.page.locator('text=Lobe AI').first();
37
- await expect(lobeAIAgent).toBeVisible({ timeout: 10_000 });
37
+ await expect(lobeAIAgent).toBeVisible({ timeout: WAIT_TIMEOUT });
38
38
 
39
39
  console.log(' 📍 Step: 点击 Lobe AI...');
40
40
  await lobeAIAgent.click();
41
41
 
42
42
  console.log(' 📍 Step: 等待聊天界面加载...');
43
43
  // Wait for the chat interface to be ready
44
- await this.page.waitForLoadState('networkidle', { timeout: 10_000 });
44
+ await this.page.waitForLoadState('networkidle', { timeout: WAIT_TIMEOUT });
45
45
 
46
46
  console.log(' 📍 Step: 查找输入框...');
47
47
  // The input is a rich text editor with contenteditable
@@ -10,7 +10,86 @@ import { Given, Then, When } from '@cucumber/cucumber';
10
10
  import { expect } from '@playwright/test';
11
11
 
12
12
  import { TEST_USER } from '../../support/seedTestUser';
13
- import { CustomWorld } from '../../support/world';
13
+ import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
14
+
15
+ // ============================================
16
+ // Helper Functions
17
+ // ============================================
18
+
19
+ async function inputNewName(
20
+ this: CustomWorld,
21
+ newName: string,
22
+ pressEnter: boolean,
23
+ ): Promise<void> {
24
+ await this.page.waitForTimeout(300);
25
+
26
+ // Try to find the popover input
27
+ const popoverInputSelectors = [
28
+ '.ant-popover-inner input',
29
+ '.ant-popover-content input',
30
+ '.ant-popover input',
31
+ ];
32
+
33
+ let renameInput = null;
34
+
35
+ for (const selector of popoverInputSelectors) {
36
+ try {
37
+ const locator = this.page.locator(selector).first();
38
+ await locator.waitFor({ state: 'visible', timeout: 2000 });
39
+ renameInput = locator;
40
+ break;
41
+ } catch {
42
+ // Try next selector
43
+ }
44
+ }
45
+
46
+ if (!renameInput) {
47
+ // Fallback: find any visible input
48
+ const allInputs = this.page.locator('input:visible');
49
+ const count = await allInputs.count();
50
+
51
+ for (let i = 0; i < count; i++) {
52
+ const input = allInputs.nth(i);
53
+ const placeholder = (await input.getAttribute('placeholder').catch(() => '')) || '';
54
+ if (placeholder.includes('Search') || placeholder.includes('搜索')) continue;
55
+
56
+ const isInPopover = await input.evaluate((el) => {
57
+ return el.closest('.ant-popover') !== null || el.closest('[class*="popover"]') !== null;
58
+ });
59
+
60
+ if (isInPopover || count <= 2) {
61
+ renameInput = input;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+
67
+ if (renameInput) {
68
+ await renameInput.click();
69
+ await renameInput.clear();
70
+ await renameInput.fill(newName);
71
+
72
+ if (pressEnter) {
73
+ await renameInput.press('Enter');
74
+ } else {
75
+ await this.page.click('body', { position: { x: 10, y: 10 } });
76
+ }
77
+ } else {
78
+ // Keyboard fallback
79
+ await this.page.keyboard.press('Meta+A');
80
+ await this.page.waitForTimeout(50);
81
+ await this.page.keyboard.type(newName, { delay: 20 });
82
+
83
+ if (pressEnter) {
84
+ await this.page.keyboard.press('Enter');
85
+ } else {
86
+ await this.page.click('body', { position: { x: 10, y: 10 } });
87
+ }
88
+ }
89
+
90
+ await this.page.waitForTimeout(1000);
91
+ console.log(` ✅ 已输入新名称 "${newName}"`);
92
+ }
14
93
 
15
94
  /**
16
95
  * Create a test agent directly in database
@@ -60,7 +139,7 @@ Given('用户在 Home 页面有一个 Agent', async function (this: CustomWorld)
60
139
  console.log(' 📍 Step: 查找新创建的 Agent...');
61
140
  // Look for the newly created agent in the sidebar by its specific ID
62
141
  const agentItem = this.page.locator(`a[href="/agent/${agentId}"]`).first();
63
- await expect(agentItem).toBeVisible({ timeout: 10_000 });
142
+ await expect(agentItem).toBeVisible({ timeout: WAIT_TIMEOUT });
64
143
 
65
144
  // Store agent reference for later use
66
145
  const agentLabel = await agentItem.getAttribute('aria-label');
@@ -81,7 +160,7 @@ Given('该 Agent 未被置顶', async function (this: CustomWorld) {
81
160
  // Unpin it first
82
161
  await targetItem.click({ button: 'right' });
83
162
  await this.page.waitForTimeout(300);
84
- const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|Unpin/i });
163
+ const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|unpin/i });
85
164
  if ((await unpinOption.count()) > 0) {
86
165
  await unpinOption.click();
87
166
  await this.page.waitForTimeout(500);
@@ -103,7 +182,7 @@ Given('该 Agent 已被置顶', async function (this: CustomWorld) {
103
182
  // Pin it first
104
183
  await targetItem.click({ button: 'right' });
105
184
  await this.page.waitForTimeout(300);
106
- const pinOption = this.page.getByRole('menuitem', { name: /置顶|Pin/i });
185
+ const pinOption = this.page.getByRole('menuitem', { name: /置顶|pin/i });
107
186
  if ((await pinOption.count()) > 0) {
108
187
  await pinOption.click();
109
188
  await this.page.waitForTimeout(500);
@@ -179,7 +258,7 @@ When('用户点击更多操作按钮', async function (this: CustomWorld) {
179
258
  When('用户在菜单中选择重命名', async function (this: CustomWorld) {
180
259
  console.log(' 📍 Step: 选择重命名选项...');
181
260
 
182
- const renameOption = this.page.getByRole('menuitem', { name: /^(Rename|重命名)$/i });
261
+ const renameOption = this.page.getByRole('menuitem', { name: /^(rename|重命名)$/i });
183
262
  await expect(renameOption).toBeVisible({ timeout: 5000 });
184
263
  await renameOption.click();
185
264
  await this.page.waitForTimeout(500);
@@ -190,7 +269,7 @@ When('用户在菜单中选择重命名', async function (this: CustomWorld) {
190
269
  When('用户在菜单中选择置顶', async function (this: CustomWorld) {
191
270
  console.log(' 📍 Step: 选择置顶选项...');
192
271
 
193
- const pinOption = this.page.getByRole('menuitem', { name: /^(Pin|置顶)$/i });
272
+ const pinOption = this.page.getByRole('menuitem', { name: /^(pin|置顶)$/i });
194
273
  await expect(pinOption).toBeVisible({ timeout: 5000 });
195
274
  await pinOption.click();
196
275
  await this.page.waitForTimeout(500);
@@ -201,7 +280,7 @@ When('用户在菜单中选择置顶', async function (this: CustomWorld) {
201
280
  When('用户在菜单中选择取消置顶', async function (this: CustomWorld) {
202
281
  console.log(' 📍 Step: 选择取消置顶选项...');
203
282
 
204
- const unpinOption = this.page.getByRole('menuitem', { name: /^(Unpin|取消置顶)$/i });
283
+ const unpinOption = this.page.getByRole('menuitem', { name: /^(unpin|取消置顶)$/i });
205
284
  await expect(unpinOption).toBeVisible({ timeout: 5000 });
206
285
  await unpinOption.click();
207
286
  await this.page.waitForTimeout(500);
@@ -212,7 +291,7 @@ When('用户在菜单中选择取消置顶', async function (this: CustomWorld)
212
291
  When('用户在菜单中选择删除', async function (this: CustomWorld) {
213
292
  console.log(' 📍 Step: 选择删除选项...');
214
293
 
215
- const deleteOption = this.page.getByRole('menuitem', { name: /^(Delete|删除)$/i });
294
+ const deleteOption = this.page.getByRole('menuitem', { name: /^(delete|删除)$/i });
216
295
  await expect(deleteOption).toBeVisible({ timeout: 5000 });
217
296
  await deleteOption.click();
218
297
  await this.page.waitForTimeout(300);
@@ -236,13 +315,10 @@ When('用户输入新的名称 {string}', async function (this: CustomWorld, new
236
315
  await inputNewName.call(this, newName, false);
237
316
  });
238
317
 
239
- When(
240
- '用户输入新的名称 {string} 并按 Enter',
241
- async function (this: CustomWorld, newName: string) {
242
- console.log(` 📍 Step: 输入新名称 "${newName}" 并按 Enter...`);
243
- await inputNewName.call(this, newName, true);
244
- },
245
- );
318
+ When('用户输入新的名称 {string} 并按 Enter', async function (this: CustomWorld, newName: string) {
319
+ console.log(` 📍 Step: 输入新名称 "${newName}" 并按 Enter...`);
320
+ await inputNewName.call(this, newName, true);
321
+ });
246
322
 
247
323
  // ============================================
248
324
  // Then Steps
@@ -292,82 +368,3 @@ Then('Agent 应该从列表中移除', async function (this: CustomWorld) {
292
368
 
293
369
  console.log(' ✅ Agent 已从列表中移除');
294
370
  });
295
-
296
- // ============================================
297
- // Helper Functions
298
- // ============================================
299
-
300
- async function inputNewName(
301
- this: CustomWorld,
302
- newName: string,
303
- pressEnter: boolean,
304
- ): Promise<void> {
305
- await this.page.waitForTimeout(300);
306
-
307
- // Try to find the popover input
308
- const popoverInputSelectors = [
309
- '.ant-popover-inner input',
310
- '.ant-popover-content input',
311
- '.ant-popover input',
312
- ];
313
-
314
- let renameInput = null;
315
-
316
- for (const selector of popoverInputSelectors) {
317
- try {
318
- const locator = this.page.locator(selector).first();
319
- await locator.waitFor({ state: 'visible', timeout: 2000 });
320
- renameInput = locator;
321
- break;
322
- } catch {
323
- // Try next selector
324
- }
325
- }
326
-
327
- if (!renameInput) {
328
- // Fallback: find any visible input
329
- const allInputs = this.page.locator('input:visible');
330
- const count = await allInputs.count();
331
-
332
- for (let i = 0; i < count; i++) {
333
- const input = allInputs.nth(i);
334
- const placeholder = (await input.getAttribute('placeholder').catch(() => '')) || '';
335
- if (placeholder.includes('Search') || placeholder.includes('搜索')) continue;
336
-
337
- const isInPopover = await input.evaluate((el) => {
338
- return el.closest('.ant-popover') !== null || el.closest('[class*="popover"]') !== null;
339
- });
340
-
341
- if (isInPopover || count <= 2) {
342
- renameInput = input;
343
- break;
344
- }
345
- }
346
- }
347
-
348
- if (renameInput) {
349
- await renameInput.click();
350
- await renameInput.clear();
351
- await renameInput.fill(newName);
352
-
353
- if (pressEnter) {
354
- await renameInput.press('Enter');
355
- } else {
356
- await this.page.click('body', { position: { x: 10, y: 10 } });
357
- }
358
- } else {
359
- // Keyboard fallback
360
- await this.page.keyboard.press('Meta+A');
361
- await this.page.waitForTimeout(50);
362
- await this.page.keyboard.type(newName, { delay: 20 });
363
-
364
- if (pressEnter) {
365
- await this.page.keyboard.press('Enter');
366
- } else {
367
- await this.page.click('body', { position: { x: 10, y: 10 } });
368
- }
369
- }
370
-
371
- await this.page.waitForTimeout(1000);
372
- console.log(` ✅ 已输入新名称 "${newName}"`);
373
- }
@@ -10,7 +10,7 @@ import { Given, Then, When } from '@cucumber/cucumber';
10
10
  import { expect } from '@playwright/test';
11
11
 
12
12
  import { TEST_USER } from '../../support/seedTestUser';
13
- import { CustomWorld } from '../../support/world';
13
+ import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
14
14
 
15
15
  /**
16
16
  * Create a test chat group directly in database
@@ -58,7 +58,7 @@ Given('用户在 Home 页面有一个 Agent Group', async function (this: Custom
58
58
 
59
59
  console.log(' 📍 Step: 查找新创建的 Agent Group...');
60
60
  const groupItem = this.page.locator(`a[href="/group/${groupId}"]`).first();
61
- await expect(groupItem).toBeVisible({ timeout: 10_000 });
61
+ await expect(groupItem).toBeVisible({ timeout: WAIT_TIMEOUT });
62
62
 
63
63
  const groupLabel = await groupItem.getAttribute('aria-label');
64
64
  this.testContext.targetItemId = groupLabel || groupId;
@@ -76,7 +76,7 @@ Given('该 Agent Group 未被置顶', async function (this: CustomWorld) {
76
76
  if ((await pinIcon.count()) > 0) {
77
77
  await targetItem.click({ button: 'right' });
78
78
  await this.page.waitForTimeout(300);
79
- const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|Unpin/i });
79
+ const unpinOption = this.page.getByRole('menuitem', { name: /取消置顶|unpin/i });
80
80
  if ((await unpinOption.count()) > 0) {
81
81
  await unpinOption.click();
82
82
  await this.page.waitForTimeout(500);
@@ -95,7 +95,7 @@ Given('该 Agent Group 已被置顶', async function (this: CustomWorld) {
95
95
  if ((await pinIcon.count()) === 0) {
96
96
  await targetItem.click({ button: 'right' });
97
97
  await this.page.waitForTimeout(300);
98
- const pinOption = this.page.getByRole('menuitem', { name: /置顶|Pin/i });
98
+ const pinOption = this.page.getByRole('menuitem', { name: /置顶|pin/i });
99
99
  if ((await pinOption.count()) > 0) {
100
100
  await pinOption.click();
101
101
  await this.page.waitForTimeout(500);
@@ -85,6 +85,7 @@ Before(async function (this: CustomWorld, { pickle }) {
85
85
  tag.name.startsWith('@COMMUNITY-') ||
86
86
  tag.name.startsWith('@AGENT-') ||
87
87
  tag.name.startsWith('@HOME-') ||
88
+ tag.name.startsWith('@PAGE-') ||
88
89
  tag.name.startsWith('@ROUTES-'),
89
90
  );
90
91
  console.log(`\n📝 Running: ${pickle.name}${testId ? ` (${testId.name.replace('@', '')})` : ''}`);
@@ -106,6 +107,7 @@ After(async function (this: CustomWorld, { pickle, result }) {
106
107
  tag.name.startsWith('@COMMUNITY-') ||
107
108
  tag.name.startsWith('@AGENT-') ||
108
109
  tag.name.startsWith('@HOME-') ||
110
+ tag.name.startsWith('@PAGE-') ||
109
111
  tag.name.startsWith('@ROUTES-'),
110
112
  )
111
113
  ?.name.replace('@', '');