@lobehub/lobehub 2.0.0-next.311 → 2.0.0-next.312

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 (79) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +12 -0
  3. package/e2e/README.md +1 -1
  4. package/e2e/src/features/community/detail-pages.feature +2 -2
  5. package/e2e/src/features/community/interactions.feature +5 -5
  6. package/e2e/src/features/community/smoke.feature +1 -1
  7. package/e2e/src/steps/community/detail-pages.steps.ts +6 -4
  8. package/e2e/src/steps/community/interactions.steps.ts +3 -3
  9. package/package.json +1 -1
  10. package/packages/builtin-tool-agent-builder/src/systemRole.ts +9 -0
  11. package/public/favicon-32x-32-error.ico +0 -0
  12. package/public/favicon-32x32-done-dev.ico +0 -0
  13. package/public/favicon-32x32-done.ico +0 -0
  14. package/public/favicon-32x32-error-dev.ico +0 -0
  15. package/public/favicon-32x32-progress-dev.ico +0 -0
  16. package/public/favicon-32x32-progress.ico +0 -0
  17. package/public/favicon-done-dev.ico +0 -0
  18. package/public/favicon-done.ico +0 -0
  19. package/public/favicon-error-dev.ico +0 -0
  20. package/public/favicon-error.ico +0 -0
  21. package/public/favicon-progress-dev.ico +0 -0
  22. package/public/favicon-progress.ico +0 -0
  23. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +1 -1
  24. package/src/app/[variants]/(main)/community/(detail)/_layout/Header.tsx +15 -3
  25. package/src/app/[variants]/(main)/community/(detail)/{assistant/features/Details/SystemRole → agent/features/Details/Overview}/TagList.tsx +1 -1
  26. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Related/index.tsx +2 -2
  27. package/src/app/[variants]/(main)/community/(detail)/{assistant/features/Details/Overview → agent/features/Details/SystemRole}/TagList.tsx +1 -1
  28. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/index.tsx +1 -1
  29. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Header.tsx +2 -2
  30. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/AddAgent.tsx +1 -1
  31. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/index.tsx +1 -1
  32. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/index.tsx +2 -2
  33. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/StatusPage/index.tsx +2 -2
  34. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +2 -2
  35. package/src/app/[variants]/(main)/community/(detail)/user/features/UserFavoriteAgents.tsx +1 -1
  36. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -2
  37. package/src/app/[variants]/(main)/community/(list)/(home)/loading.tsx +1 -1
  38. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/Client.tsx +5 -1
  39. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/index.tsx +1 -1
  40. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/Item.tsx +1 -1
  41. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/MarketSourceSwitch.tsx +1 -1
  42. package/src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx +2 -2
  43. package/src/app/[variants]/(main)/home/features/CommunityAgents/List.tsx +1 -1
  44. package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +1 -1
  45. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -1
  46. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +6 -6
  47. package/src/app/[variants]/router/desktopRouter.config.tsx +8 -8
  48. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  49. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  50. package/src/features/Electron/navigation/routeMetadata.ts +1 -1
  51. package/src/layout/GlobalProvider/FaviconProvider.tsx +92 -0
  52. package/src/layout/GlobalProvider/index.tsx +15 -11
  53. package/src/libs/next/config/define-config.ts +1 -1
  54. package/src/server/sitemap.test.ts +5 -5
  55. package/src/server/sitemap.ts +3 -3
  56. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/DetailProvider.tsx +0 -0
  57. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Block.tsx +0 -0
  58. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Knowledge.tsx +0 -0
  59. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/KnowledgeItem.tsx +0 -0
  60. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/PluginItem.tsx +0 -0
  61. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Plugins.tsx +0 -0
  62. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/index.tsx +0 -0
  63. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Nav.tsx +0 -0
  64. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/index.tsx +0 -0
  65. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Versions/index.tsx +0 -0
  66. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/index.tsx +0 -0
  67. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/Item.tsx +0 -0
  68. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Summary/index.tsx +0 -0
  69. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/TocList/index.tsx +0 -0
  70. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/index.tsx +0 -0
  71. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/index.tsx +0 -0
  72. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/loading.tsx +0 -0
  73. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/index.tsx +0 -0
  74. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/style.ts +0 -0
  75. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/useCategory.tsx +0 -0
  76. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/TokenTag.tsx +0 -0
  77. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/index.tsx +0 -0
  78. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/index.tsx +0 -0
  79. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/loading.tsx +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.312](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.311...v2.0.0-next.312)
