@lobehub/lobehub 2.0.0-next.270 → 2.0.0-next.272
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.
- package/.eslintrc.js +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/e2e/src/features/home/starter.feature +34 -0
- package/e2e/src/steps/community/interactions.steps.ts +37 -14
- package/e2e/src/steps/home/starter.steps.ts +216 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx +7 -17
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/AgentTool.tsx +8 -487
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/AgentTool.tsx +6 -388
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +1 -2
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +0 -1
- package/src/features/ProfileEditor/AgentTool.tsx +549 -0
- package/src/features/ProfileEditor/PluginTag.tsx +213 -0
- package/src/features/ProfileEditor/index.ts +2 -0
- package/src/libs/better-auth/define-config.ts +7 -1
- package/src/store/home/slices/homeInput/action.ts +6 -3
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/PluginTag.tsx +0 -195
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/PluginTag.tsx +0 -180
package/.eslintrc.js
CHANGED
|
@@ -22,6 +22,7 @@ config.rules['unicorn/prefer-query-selector'] = 0;
|
|
|
22
22
|
config.rules['unicorn/no-array-callback-reference'] = 0;
|
|
23
23
|
// FIXME: Linting error in src/app/[variants]/(main)/chat/features/Migration/DBReader.ts, the fundamental solution should be upgrading typescript-eslint
|
|
24
24
|
config.rules['@typescript-eslint/no-useless-constructor'] = 0;
|
|
25
|
+
config.rules['@next/next/no-img-element'] = 0;
|
|
25
26
|
|
|
26
27
|
config.overrides = [
|
|
27
28
|
{
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.272](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.271...v2.0.0-next.272)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-13**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Refresh sidebar after sendAsGroup and add E2E tests.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Refresh sidebar after sendAsGroup and add E2E tests, closes [#11450](https://github.com/lobehub/lobe-chat/issues/11450) ([8376a80](https://github.com/lobehub/lobe-chat/commit/8376a80))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.271](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.270...v2.0.0-next.271)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2026-01-12**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Improve baseline alignment for tool items.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Improve baseline alignment for tool items, closes [#11447](https://github.com/lobehub/lobe-chat/issues/11447) ([be8dddd](https://github.com/lobehub/lobe-chat/commit/be8dddd))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.270](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.269...v2.0.0-next.270)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2026-01-12**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"fixes": [
|
|
5
|
+
"Refresh sidebar after sendAsGroup and add E2E tests."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2026-01-13",
|
|
9
|
+
"version": "2.0.0-next.272"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"children": {
|
|
13
|
+
"features": [
|
|
14
|
+
"Improve baseline alignment for tool items."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"date": "2026-01-12",
|
|
18
|
+
"version": "2.0.0-next.271"
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"children": {},
|
|
4
22
|
"date": "2026-01-12",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@journey @home @starter
|
|
2
|
+
Feature: Home 页面 Starter 快捷创建功能
|
|
3
|
+
作为用户,我希望在 Home 页面可以通过 Starter 快捷创建 Agent 或 Group,返回首页后在侧边栏中看到它
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given 用户已登录系统
|
|
7
|
+
|
|
8
|
+
# ============================================
|
|
9
|
+
# 创建 Agent 后侧边栏刷新
|
|
10
|
+
# ============================================
|
|
11
|
+
|
|
12
|
+
@HOME-STARTER-AGENT-001 @P0
|
|
13
|
+
Scenario: 通过 Home 页面创建 Agent 后返回首页侧边栏应显示新创建的 Agent
|
|
14
|
+
Given 用户在 Home 页面
|
|
15
|
+
When 用户点击创建 Agent 按钮
|
|
16
|
+
And 用户在输入框中输入 "E2E Test Agent"
|
|
17
|
+
And 用户按 Enter 发送
|
|
18
|
+
Then 页面应该跳转到 Agent 的 profile 页面
|
|
19
|
+
When 用户返回 Home 页面
|
|
20
|
+
Then 新创建的 Agent 应该在侧边栏中显示
|
|
21
|
+
|
|
22
|
+
# ============================================
|
|
23
|
+
# 创建 Group 后侧边栏刷新
|
|
24
|
+
# ============================================
|
|
25
|
+
|
|
26
|
+
@HOME-STARTER-GROUP-001 @P0
|
|
27
|
+
Scenario: 通过 Home 页面创建 Group 后返回首页侧边栏应显示新创建的 Group
|
|
28
|
+
Given 用户在 Home 页面
|
|
29
|
+
When 用户点击创建 Group 按钮
|
|
30
|
+
And 用户在输入框中输入 "E2E Test Group"
|
|
31
|
+
And 用户按 Enter 发送
|
|
32
|
+
Then 页面应该跳转到 Group 的 profile 页面
|
|
33
|
+
When 用户返回 Home 页面
|
|
34
|
+
Then 新创建的 Group 应该在侧边栏中显示
|
|
@@ -55,12 +55,13 @@ When('I click on a category in the category menu', async function (this: CustomW
|
|
|
55
55
|
// Wait for categories to be visible
|
|
56
56
|
await categoryItems.first().waitFor({ state: 'visible', timeout: 30_000 });
|
|
57
57
|
|
|
58
|
-
// Click the
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// Click the third category (skip "Discover" at index 0 and "All" at index 1)
|
|
59
|
+
// This should select the first actual category filter like "Academic"
|
|
60
|
+
const targetCategory = categoryItems.nth(2);
|
|
61
|
+
await targetCategory.click();
|
|
61
62
|
|
|
62
63
|
// Store the category for later verification
|
|
63
|
-
const categoryText = await
|
|
64
|
+
const categoryText = await targetCategory.textContent();
|
|
64
65
|
this.testContext.selectedCategory = categoryText?.trim();
|
|
65
66
|
});
|
|
66
67
|
|
|
@@ -94,12 +95,13 @@ When('I click on a category in the category filter', async function (this: Custo
|
|
|
94
95
|
// Wait for categories to be visible
|
|
95
96
|
await categoryItems.first().waitFor({ state: 'visible', timeout: 30_000 });
|
|
96
97
|
|
|
97
|
-
// Click the
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
// Click the third category (skip "Discover" at index 0 and "All" at index 1)
|
|
99
|
+
// This should select the first actual category filter
|
|
100
|
+
const targetCategory = categoryItems.nth(2);
|
|
101
|
+
await targetCategory.click();
|
|
100
102
|
|
|
101
103
|
// Store the category for later verification
|
|
102
|
-
const categoryText = await
|
|
104
|
+
const categoryText = await targetCategory.textContent();
|
|
103
105
|
this.testContext.selectedCategory = categoryText?.trim();
|
|
104
106
|
});
|
|
105
107
|
|
|
@@ -285,10 +287,15 @@ When(
|
|
|
285
287
|
|
|
286
288
|
// Try to find "more" link near MCP-related content
|
|
287
289
|
const mcpSection = this.page.locator('section:has-text("MCP"), div:has-text("MCP Tools")');
|
|
288
|
-
const mcpSectionVisible = await mcpSection
|
|
290
|
+
const mcpSectionVisible = await mcpSection
|
|
291
|
+
.first()
|
|
292
|
+
.isVisible()
|
|
293
|
+
.catch(() => false);
|
|
289
294
|
|
|
290
295
|
if (mcpSectionVisible) {
|
|
291
|
-
const moreLinkInSection = mcpSection.locator(
|
|
296
|
+
const moreLinkInSection = mcpSection.locator(
|
|
297
|
+
`a:has-text("${linkText}"), button:has-text("${linkText}")`,
|
|
298
|
+
);
|
|
292
299
|
if ((await moreLinkInSection.count()) > 0) {
|
|
293
300
|
await moreLinkInSection.first().click();
|
|
294
301
|
return;
|
|
@@ -297,7 +304,9 @@ When(
|
|
|
297
304
|
|
|
298
305
|
// Fallback: click on MCP in the sidebar navigation
|
|
299
306
|
console.log(' 📍 Fallback: clicking MCP in sidebar');
|
|
300
|
-
const mcpNavItem = this.page
|
|
307
|
+
const mcpNavItem = this.page
|
|
308
|
+
.locator('nav a:has-text("MCP"), [class*="nav"] a:has-text("MCP")')
|
|
309
|
+
.first();
|
|
301
310
|
if (await mcpNavItem.isVisible().catch(() => false)) {
|
|
302
311
|
await mcpNavItem.click();
|
|
303
312
|
return;
|
|
@@ -363,9 +372,19 @@ Then(
|
|
|
363
372
|
|
|
364
373
|
Then('the URL should contain the category parameter', async function (this: CustomWorld) {
|
|
365
374
|
const currentUrl = this.page.url();
|
|
375
|
+
console.log(` 📍 Current URL: ${currentUrl}`);
|
|
376
|
+
console.log(` 📍 Selected category: ${this.testContext.selectedCategory}`);
|
|
377
|
+
|
|
366
378
|
// Check if URL contains a category-related parameter
|
|
379
|
+
// The URL format is: /community/assistant?category=xxx
|
|
380
|
+
const hasCategory =
|
|
381
|
+
currentUrl.includes('category=') ||
|
|
382
|
+
currentUrl.includes('tag=') ||
|
|
383
|
+
// For path-based routing like /community/assistant/category-name
|
|
384
|
+
/\/community\/assistant\/[^/?]+/.test(currentUrl);
|
|
385
|
+
|
|
367
386
|
expect(
|
|
368
|
-
|
|
387
|
+
hasCategory,
|
|
369
388
|
`Expected URL to contain category parameter, but got: ${currentUrl}`,
|
|
370
389
|
).toBeTruthy();
|
|
371
390
|
});
|
|
@@ -383,7 +402,9 @@ Then('I should see different assistant cards', async function (this: CustomWorld
|
|
|
383
402
|
|
|
384
403
|
// If we used infinite scroll, check that we have cards (might be same or more)
|
|
385
404
|
if (this.testContext.usedInfiniteScroll) {
|
|
386
|
-
console.log(
|
|
405
|
+
console.log(
|
|
406
|
+
` 📍 Used infinite scroll, initial count was: ${this.testContext.initialCardCount}`,
|
|
407
|
+
);
|
|
387
408
|
expect(currentCount).toBeGreaterThan(0);
|
|
388
409
|
} else {
|
|
389
410
|
expect(currentCount).toBeGreaterThan(0);
|
|
@@ -463,7 +484,9 @@ Then('I should see the model detail content', async function (this: CustomWorld)
|
|
|
463
484
|
|
|
464
485
|
// Model detail page should have tabs like "Overview", "Model Parameters"
|
|
465
486
|
// Wait for these specific elements to appear
|
|
466
|
-
const modelTabs = this.page.locator(
|
|
487
|
+
const modelTabs = this.page.locator(
|
|
488
|
+
'text=/Overview|Model Parameters|Related Recommendations|Configuration Guide/',
|
|
489
|
+
);
|
|
467
490
|
|
|
468
491
|
console.log(' 📍 Waiting for model detail content to load...');
|
|
469
492
|
await expect(modelTabs.first()).toBeVisible({ timeout: 30_000 });
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home Starter Steps
|
|
3
|
+
*
|
|
4
|
+
* Step definitions for Home page Starter E2E tests
|
|
5
|
+
* - Create Agent from Home input
|
|
6
|
+
* - Create Group from Home input
|
|
7
|
+
* - Verify Agent/Group appears in sidebar after returning to Home
|
|
8
|
+
*/
|
|
9
|
+
import { Given, Then, When } from '@cucumber/cucumber';
|
|
10
|
+
import { expect } from '@playwright/test';
|
|
11
|
+
|
|
12
|
+
import { llmMockManager, presetResponses } from '../../mocks/llm';
|
|
13
|
+
import { CustomWorld, WAIT_TIMEOUT } from '../../support/world';
|
|
14
|
+
|
|
15
|
+
// Store created IDs for verification
|
|
16
|
+
let createdAgentId: string | null = null;
|
|
17
|
+
let createdGroupId: string | null = null;
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// Given Steps
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
Given('用户在 Home 页面', async function (this: CustomWorld) {
|
|
24
|
+
console.log(' 📍 Step: 设置 LLM mock...');
|
|
25
|
+
// Setup LLM mock before navigation (for agent/group builder message)
|
|
26
|
+
llmMockManager.setResponse('E2E Test Agent', presetResponses.greeting);
|
|
27
|
+
llmMockManager.setResponse('E2E Test Group', presetResponses.greeting);
|
|
28
|
+
await llmMockManager.setup(this.page);
|
|
29
|
+
|
|
30
|
+
console.log(' 📍 Step: 导航到 Home 页面...');
|
|
31
|
+
await this.page.goto('/');
|
|
32
|
+
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
|
33
|
+
await this.page.waitForTimeout(1000);
|
|
34
|
+
|
|
35
|
+
// Reset IDs for each test
|
|
36
|
+
createdAgentId = null;
|
|
37
|
+
createdGroupId = null;
|
|
38
|
+
|
|
39
|
+
console.log(' ✅ 已进入 Home 页面');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// When Steps
|
|
44
|
+
// ============================================
|
|
45
|
+
|
|
46
|
+
When('用户点击创建 Agent 按钮', async function (this: CustomWorld) {
|
|
47
|
+
console.log(' 📍 Step: 点击创建 Agent 按钮...');
|
|
48
|
+
|
|
49
|
+
// Find the "Create Agent" button by text (supports both English and Chinese)
|
|
50
|
+
const createAgentButton = this.page
|
|
51
|
+
.getByRole('button', { name: /create agent|创建智能体/i })
|
|
52
|
+
.first();
|
|
53
|
+
|
|
54
|
+
await expect(createAgentButton).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
55
|
+
await createAgentButton.click();
|
|
56
|
+
await this.page.waitForTimeout(500);
|
|
57
|
+
|
|
58
|
+
console.log(' ✅ 已点击创建 Agent 按钮');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
When('用户点击创建 Group 按钮', async function (this: CustomWorld) {
|
|
62
|
+
console.log(' 📍 Step: 点击创建 Group 按钮...');
|
|
63
|
+
|
|
64
|
+
// Find the "Create Group" button by text (supports both English and Chinese)
|
|
65
|
+
const createGroupButton = this.page
|
|
66
|
+
.getByRole('button', { name: /create group|创建群组/i })
|
|
67
|
+
.first();
|
|
68
|
+
|
|
69
|
+
await expect(createGroupButton).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
70
|
+
await createGroupButton.click();
|
|
71
|
+
await this.page.waitForTimeout(500);
|
|
72
|
+
|
|
73
|
+
console.log(' ✅ 已点击创建 Group 按钮');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
When('用户在输入框中输入 {string}', async function (this: CustomWorld, message: string) {
|
|
77
|
+
console.log(` 📍 Step: 在输入框中输入 "${message}"...`);
|
|
78
|
+
|
|
79
|
+
// The chat input is a contenteditable editor, need to click first then type
|
|
80
|
+
const chatInputContainer = this.page.locator('[data-testid="chat-input"]').first();
|
|
81
|
+
|
|
82
|
+
// If data-testid not found, try alternative selectors
|
|
83
|
+
let inputFound = false;
|
|
84
|
+
if ((await chatInputContainer.count()) > 0) {
|
|
85
|
+
await chatInputContainer.click();
|
|
86
|
+
inputFound = true;
|
|
87
|
+
} else {
|
|
88
|
+
// Try to find the editor by its contenteditable attribute
|
|
89
|
+
const editor = this.page.locator('[contenteditable="true"]').first();
|
|
90
|
+
if ((await editor.count()) > 0) {
|
|
91
|
+
await editor.click();
|
|
92
|
+
inputFound = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!inputFound) {
|
|
97
|
+
throw new Error('Could not find chat input');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await this.page.waitForTimeout(300);
|
|
101
|
+
await this.page.keyboard.type(message, { delay: 30 });
|
|
102
|
+
|
|
103
|
+
console.log(` ✅ 已输入 "${message}"`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
When('用户按 Enter 发送', async function (this: CustomWorld) {
|
|
107
|
+
console.log(' 📍 Step: 按 Enter 发送...');
|
|
108
|
+
|
|
109
|
+
// Listen for navigation to capture the agent/group ID
|
|
110
|
+
const navigationPromise = this.page.waitForURL(/\/(agent|group)\/.*\/profile/, {
|
|
111
|
+
timeout: 30_000,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await this.page.keyboard.press('Enter');
|
|
115
|
+
|
|
116
|
+
// Wait for navigation to profile page
|
|
117
|
+
await navigationPromise;
|
|
118
|
+
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
|
119
|
+
|
|
120
|
+
// Extract agent/group ID from URL
|
|
121
|
+
const currentUrl = this.page.url();
|
|
122
|
+
|
|
123
|
+
const agentMatch = currentUrl.match(/\/agent\/([^/]+)/);
|
|
124
|
+
if (agentMatch) {
|
|
125
|
+
createdAgentId = agentMatch[1];
|
|
126
|
+
console.log(` 📍 Created agent ID: ${createdAgentId}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const groupMatch = currentUrl.match(/\/group\/([^/]+)/);
|
|
130
|
+
if (groupMatch) {
|
|
131
|
+
createdGroupId = groupMatch[1];
|
|
132
|
+
console.log(` 📍 Created group ID: ${createdGroupId}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(' ✅ 已发送消息');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
When('用户返回 Home 页面', async function (this: CustomWorld) {
|
|
139
|
+
console.log(' 📍 Step: 返回 Home 页面...');
|
|
140
|
+
|
|
141
|
+
await this.page.goto('/');
|
|
142
|
+
await this.page.waitForLoadState('networkidle', { timeout: 15_000 });
|
|
143
|
+
await this.page.waitForTimeout(1000);
|
|
144
|
+
|
|
145
|
+
console.log(' ✅ 已返回 Home 页面');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// Then Steps
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
Then('页面应该跳转到 Agent 的 profile 页面', async function (this: CustomWorld) {
|
|
153
|
+
console.log(' 📍 Step: 验证页面跳转到 Agent profile 页面...');
|
|
154
|
+
|
|
155
|
+
// Check current URL matches /agent/{id}/profile pattern
|
|
156
|
+
const currentUrl = this.page.url();
|
|
157
|
+
expect(currentUrl).toMatch(/\/agent\/[^/]+\/profile/);
|
|
158
|
+
|
|
159
|
+
console.log(' ✅ 已跳转到 Agent profile 页面');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
Then('页面应该跳转到 Group 的 profile 页面', async function (this: CustomWorld) {
|
|
163
|
+
console.log(' 📍 Step: 验证页面跳转到 Group profile 页面...');
|
|
164
|
+
|
|
165
|
+
// Check current URL matches /group/{id}/profile pattern
|
|
166
|
+
const currentUrl = this.page.url();
|
|
167
|
+
expect(currentUrl).toMatch(/\/group\/[^/]+\/profile/);
|
|
168
|
+
|
|
169
|
+
console.log(' ✅ 已跳转到 Group profile 页面');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
Then('新创建的 Agent 应该在侧边栏中显示', async function (this: CustomWorld) {
|
|
173
|
+
console.log(' 📍 Step: 验证 Agent 在侧边栏中显示...');
|
|
174
|
+
|
|
175
|
+
// Wait for sidebar to be visible and data to load
|
|
176
|
+
await this.page.waitForTimeout(1500);
|
|
177
|
+
|
|
178
|
+
// Check if the agent appears in sidebar by its link (primary assertion)
|
|
179
|
+
// This proves that refreshAgentList() was called and the sidebar was updated
|
|
180
|
+
if (!createdAgentId) {
|
|
181
|
+
throw new Error('Agent ID was not captured during creation');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const agentLink = this.page.locator(`a[href="/agent/${createdAgentId}"]`).first();
|
|
185
|
+
await expect(agentLink).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
186
|
+
console.log(` ✅ 找到 Agent 链接: /agent/${createdAgentId}`);
|
|
187
|
+
|
|
188
|
+
// Get the aria-label or text content to verify it's the correct agent
|
|
189
|
+
const ariaLabel = await agentLink.getAttribute('aria-label');
|
|
190
|
+
console.log(` 📍 Agent aria-label: ${ariaLabel}`);
|
|
191
|
+
|
|
192
|
+
console.log(' ✅ Agent 已在侧边栏中显示');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
Then('新创建的 Group 应该在侧边栏中显示', async function (this: CustomWorld) {
|
|
196
|
+
console.log(' 📍 Step: 验证 Group 在侧边栏中显示...');
|
|
197
|
+
|
|
198
|
+
// Wait for sidebar to be visible and data to load
|
|
199
|
+
await this.page.waitForTimeout(1500);
|
|
200
|
+
|
|
201
|
+
// Check if the group appears in sidebar by its link (primary assertion)
|
|
202
|
+
// This proves that refreshAgentList() was called and the sidebar was updated
|
|
203
|
+
if (!createdGroupId) {
|
|
204
|
+
throw new Error('Group ID was not captured during creation');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const groupLink = this.page.locator(`a[href="/group/${createdGroupId}"]`).first();
|
|
208
|
+
await expect(groupLink).toBeVisible({ timeout: WAIT_TIMEOUT });
|
|
209
|
+
console.log(` ✅ 找到 Group 链接: /group/${createdGroupId}`);
|
|
210
|
+
|
|
211
|
+
// Get the aria-label or text content to verify it's the correct group
|
|
212
|
+
const ariaLabel = await groupLink.getAttribute('aria-label');
|
|
213
|
+
console.log(` 📍 Group aria-label: ${ariaLabel}`);
|
|
214
|
+
|
|
215
|
+
console.log(' ✅ Group 已在侧边栏中显示');
|
|
216
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.272",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Switch } from 'antd';
|
|
3
|
+
import { type DropdownMenuCheckboxItem } from '@lobehub/ui';
|
|
5
4
|
import { useMemo } from 'react';
|
|
6
5
|
import { useTranslation } from 'react-i18next';
|
|
7
6
|
|
|
8
7
|
import { useGlobalStore } from '@/store/global';
|
|
9
8
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
10
9
|
|
|
11
|
-
export const useMenu = (): { menuItems:
|
|
10
|
+
export const useMenu = (): { menuItems: DropdownMenuCheckboxItem[] } => {
|
|
12
11
|
const { t } = useTranslation('chat');
|
|
13
12
|
|
|
14
13
|
const [wideScreen, toggleWideScreen] = useGlobalStore((s) => [
|
|
@@ -16,23 +15,14 @@ export const useMenu = (): { menuItems: any[] } => {
|
|
|
16
15
|
s.toggleWideScreen,
|
|
17
16
|
]);
|
|
18
17
|
|
|
19
|
-
const menuItems = useMemo(
|
|
18
|
+
const menuItems = useMemo<DropdownMenuCheckboxItem[]>(
|
|
20
19
|
() => [
|
|
21
20
|
{
|
|
21
|
+
checked: wideScreen,
|
|
22
22
|
key: 'full-width',
|
|
23
|
-
label: (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<Switch
|
|
27
|
-
checked={wideScreen}
|
|
28
|
-
onChange={toggleWideScreen}
|
|
29
|
-
onClick={(checked, event) => {
|
|
30
|
-
event.stopPropagation();
|
|
31
|
-
}}
|
|
32
|
-
size="small"
|
|
33
|
-
/>
|
|
34
|
-
</Flexbox>
|
|
35
|
-
),
|
|
23
|
+
label: t('viewMode.fullWidth'),
|
|
24
|
+
onCheckedChange: toggleWideScreen,
|
|
25
|
+
type: 'checkbox',
|
|
36
26
|
},
|
|
37
27
|
],
|
|
38
28
|
[t, wideScreen, toggleWideScreen],
|