6
+
7
+ <sup>Released on **2026-01-19**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Change the /community/assistant to /agent routes.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: Improve the agentbuilder systemRole.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### Code refactoring
23
+
24
+ - **misc**: Change the /community/assistant to /agent routes, closes [#11606](https://github.com/lobehub/lobe-chat/issues/11606) ([7f004c5](https://github.com/lobehub/lobe-chat/commit/7f004c5))
25
+
26
+ #### What's improved
27
+
28
+ - **misc**: Improve the agentbuilder systemRole, closes [#11608](https://github.com/lobehub/lobe-chat/issues/11608) ([2f032d4](https://github.com/lobehub/lobe-chat/commit/2f032d4))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
5
38
  ## [Version 2.0.0-next.311](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.310...v2.0.0-next.311)
6
39
 
7
40
  <sup>Released on **2026-01-19**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Change the /community/assistant to /agent routes."
6
+ ],
7
+ "features": [
8
+ "Improve the agentbuilder systemRole."
9
+ ]
10
+ },
11
+ "date": "2026-01-19",
12
+ "version": "2.0.0-next.312"
13
+ },
2
14
  {
3
15
  "children": {
4
16
  "improvements": [
package/e2e/README.md CHANGED
@@ -90,7 +90,7 @@ Feature: Community Smoke Tests
90
90
 
91
91
  @COMMUNITY-SMOKE-001 @P0
92
92
  Scenario: Load community assistant list page
93
- Given I navigate to "/community/assistant"
93
+ Given I navigate to "/community/agent"
94
94
  Then the page should load without errors
95
95
  And I should see the page body
96
96
  And I should see the search bar
@@ -11,7 +11,7 @@ Feature: Discover Detail Pages
11
11
 
12
12
  @COMMUNITY-DETAIL-001 @P1
13
13
  Scenario: Load assistant detail page and verify content
14
- Given I navigate to "/community/assistant"
14
+ Given I navigate to "/community/agent"
15
15
  And I wait for the page to fully load
16
16
  When I click on the first assistant card
17
17
  Then I should be on an assistant detail page
@@ -22,7 +22,7 @@ Feature: Discover Detail Pages
22
22
 
23
23
  @COMMUNITY-DETAIL-002 @P1
24
24
  Scenario: Navigate back from assistant detail page
25
- Given I navigate to "/community/assistant"
25
+ Given I navigate to "/community/agent"
26
26
  And I wait for the page to fully load
27
27
  And I click on the first assistant card
28
28
  When I click the back button
@@ -11,14 +11,14 @@ Feature: Discover Interactions
11
11
 
12
12
  @COMMUNITY-INTERACT-001 @P1
13
13
  Scenario: Search for assistants
14
- Given I navigate to "/community/assistant"
14
+ Given I navigate to "/community/agent"
15
15
  When I type "developer" in the search bar
16
16
  And I wait for the search results to load
17
17
  Then I should see filtered assistant cards
18
18
 
19
19
  @COMMUNITY-INTERACT-002 @P1
20
20
  Scenario: Filter assistants by category
21
- Given I navigate to "/community/assistant"
21
+ Given I navigate to "/community/agent"
22
22
  When I click on a category in the category menu
23
23
  And I wait for the filtered results to load
24
24
  Then I should see assistant cards filtered by the selected category
@@ -26,7 +26,7 @@ Feature: Discover Interactions
26
26
 
27
27
  @COMMUNITY-INTERACT-003 @P1
28
28
  Scenario: Navigate to next page of assistants
29
- Given I navigate to "/community/assistant"
29
+ Given I navigate to "/community/agent"
30
30
  When I click the next page button
31
31
  And I wait for the next page to load
32
32
  Then I should see different assistant cards
@@ -34,7 +34,7 @@ Feature: Discover Interactions
34
34
 
35
35
  @COMMUNITY-INTERACT-004 @P1
36
36
  Scenario: Navigate to assistant detail page
37
- Given I navigate to "/community/assistant"
37
+ Given I navigate to "/community/agent"
38
38
  When I click on the first assistant card
39
39
  Then I should be navigated to the assistant detail page
40
40
  And I should see the assistant detail content
@@ -95,7 +95,7 @@ Feature: Discover Interactions
95
95
  Scenario: Navigate from home to assistant list
96
96
  Given I navigate to "/community"
97
97
  When I click on the "more" link in the featured assistants section
98
- Then I should be navigated to "/community/assistant"
98
+ Then I should be navigated to "/community/agent"
99
99
  And I should see the page body
100
100
 
101
101
  @COMMUNITY-INTERACT-011 @P1
@@ -12,7 +12,7 @@ Feature: Community Smoke Tests
12
12
 
13
13
  @COMMUNITY-SMOKE-002 @P0
14
14
  Scenario: Load Assistant List Page
15
- Given I navigate to "/community/assistant"
15
+ Given I navigate to "/community/agent"
16
16
  Then the page should load without errors
17
17
  And I should see the page body
18
18
  And I should see the search bar
@@ -8,7 +8,7 @@ import { CustomWorld } from '../../support/world';
8
8
  // ============================================
9
9
 
10
10
  Given('I wait for the page to fully load', async function (this: CustomWorld) {
11
- // Use domcontentloaded instead of networkidle to avoid hanging on persistent connections
11
+ // Use domcontentloaded instead of networkidle to avoid hanging on persistent connections
12
12
  await this.page.waitForLoadState('domcontentloaded', { timeout: 10_000 });
13
13
  // Short wait for React hydration
14
14
  await this.page.waitForTimeout(1000);
@@ -135,9 +135,9 @@ Then('I should be on the assistant list page', async function (this: CustomWorld
135
135
 
136
136
  const currentUrl = this.page.url();
137
137
  // Check if URL is assistant list (not detail page) or community home
138
- // After back navigation, URL should be /community/assistant or /community
138
+ // After back navigation, URL should be /community/agent or /community
139
139
  const isListPage =
140
- (currentUrl.includes('/community/assistant') &&
140
+ (currentUrl.includes('/community/agent') &&
141
141
  !/\/community\/assistant\/[\dA-Za-z-]+$/.test(currentUrl)) ||
142
142
  currentUrl.endsWith('/community') ||
143
143
  currentUrl.includes('/community#');
@@ -176,7 +176,9 @@ Then('I should see the model description', async function (this: CustomWorld) {
176
176
 
177
177
  // Model detail page shows description below the title, it might be a placeholder like "model.description"
178
178
  // or actual content. Just verify the page structure is correct.
179
- const descriptionArea = this.page.locator('main, article, [class*="detail"], [class*="content"]').first();
179
+ const descriptionArea = this.page
180
+ .locator('main, article, [class*="detail"], [class*="content"]')
181
+ .first();
180
182
  const isVisible = await descriptionArea.isVisible().catch(() => false);
181
183
 
182
184
  // Pass if any content area is visible - the description might be a placeholder
@@ -376,11 +376,11 @@ Then('the URL should contain the category parameter', async function (this: Cust
376
376
  console.log(` 📍 Selected category: ${this.testContext.selectedCategory}`);
377
377
 
378
378
  // Check if URL contains a category-related parameter
379
- // The URL format is: /community/assistant?category=xxx
379
+ // The URL format is: /community/agent?category=xxx
380
380
  const hasCategory =
381
381
  currentUrl.includes('category=') ||
382
382
  currentUrl.includes('tag=') ||
383
- // For path-based routing like /community/assistant/category-name
383
+ // For path-based routing like /community/agent/category-name
384
384
  /\/community\/assistant\/[^/?]+/.test(currentUrl);
385
385
 
386
386
  expect(
@@ -418,7 +418,7 @@ Then('the URL should contain the page parameter', async function (this: CustomWo
418
418
  if (this.testContext.usedInfiniteScroll) {
419
419
  console.log(' 📍 Used infinite scroll, page parameter not expected');
420
420
  // Just verify we're still on the assistant page
421
- expect(currentUrl.includes('/community/assistant')).toBeTruthy();
421
+ expect(currentUrl.includes('/community/agent')).toBeTruthy();
422
422
  return;
423
423
  }
424
424
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.311",
3
+ "version": "2.0.0-next.312",
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",
@@ -86,6 +86,7 @@ Always adapt to user's language. Use natural descriptions, not raw field names.
86
86
  5. **Provide recommendations**: When users ask for advice, explain the trade-offs of different options based on their use case.
87
87
  6. **Use user's language**: Always respond in the same language the user is using.
88
88
  7. **Keep it simple**: Focus on core settings. Don't overwhelm users with advanced options unless they ask.
89
+ 8. **Install plugins one by one**: When multiple plugins need to be installed, install them sequentially one at a time instead of batching. This ensures better error handling, allows users to understand each plugin's purpose, and makes it easier to troubleshoot if something goes wrong.
89
90
  </guidelines>
90
91
 
91
92
  <configuration_knowledge>
@@ -202,6 +203,14 @@ Action: Use updateConfig with { config: { params: { temperature: 0.7 } } }
202
203
 
203
204
  User: "我想调整对话配置" / "I want to configure chat settings"
204
205
  Action: Explain the available chatConfig options and help them configure as needed.
206
+
207
+ User: "帮我安装网页浏览和图片生成这两个插件" / "Install web browsing and image generation plugins for me"
208
+ Action: Install plugins one by one:
209
+ 1. First, use installPlugin to install "lobe-web-browsing", explain what it does
210
+ 2. Wait for confirmation of success
211
+ 3. Then, use installPlugin to install "lobe-image-generation", explain what it does
212
+ 4. Confirm both plugins are installed successfully
213
+ This sequential approach ensures each plugin is properly installed and allows the user to understand each tool's purpose.
205
214
  </examples>
206
215
 
207
216
  <response_format>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -19,7 +19,7 @@ const PublishResultModal = memo<PublishResultModalProps>(({ identifier, onCancel
19
19
 
20
20
  const handleGoToMarket = () => {
21
21
  if (identifier) {
22
- navigate(`/community/assistant/${identifier}`);
22
+ navigate(`/community/agent/${identifier}`);
23
23
  }
24
24
  onCancel();
25
25
  };
@@ -18,11 +18,23 @@ const Header = memo(() => {
18
18
  const navigate = useNavigate();
19
19
 
20
20
  const handleGoBack = () => {
21
- // Extract the path segment (assistant, model, provider, mcp)
21
+ // Extract the path segment (agent, model, provider, mcp, group_agent, user)
22
22
  const path = location.pathname.split('/').filter(Boolean);
23
- if (path[1] && path[1] !== 'user') {
24
- navigate(urlJoin('/community', path[1]));
23
+ const detailType = path[1];
24
+
25
+ // group_agent goes back to agent list page
26
+ if (detailType === 'group_agent') {
27
+ navigate('/community/agent');
28
+ return;
29
+ }
30
+
31
+ // Types that have their own list pages
32
+ const typesWithListPage = ['agent', 'model', 'provider', 'mcp'];
33
+
34
+ if (detailType && typesWithListPage.includes(detailType)) {
35
+ navigate(urlJoin('/community', detailType));
25
36
  } else {
37
+ // For user or any other type without a list page
26
38
  navigate('/community');
27
39
  }
28
40
  };
@@ -38,7 +38,7 @@ const TagList = memo<{ tags: string[] }>(({ tags }) => {
38
38
  q: tag,
39
39
  source: marketSource,
40
40
  },
41
- url: '/community/assistant',
41
+ url: '/community/agent',
42
42
  },
43
43
  { skipNull: true },
44
44
  )}
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
  import { useQuery } from '@/hooks/useQuery';
7
7
  import { type AssistantMarketSource } from '@/types/discover';
8
8
 
9
- import McpList from '../../../../../(list)/assistant/features/List';
9
+ import McpList from '../../../../../(list)/agent/features/List';
10
10
  import Title from '../../../../../features/Title';
11
11
  import { useDetailContext } from '../../DetailProvider';
12
12
 
@@ -25,7 +25,7 @@ const Related = memo(() => {
25
25
  category,
26
26
  source: marketSource,
27
27
  },
28
- url: '/community/assistant',
28
+ url: '/community/agent',
29
29
  },
30
30
  { skipNull: true },
31
31
  )}
@@ -38,7 +38,7 @@ const TagList = memo<{ tags: string[] }>(({ tags }) => {
38
38
  q: tag,
39
39
  source: marketSource,
40
40
  },
41
- url: '/community/assistant',
41
+ url: '/community/agent',
42
42
  },
43
43
  { skipNull: true },
44
44
  )}
@@ -4,7 +4,7 @@ import { MessageCircleHeartIcon, MessageCircleQuestionIcon } from 'lucide-react'
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import TokenTag from '../../../../../(list)/assistant/features/List/TokenTag';
7
+ import TokenTag from '../../../../../(list)/agent/features/List/TokenTag';
8
8
  import Title from '../../../../../features/Title';
9
9
  import MarkdownRender from '../../../../features/MakedownRender';
10
10
  import { useDetailContext } from '../../DetailProvider';
@@ -32,7 +32,7 @@ import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
32
32
  import { socialService } from '@/services/social';
33
33
  import { formatIntergerNumber } from '@/utils/format';
34
34
 
35
- import { useCategory } from '../../../(list)/assistant/features/Category/useCategory';
35
+ import { useCategory } from '../../../(list)/agent/features/Category/useCategory';
36
36
  import PublishedTime from '../../../../../../../components/PublishedTime';
37
37
  import { useDetailContext } from './DetailProvider';
38
38
 
@@ -142,7 +142,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
142
142
  <Link
143
143
  to={qs.stringifyUrl({
144
144
  query: { category: cate?.key },
145
- url: '/community/assistant',
145
+ url: '/community/agent',
146
146
  })}
147
147
  >
148
148
  <Button icon={cate?.icon} size={'middle'} variant={'outlined'}>
@@ -87,7 +87,7 @@ const AddAgent = memo<{ mobile?: boolean }>(({ mobile }) => {
87
87
  event: 'add',
88
88
  identifier,
89
89
  source: location.pathname,
90
- })
90
+ });
91
91
  }
92
92
 
93
93
  if (shouldNavigate) {
@@ -21,7 +21,7 @@ const ActionButton = memo<{ mobile?: boolean }>(({ mobile }) => {
21
21
  desc: description,
22
22
  hashtags: tags,
23
23
  title: title,
24
- url: urlJoin(OFFICIAL_URL, '/community/assistant', identifier as string),
24
+ url: urlJoin(OFFICIAL_URL, '/community/agent', identifier as string),
25
25
  }}
26
26
  />
27
27
  </Flexbox>
@@ -28,7 +28,7 @@ const Related = memo(() => {
28
28
  category,
29
29
  source: marketSource,
30
30
  },
31
- url: '/community/assistant',
31
+ url: '/community/agent',
32
32
  },
33
33
  { skipNull: true },
34
34
  )}
@@ -40,7 +40,7 @@ const Related = memo(() => {
40
40
  const link = qs.stringifyUrl(
41
41
  {
42
42
  query: marketSource ? { source: marketSource } : undefined,
43
- url: urlJoin('/community/assistant', item.identifier),
43
+ url: urlJoin('/community/agent', item.identifier),
44
44
  },
45
45
  { skipNull: true },
46
46
  );
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { ExclamationCircleOutlined, FolderOpenOutlined } from '@ant-design/icons';
4
- import { FluentEmoji, Text , Button } from '@lobehub/ui';
4
+ import { Button, FluentEmoji, Text } from '@lobehub/ui';
5
5
  import { Result } from 'antd';
6
6
  import { memo } from 'react';
7
7
  import { Trans, useTranslation } from 'react-i18next';
@@ -16,7 +16,7 @@ const StatusPage = memo<StatusPageProps>(({ status }) => {
16
16
  const { t } = useTranslation('discover');
17
17
 
18
18
  const handleBackToMarket = () => {
19
- navigate('/community/assistant');
19
+ navigate('/community/agent');
20
20
  };
21
21
 
22
22
  // 审核中状态
@@ -142,7 +142,7 @@ const UserAgentCard = memo<UserAgentCardProps>(
142
142
  const link = qs.stringifyUrl(
143
143
  {
144
144
  query: { source: 'new' },
145
- url: urlJoin('/community/assistant', identifier),
145
+ url: urlJoin('/community/agent', identifier),
146
146
  },
147
147
  { skipNull: true },
148
148
  );
@@ -150,7 +150,7 @@ const UserAgentCard = memo<UserAgentCardProps>(
150
150
  const isPublished = status === 'published';
151
151
 
152
152
  const handleViewDetail = useCallback(() => {
153
- window.open(urlJoin('/community/assistant', identifier), '_blank');
153
+ window.open(urlJoin('/community/agent', identifier), '_blank');
154
154
  }, [identifier]);
155
155
 
156
156
  const handleEdit = useCallback(async () => {
@@ -96,7 +96,7 @@ const FavoriteAgentCard = memo<FavoriteAgentCardProps>(
96
96
  const link = qs.stringifyUrl(
97
97
  {
98
98
  query: { source: 'new' },
99
- url: urlJoin('/community/assistant', identifier),
99
+ url: urlJoin('/community/agent', identifier),
100
100
  },
101
101
  { skipNull: true },
102
102
  );
@@ -7,7 +7,7 @@ import { useDiscoverStore } from '@/store/discover';
7
7
  import { AssistantSorts, McpSorts } from '@/types/discover';
8
8
 
9
9
  import Title from '../../components/Title';
10
- import AssistantList from '../assistant/features/List';
10
+ import AssistantList from '../agent/features/List';
11
11
  import McpList from '../mcp/features/List';
12
12
  import Loading from './loading';
13
13
 
@@ -32,7 +32,7 @@ const HomePage = memo(() => {
32
32
 
33
33
  return (
34
34
  <>
35
- <Title more={t('home.more')} moreLink={'/community/assistant'}>
35
+ <Title more={t('home.more')} moreLink={'/community/agent'}>
36
36
  {t('home.featuredAssistants')}
37
37
  </Title>
38
38
  <AssistantList data={assistantList.items} rows={4} />
@@ -9,7 +9,7 @@ const Loading = memo(() => {
9
9
 
10
10
  return (
11
11
  <>
12
- <Title more={t('home.more')} moreLink={'/community/assistant'}>
12
+ <Title more={t('home.more')} moreLink={'/community/agent'}>
13
13
  {t('home.featuredAssistants')}
14
14
  </Title>
15
15
  <ListLoading length={8} rows={4} />
@@ -6,7 +6,11 @@ import { memo } from 'react';
6
6
  import { withSuspense } from '@/components/withSuspense';
7
7
  import { useQuery } from '@/hooks/useQuery';
8
8
  import { useDiscoverStore } from '@/store/discover';
9
- import { type AssistantMarketSource, type AssistantQueryParams, DiscoverTab } from '@/types/discover';
9
+ import {
10
+ type AssistantMarketSource,
11
+ type AssistantQueryParams,
12
+ DiscoverTab,
13
+ } from '@/types/discover';
10
14
 
11
15
  import Pagination from '../features/Pagination';
12
16
  import List from './features/List';
@@ -29,7 +29,7 @@ const Category = memo(() => {
29
29
  qs.stringifyUrl(
30
30
  {
31
31
  query: { category: key === AssistantCategory.All ? null : key, q, source },
32
- url: '/community/assistant',
32
+ url: '/community/agent',
33
33
  },
34
34
  { skipNull: true },
35
35
  );
@@ -73,7 +73,7 @@ const AssistantItem = memo<DiscoverAssistantItem>(
73
73
  const navigate = useNavigate();
74
74
  const { source } = useQuery() as { source?: AssistantMarketSource };
75
75
  const isGroupAgent = type === 'agent-group';
76
- const basePath = isGroupAgent ? '/community/group_agent' : '/community/assistant';
76
+ const basePath = isGroupAgent ? '/community/group_agent' : '/community/agent';
77
77
  const link = qs.stringifyUrl(
78
78
  {
79
79
  query: { source },
@@ -41,7 +41,7 @@ const MarketSourceSwitch = memo(() => {
41
41
  );
42
42
 
43
43
  const handleChange = (value: AssistantMarketSource) => {
44
- router.push('/community/assistant', {
44
+ router.push('/community/agent', {
45
45
  query: {
46
46
  page: null,
47
47
  source: value === 'new' ? null : value,
@@ -3,12 +3,12 @@
3
3
  import { Flexbox } from '@lobehub/ui';
4
4
  import { McpIcon, ProviderIcon } from '@lobehub/ui/icons';
5
5
  import { Bot, Brain, ShapesIcon } from 'lucide-react';
6
- import { usePathname } from '@/libs/router/navigation';
7
6
  import { memo, useMemo } from 'react';
8
7
  import { useTranslation } from 'react-i18next';
9
8
  import { Link, useNavigate } from 'react-router-dom';
10
9
 
11
10
  import NavItem, { type NavItemProps } from '@/features/NavPanel/components/NavItem';
11
+ import { usePathname } from '@/libs/router/navigation';
12
12
  import { DiscoverTab } from '@/types/discover';
13
13
 
14
14
  interface Item {
@@ -42,7 +42,7 @@ const Nav = memo(() => {
42
42
  icon: Bot,
43
43
  key: DiscoverTab.Assistants,
44
44
  title: t('tab.assistant'),
45
- url: '/community/assistant',
45
+ url: '/community/agent',
46
46
  },
47
47
  {
48
48
  icon: McpIcon,
@@ -41,7 +41,7 @@ const CommunityAgentsList = memo(() => {
41
41
  color: 'inherit',
42
42
  textDecoration: 'none',
43
43
  }}
44
- to={urlJoin('/community/assistant', item.identifier)}
44
+ to={urlJoin('/community/agent', item.identifier)}
45
45
  >
46
46
  <CommunityAgentItem {...item} />
47
47
  </Link>
@@ -24,7 +24,7 @@ const CommunityAgents = memo(() => {
24
24
  key: 'all-assistants',
25
25
  label: t('home.more'),
26
26
  onClick: () => {
27
- navigate('/community/assistant');
27
+ navigate('/community/agent');
28
28
  },
29
29
  },
30
30
  ]}
@@ -15,7 +15,7 @@ const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
15
15
  const MOBILE_NAV_ROUTES = new Set([
16
16
  '/',
17
17
  '/community',
18
- '/community/assistant',
18
+ '/community/agent',
19
19
  '/community/mcp',
20
20
  '/community/plugin',
21
21
  '/community/model',
@@ -65,10 +65,10 @@ export const mobileRoutes: RouteConfig[] = [
65
65
  children: [
66
66
  {
67
67
  element: dynamicElement(
68
- () => import('../../(main)/community/(list)/assistant'),
69
- 'Mobile > Discover > List > Assistant',
68
+ () => import('../../(main)/community/(list)/agent'),
69
+ 'Mobile > Discover > List > Agent',
70
70
  ),
71
- path: 'assistant',
71
+ path: 'agent',
72
72
  },
73
73
  ],
74
74
  },
@@ -113,12 +113,12 @@ export const mobileRoutes: RouteConfig[] = [
113
113
  {
114
114
  element: dynamicElement(
115
115
  () =>
116
- import('../../(main)/community/(detail)/assistant').then(
116
+ import('../../(main)/community/(detail)/agent').then(
117
117
  (m) => m.MobileDiscoverAssistantDetailPage,
118
118
  ),
119
- 'Mobile > Discover > Detail > Assistant',
119
+ 'Mobile > Discover > Detail > Agent',
120
120
  ),
121
- path: 'assistant/:slug',
121
+ path: 'agent/:slug',
122
122
  },
123
123
  {
124
124
  element: dynamicElement(
@@ -94,17 +94,17 @@ export const desktopRoutes: RouteConfig[] = [
94
94
  children: [
95
95
  {
96
96
  element: dynamicElement(
97
- () => import('../(main)/community/(list)/assistant'),
98
- 'Desktop > Discover > List > Assistant',
97
+ () => import('../(main)/community/(list)/agent'),
98
+ 'Desktop > Discover > List > Agent',
99
99
  ),
100
100
  index: true,
101
101
  },
102
102
  ],
103
103
  element: dynamicElement(
104
- () => import('../(main)/community/(list)/assistant/_layout'),
105
- 'Desktop > Discover > List > Assistant > Layout',
104
+ () => import('../(main)/community/(list)/agent/_layout'),
105
+ 'Desktop > Discover > List > Agent > Layout',
106
106
  ),
107
- path: 'assistant',
107
+ path: 'agent',
108
108
  },
109
109
  {
110
110
  children: [
@@ -163,10 +163,10 @@ export const desktopRoutes: RouteConfig[] = [
163
163
  children: [
164
164
  {
165
165
  element: dynamicElement(
166
- () => import('../(main)/community/(detail)/assistant'),
167
- 'Desktop > Discover > Detail > Assistant',
166
+ () => import('../(main)/community/(detail)/agent'),
167
+ 'Desktop > Discover > Detail > Agent',
168
168
  ),
169
- path: 'assistant/:slug',
169
+ path: 'agent/:slug',
170
170
  },
171
171
  {
172
172
  element: dynamicElement(
@@ -116,7 +116,7 @@ const ShareTopicLayout = memo<PropsWithChildren>(({ children }) => {
116
116
  // If agent has marketIdentifier, render as link to assistant page
117
117
  if (agentMarketIdentifier && !data?.groupMeta?.title) {
118
118
  return (
119
- <a href={`/community/assistant/${agentMarketIdentifier}`} rel="noreferrer" target="_blank">
119
+ <a href={`/community/agent/${agentMarketIdentifier}`} rel="noreferrer" target="_blank">
120
120
  <Typography.Text ellipsis strong>
121
121
  {agentOrGroupTitle}
122
122
  </Typography.Text>
@@ -101,7 +101,7 @@ const SearchResults = memo<SearchResultsProps>(
101
101
  break;
102
102
  }
103
103
  case 'communityAgent': {
104
- navigate(`/community/assistant/${result.identifier}`);
104
+ navigate(`/community/agent/${result.identifier}`);
105
105
  break;
106
106
  }
107
107
  }
@@ -79,7 +79,7 @@ const routePatterns: RoutePattern[] = [
79
79
  // Community/Discover routes
80
80
  {
81
81
  icon: Compass,
82
- test: (p) => p.startsWith('/community/assistant'),
82
+ test: (p) => p.startsWith('/community/agent'),
83
83
  titleKey: 'navigation.discoverAssistants',
84
84
  },
85
85
  {
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ import { type ReactNode, createContext, memo, useCallback, useContext, useState } from 'react';
4
+
5
+ export type FaviconState = 'default' | 'done' | 'error' | 'progress';
6
+
7
+ interface FaviconContextValue {
8
+ currentState: FaviconState;
9
+ isDevMode: boolean;
10
+ setFavicon: (state: FaviconState) => void;
11
+ setIsDevMode: (isDev: boolean) => void;
12
+ }
13
+
14
+ const FaviconContext = createContext<FaviconContextValue | null>(null);
15
+
16
+ export const useFavicon = () => {
17
+ const context = useContext(FaviconContext);
18
+ if (!context) {
19
+ throw new Error('useFavicon must be used within FaviconProvider');
20
+ }
21
+ return context;
22
+ };
23
+
24
+ const stateToFileName: Record<FaviconState, string> = {
25
+ default: '',
26
+ done: '-done',
27
+ error: '-error',
28
+ progress: '-progress',
29
+ };
30
+
31
+ const getFaviconPath = (state: FaviconState, isDev: boolean, size?: '32x32'): string => {
32
+ const devSuffix = isDev ? '-dev' : '';
33
+ const stateSuffix = stateToFileName[state];
34
+ const sizeSuffix = size ? `-${size}` : '';
35
+ return `/favicon${sizeSuffix}${stateSuffix}${devSuffix}.ico`;
36
+ };
37
+
38
+ const updateFaviconDOM = (state: FaviconState, isDev: boolean) => {
39
+ if (typeof document === 'undefined') return;
40
+
41
+ const head = document.head;
42
+ const existingLinks = document.querySelectorAll<HTMLLinkElement>(
43
+ 'link[rel="icon"], link[rel="shortcut icon"]',
44
+ );
45
+
46
+ // Remove existing favicon links and create new ones to bust cache
47
+ existingLinks.forEach((link) => {
48
+ const oldHref = link.href;
49
+ const is32 = oldHref.includes('32x32');
50
+ const rel = link.rel;
51
+
52
+ // Remove old link
53
+ link.remove();
54
+
55
+ // Create new link with cache-busting query param
56
+ const newLink = document.createElement('link');
57
+ newLink.rel = rel;
58
+ newLink.href = `${getFaviconPath(state, isDev, is32 ? '32x32' : undefined)}?v=${Date.now()}`;
59
+ head.append(newLink);
60
+ });
61
+ };
62
+
63
+ const defaultIsDev = process.env.NODE_ENV === 'development';
64
+
65
+ export const FaviconProvider = memo<{ children: ReactNode }>(({ children }) => {
66
+ const [currentState, setCurrentState] = useState<FaviconState>('default');
67
+ const [isDevMode, setIsDevModeState] = useState<boolean>(defaultIsDev);
68
+
69
+ const setFavicon = useCallback(
70
+ (state: FaviconState) => {
71
+ setCurrentState(state);
72
+ updateFaviconDOM(state, isDevMode);
73
+ },
74
+ [isDevMode],
75
+ );
76
+
77
+ const setIsDevMode = useCallback(
78
+ (isDev: boolean) => {
79
+ setIsDevModeState(isDev);
80
+ updateFaviconDOM(currentState, isDev);
81
+ },
82
+ [currentState],
83
+ );
84
+
85
+ return (
86
+ <FaviconContext.Provider value={{ currentState, isDevMode, setFavicon, setIsDevMode }}>
87
+ {children}
88
+ </FaviconContext.Provider>
89
+ );
90
+ });
91
+
92
+ FaviconProvider.displayName = 'FaviconProvider';
@@ -14,6 +14,7 @@ import { ServerConfigStoreProvider } from '@/store/serverConfig/Provider';
14
14
  import { getAntdLocale } from '@/utils/locale';
15
15
 
16
16
  import AppTheme from './AppTheme';
17
+ import { FaviconProvider } from './FaviconProvider';
17
18
  import { GroupWizardProvider } from './GroupWizardProvider';
18
19
  import ImportSettings from './ImportSettings';
19
20
  import Locale from './Locale';
@@ -65,17 +66,20 @@ const GlobalLayout = async ({
65
66
  >
66
67
  <QueryProvider>
67
68
  <StoreInitialization />
68
- <GroupWizardProvider>
69
- <DragUploadProvider>
70
- <LazyMotion features={domMax}>
71
- <TooltipGroup layoutAnimation={false}>
72
- <LobeAnalyticsProviderWrapper>{children}</LobeAnalyticsProviderWrapper>
73
- </TooltipGroup>
74
- <ModalHost />
75
- <ContextMenuHost />
76
- </LazyMotion>
77
- </DragUploadProvider>
78
- </GroupWizardProvider>
69
+ <FaviconProvider>
70
+ {/* {process.env.NODE_ENV === 'development' && <FaviconTestPanel />} */}
71
+ <GroupWizardProvider>
72
+ <DragUploadProvider>
73
+ <LazyMotion features={domMax}>
74
+ <TooltipGroup layoutAnimation={false}>
75
+ <LobeAnalyticsProviderWrapper>{children}</LobeAnalyticsProviderWrapper>
76
+ </TooltipGroup>
77
+ <ModalHost />
78
+ <ContextMenuHost />
79
+ </LazyMotion>
80
+ </DragUploadProvider>
81
+ </GroupWizardProvider>
82
+ </FaviconProvider>
79
83
  </QueryProvider>
80
84
  <Suspense>
81
85
  {ENABLE_BUSINESS_FEATURES ? <ReferralProvider /> : null}
@@ -267,7 +267,7 @@ export function defineConfig(config: CustomNextConfig) {
267
267
  source: '/manifest.json',
268
268
  },
269
269
  {
270
- destination: '/community/assistant',
270
+ destination: '/community/agent',
271
271
  permanent: true,
272
272
  source: '/community/assistants',
273
273
  },
@@ -59,7 +59,7 @@ describe('Sitemap', () => {
59
59
  );
60
60
  expect(pageSitemap).toContainEqual(
61
61
  expect.objectContaining({
62
- url: getCanonicalUrl('/community/assistant'),
62
+ url: getCanonicalUrl('/community/agent'),
63
63
  changeFrequency: 'daily',
64
64
  priority: 0.7,
65
65
  }),
@@ -85,13 +85,13 @@ describe('Sitemap', () => {
85
85
  expect(assistantsSitemap.length).toBe(LOCALE_COUNT);
86
86
  expect(assistantsSitemap).toContainEqual(
87
87
  expect.objectContaining({
88
- url: getCanonicalUrl('/community/assistant/test-assistant'),
88
+ url: getCanonicalUrl('/community/agent/test-assistant'),
89
89
  lastModified: '2023-01-01T00:00:00.000Z',
90
90
  }),
91
91
  );
92
92
  expect(assistantsSitemap).toContainEqual(
93
93
  expect.objectContaining({
94
- url: getCanonicalUrl('/community/assistant/test-assistant?hl=zh-CN'),
94
+ url: getCanonicalUrl('/community/agent/test-assistant?hl=zh-CN'),
95
95
  lastModified: '2023-01-01T00:00:00.000Z',
96
96
  }),
97
97
  );
@@ -113,7 +113,7 @@ describe('Sitemap', () => {
113
113
  expect(firstPageSitemap.length).toBe(100 * LOCALE_COUNT); // 100 items * LOCALE_COUNT locales
114
114
  expect(firstPageSitemap).toContainEqual(
115
115
  expect.objectContaining({
116
- url: getCanonicalUrl('/community/assistant/test-assistant-0'),
116
+ url: getCanonicalUrl('/community/agent/test-assistant-0'),
117
117
  lastModified: '2023-01-01T00:00:00.000Z',
118
118
  }),
119
119
  );
@@ -123,7 +123,7 @@ describe('Sitemap', () => {
123
123
  expect(secondPageSitemap.length).toBe(50 * LOCALE_COUNT); // 50 items * LOCALE_COUNT locales
124
124
  expect(secondPageSitemap).toContainEqual(
125
125
  expect.objectContaining({
126
- url: getCanonicalUrl('/community/assistant/test-assistant-100'),
126
+ url: getCanonicalUrl('/community/agent/test-assistant-100'),
127
127
  lastModified: '2023-01-01T00:00:00.000Z',
128
128
  }),
129
129
  );
@@ -213,7 +213,7 @@ export class Sitemap {
213
213
  const sitmap = pageAssistants
214
214
  .filter((item) => item.identifier) // Filter out items with empty identifiers
215
215
  .map((item) =>
216
- this._genSitemap(urlJoin('/community/assistant', item.identifier), {
216
+ this._genSitemap(urlJoin('/community/agent', item.identifier), {
217
217
  lastModified: item?.lastModified || LAST_MODIFIED,
218
218
  }),
219
219
  );
@@ -224,7 +224,7 @@ export class Sitemap {
224
224
  const sitmap = list
225
225
  .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
226
226
  .map((item) =>
227
- this._genSitemap(urlJoin('/community/assistant', item.identifier), {
227
+ this._genSitemap(urlJoin('/community/agent', item.identifier), {
228
228
  lastModified: item?.lastModified || LAST_MODIFIED,
229
229
  }),
230
230
  );
@@ -311,7 +311,7 @@ export class Sitemap {
311
311
 
312
312
  /* ↑ cloud slot ↑ */
313
313
  ...this._genSitemap('/community', { changeFrequency: 'daily', priority: 0.7 }),
314
- ...this._genSitemap('/community/assistant', { changeFrequency: 'daily', priority: 0.7 }),
314
+ ...this._genSitemap('/community/agent', { changeFrequency: 'daily', priority: 0.7 }),
315
315
  ...this._genSitemap('/community/mcp', { changeFrequency: 'daily', priority: 0.7 }),
316
316
  ...this._genSitemap('/community/plugin', { changeFrequency: 'daily', priority: 0.7 }),
317
317
  ...this._genSitemap('/community/model', { changeFrequency: 'daily', priority: 0.7 }),