@lobehub/lobehub 2.0.0-next.249 → 2.0.0-next.250

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 (87) hide show
  1. package/.github/workflows/release.yml +4 -0
  2. package/CHANGELOG.md +25 -0
  3. package/changelog/v1.json +5 -0
  4. package/locales/en-US/file.json +2 -0
  5. package/locales/zh-CN/discover.json +3 -0
  6. package/locales/zh-CN/file.json +2 -0
  7. package/package.json +3 -3
  8. package/packages/types/package.json +2 -2
  9. package/packages/types/src/discover/mcp.ts +3 -1
  10. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -0
  11. package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +4 -0
  12. package/src/app/[variants]/(main)/community/(list)/mcp/features/Category/index.tsx +7 -3
  13. package/src/app/[variants]/(main)/community/(list)/mcp/index.tsx +2 -2
  14. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Editing.tsx +1 -1
  15. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/Item.tsx +1 -1
  16. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/index.tsx +1 -1
  17. package/src/app/[variants]/(main)/home/_layout/Body/Project/List/useDropdownMenu.tsx +1 -1
  18. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Editing.tsx +1 -1
  19. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/index.tsx +1 -1
  20. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/useDropdownMenu.tsx +1 -1
  21. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/index.tsx +16 -6
  22. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/{KnowledgeBase.tsx → index.tsx} +2 -3
  23. package/src/app/[variants]/(main)/resource/(home)/_layout/Sidebar.tsx +2 -2
  24. package/src/app/[variants]/(main)/resource/(home)/index.tsx +23 -10
  25. package/src/app/[variants]/(main)/resource/features/DndContextWrapper.tsx +12 -37
  26. package/src/app/[variants]/(main)/resource/features/hooks/useKnowledgeItem.ts +1 -1
  27. package/src/app/[variants]/(main)/resource/features/store/action.ts +9 -39
  28. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +1 -1
  29. package/src/app/[variants]/(main)/resource/library/index.tsx +13 -6
  30. package/src/features/LibraryModal/AddFilesToKnowledgeBase/SelectForm.tsx +1 -1
  31. package/src/features/LibraryModal/CreateNew/CreateForm.tsx +1 -1
  32. package/src/features/PageEditor/Header/Breadcrumb.tsx +1 -1
  33. package/src/features/PageEditor/store/action.ts +5 -2
  34. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +5 -7
  35. package/src/features/ResourceManager/components/Explorer/Header/Breadcrumb.tsx +1 -1
  36. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +57 -26
  37. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +35 -6
  38. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +20 -14
  39. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +41 -31
  40. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/index.tsx +1 -1
  41. package/src/features/ResourceManager/components/Explorer/MasonryView/Skeleton.tsx +6 -2
  42. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +29 -18
  43. package/src/features/ResourceManager/components/Explorer/MoveToFolderModal.tsx +7 -34
  44. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +1 -1
  45. package/src/features/ResourceManager/components/Explorer/index.tsx +58 -18
  46. package/src/features/ResourceManager/components/Explorer/useCheckTaskStatus.ts +6 -4
  47. package/src/features/ResourceManager/components/Header/AddButton.tsx +58 -35
  48. package/src/features/ResourceManager/components/Header/hooks/useNotionImport.ts +5 -5
  49. package/src/features/ResourceManager/components/Tree/TreeSkeleton.tsx +19 -9
  50. package/src/features/ResourceManager/components/Tree/index.tsx +110 -5
  51. package/src/features/ResourceManager/components/UploadDock/index.tsx +2 -1
  52. package/src/features/ResourceManager/constants.ts +3 -0
  53. package/src/hooks/useMCPCategory.tsx +7 -0
  54. package/src/locales/default/discover.ts +3 -0
  55. package/src/locales/default/file.ts +2 -0
  56. package/src/services/file/index.ts +34 -1
  57. package/src/services/resource/index.ts +249 -0
  58. package/src/store/discover/slices/mcp/action.ts +1 -1
  59. package/src/store/file/slices/chat/action.ts +2 -1
  60. package/src/store/file/slices/document/action.ts +10 -7
  61. package/src/store/file/slices/fileManager/action.ts +14 -4
  62. package/src/store/file/slices/fileManager/initialState.ts +2 -0
  63. package/src/store/file/slices/resource/action.ts +432 -0
  64. package/src/store/file/slices/resource/hooks.ts +82 -0
  65. package/src/store/file/slices/resource/initialState.ts +67 -0
  66. package/src/store/file/slices/resource/syncEngine.ts +326 -0
  67. package/src/store/file/store.ts +6 -1
  68. package/src/store/{knowledgeBase → library}/initialState.ts +2 -2
  69. package/src/store/{knowledgeBase → library}/slices/content/action.test.ts +37 -51
  70. package/src/store/{knowledgeBase → library}/slices/content/action.ts +8 -4
  71. package/src/store/{knowledgeBase → library}/slices/crud/action.ts +1 -1
  72. package/src/store/{knowledgeBase → library}/slices/crud/selectors.ts +1 -1
  73. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/dataset.ts +1 -1
  74. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/evaluation.ts +1 -1
  75. package/src/store/{knowledgeBase → library}/slices/ragEval/actions/index.ts +1 -1
  76. package/src/types/resource.ts +133 -0
  77. package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/List/index.tsx +0 -25
  78. /package/src/app/[variants]/(main)/resource/(home)/_layout/Body/LibraryList/{List/Item → Item}/Actions.tsx +0 -0
  79. /package/src/store/{knowledgeBase → library}/index.ts +0 -0
  80. /package/src/store/{knowledgeBase → library}/selectors.ts +0 -0
  81. /package/src/store/{knowledgeBase → library}/slices/content/index.ts +0 -0
  82. /package/src/store/{knowledgeBase → library}/slices/crud/action.test.ts +0 -0
  83. /package/src/store/{knowledgeBase → library}/slices/crud/index.ts +0 -0
  84. /package/src/store/{knowledgeBase → library}/slices/crud/initialState.ts +0 -0
  85. /package/src/store/{knowledgeBase → library}/slices/ragEval/index.ts +0 -0
  86. /package/src/store/{knowledgeBase → library}/slices/ragEval/initialState.ts +0 -0
  87. /package/src/store/{knowledgeBase → library}/store.ts +0 -0
@@ -11,6 +11,10 @@ on:
11
11
  - main
12
12
  - next
13
13
 
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
14
18
  jobs:
15
19
  release:
16
20
  name: Release
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.250](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.249...v2.0.0-next.250)
6
+
7
+ <sup>Released on **2026-01-09**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **community**: Recommended for home & added discover tab.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **community**: Recommended for home & added discover tab, closes [#11290](https://github.com/lobehub/lobe-chat/issues/11290) ([8db248c](https://github.com/lobehub/lobe-chat/commit/8db248c))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.249](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.248...v2.0.0-next.249)
6
31
 
7
32
  <sup>Released on **2026-01-09**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-09",
5
+ "version": "2.0.0-next.250"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "improvements": [
@@ -18,6 +18,8 @@
18
18
  "empty": "No files or folders have been uploaded yet.",
19
19
  "header.actions.builtInBlockList.filtered": "{{ignored}} files filtered (out of {{total}} total)",
20
20
  "header.actions.connect": "Connect...",
21
+ "header.actions.createFolderError": "Failed to create folder",
22
+ "header.actions.creatingFolder": "Creating folder...",
21
23
  "header.actions.gitignore.apply": "Apply Rules",
22
24
  "header.actions.gitignore.cancel": "Ignore Rules",
23
25
  "header.actions.gitignore.content": ".gitignore file detected ({{count}} files in total). Would you like to apply the ignore rules?",
@@ -152,6 +152,8 @@
152
152
  "like": "喜欢",
153
153
  "mcp.categories.all.description": "全部 MCP Servers",
154
154
  "mcp.categories.all.name": "全部",
155
+ "mcp.categories.discover.description": "推荐和热门的 MCP 服务",
156
+ "mcp.categories.discover.name": "发现推荐",
155
157
  "mcp.categories.business.description": "商业与企业服务",
156
158
  "mcp.categories.business.name": "商业服务",
157
159
  "mcp.categories.developer.description": "开发相关的技能与服务",
@@ -338,6 +340,7 @@
338
340
  "mcp.hero.subTitle": "开源 & 开箱即用",
339
341
  "mcp.hero.title": "面向 AI 的开源 MCP 社区",
340
342
  "mcp.sorts.createdAt": "最近新增",
343
+ "mcp.sorts.discover": "推荐",
341
344
  "mcp.sorts.installCount": "安装数",
342
345
  "mcp.sorts.isFeatured": "推荐技能",
343
346
  "mcp.sorts.isValidated": "已验证技能",
@@ -18,6 +18,8 @@
18
18
  "empty": "这里还没有文件或文件夹。先上传一个文件,或创建文稿开始整理",
19
19
  "header.actions.builtInBlockList.filtered": "已过滤 {{ignored}} 个文件(共 {{total}} 个文件)",
20
20
  "header.actions.connect": "连接…",
21
+ "header.actions.createFolderError": "创建文件夹失败",
22
+ "header.actions.creatingFolder": "正在创建文件夹...",
21
23
  "header.actions.gitignore.apply": "应用规则",
22
24
  "header.actions.gitignore.cancel": "忽略规则",
23
25
  "header.actions.gitignore.content": "检测到 .gitignore 文件(共 {{count}} 个文件),是否应用忽略规则?",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.249",
3
+ "version": "2.0.0-next.250",
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",
@@ -204,7 +204,7 @@
204
204
  "@lobehub/desktop-ipc-typings": "workspace:*",
205
205
  "@lobehub/editor": "^3.8.0",
206
206
  "@lobehub/icons": "^4.0.2",
207
- "@lobehub/market-sdk": "0.28.0",
207
+ "@lobehub/market-sdk": "0.28.1",
208
208
  "@lobehub/tts": "^4.0.2",
209
209
  "@lobehub/ui": "^4.11.6",
210
210
  "@modelcontextprotocol/sdk": "^1.25.1",
@@ -366,7 +366,7 @@
366
366
  "@lobechat/types": "workspace:*",
367
367
  "@lobehub/i18n-cli": "^1.26.0",
368
368
  "@lobehub/lint": "^1.26.3",
369
- "@lobehub/market-types": "^1.11.5",
369
+ "@lobehub/market-types": "^1.12.3",
370
370
  "@lobehub/seo-cli": "^1.7.0",
371
371
  "@next/bundle-analyzer": "^16.1.1",
372
372
  "@next/eslint-plugin-next": "^16.1.1",
@@ -8,8 +8,8 @@
8
8
  "@lobechat/python-interpreter": "workspace:*",
9
9
  "@lobechat/web-crawler": "workspace:*",
10
10
  "@lobehub/chat-plugin-sdk": "^1.32.4",
11
- "@lobehub/market-sdk": "0.28.0",
12
- "@lobehub/market-types": "^1.11.4",
11
+ "@lobehub/market-sdk": "0.28.1",
12
+ "@lobehub/market-types": "^1.12.3",
13
13
  "model-bank": "workspace:*",
14
14
  "type-fest": "^4.41.0",
15
15
  "zustand": "5.0.4"
@@ -5,6 +5,7 @@ export enum McpCategory {
5
5
  All = 'all',
6
6
  Business = 'business',
7
7
  Developer = 'developer',
8
+ Discover = 'discover',
8
9
  GamingEntertainment = 'gaming-entertainment',
9
10
  HealthWellness = 'health-wellness',
10
11
  Lifestyle = 'lifestyle',
@@ -26,7 +27,8 @@ export enum McpSorts {
26
27
  IsFeatured = 'isFeatured',
27
28
  IsValidated = 'isValidated',
28
29
  RatingCount = 'ratingCount',
29
- UpdatedAt = 'updatedAt',
30
+ Recommended = 'recommended',
31
+ UpdatedAt = 'updatedAt'
30
32
  }
31
33
 
32
34
  export enum McpNavKey {
@@ -4,6 +4,7 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { useDiscoverStore } from '@/store/discover';
7
+ import { McpSorts } from '@/types/discover';
7
8
 
8
9
  import Title from '../../components/Title';
9
10
  import AssistantList from '../assistant/features/List';
@@ -23,6 +24,7 @@ const HomePage = memo(() => {
23
24
  const { data: mcpList, isLoading: pluginLoading } = useMcpList({
24
25
  page: 1,
25
26
  pageSize: 12,
27
+ sort: McpSorts.Recommended,
26
28
  });
27
29
 
28
30
  if (assistantLoading || pluginLoading || !assistantList || !mcpList) return <Loading />;
@@ -125,6 +125,10 @@ const SortButton = memo(() => {
125
125
  }
126
126
  case DiscoverTab.Mcp: {
127
127
  return [
128
+ {
129
+ key: McpSorts.Recommended,
130
+ label: t('mcp.sorts.recommended'),
131
+ },
128
132
  {
129
133
  key: McpSorts.IsFeatured,
130
134
  label: t('mcp.sorts.isFeatured'),
@@ -10,13 +10,13 @@ import { withSuspense } from '@/components/withSuspense';
10
10
  import { useCategory } from '@/hooks/useMCPCategory';
11
11
  import { useQuery } from '@/hooks/useQuery';
12
12
  import { useDiscoverStore } from '@/store/discover';
13
- import { McpCategory } from '@/types/discover';
13
+ import { McpCategory, McpSorts } from '@/types/discover';
14
14
 
15
15
  import CategoryMenu from '../../../../components/CategoryMenu';
16
16
 
17
17
  const Category = memo(() => {
18
18
  const useMcpCategories = useDiscoverStore((s) => s.useMcpCategories);
19
- const { category = 'all', q } = useQuery() as { category?: McpCategory; q?: string };
19
+ const { category = McpCategory.Discover, q } = useQuery() as { category?: McpCategory; q?: string };
20
20
  const { data: items = [] } = useMcpCategories({ q });
21
21
  const navigate = useNavigate();
22
22
  const cates = useCategory();
@@ -24,7 +24,11 @@ const Category = memo(() => {
24
24
  const genUrl = (key: McpCategory) =>
25
25
  qs.stringifyUrl(
26
26
  {
27
- query: { category: key === McpCategory.All ? null : key, q },
27
+ query: {
28
+ category: [McpCategory.All, McpCategory.Discover].includes(key) ? null : key,
29
+ q,
30
+ sort: key === McpCategory.Discover ? McpSorts.Recommended : null,
31
+ },
28
32
  url: '/community/mcp',
29
33
  },
30
34
  { skipNull: true },
@@ -5,7 +5,7 @@ import { memo } from 'react';
5
5
 
6
6
  import { useQuery } from '@/hooks/useQuery';
7
7
  import { useDiscoverStore } from '@/store/discover';
8
- import { DiscoverTab, type McpQueryParams } from '@/types/discover';
8
+ import { DiscoverTab, McpSorts, type McpQueryParams } from '@/types/discover';
9
9
 
10
10
  import Pagination from '../features/Pagination';
11
11
  import List from './features/List';
@@ -20,7 +20,7 @@ const McpPage = memo(() => {
20
20
  page,
21
21
  pageSize: 21,
22
22
  q,
23
- sort,
23
+ sort: sort ?? McpSorts.Recommended,
24
24
  });
25
25
 
26
26
  if (isLoading || !data) return <Loading />;
@@ -1,7 +1,7 @@
1
1
  import { Input, Popover } from '@lobehub/ui';
2
2
  import { memo, useCallback, useState } from 'react';
3
3
 
4
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
4
+ import { useKnowledgeBaseStore } from '@/store/library';
5
5
 
6
6
  interface EditingProps {
7
7
  id: string;
@@ -1,7 +1,7 @@
1
1
  import { BoxIcon } from 'lucide-react';
2
2
  import { memo, useCallback } from 'react';
3
3
 
4
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
4
+ import { useKnowledgeBaseStore } from '@/store/library';
5
5
 
6
6
  import NavItem from '../../../../../../../../features/NavPanel/components/NavItem';
7
7
  import Actions from './Actions';
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Link, useNavigate } from 'react-router-dom';
7
7
 
8
8
  import { LIBRARY_URL } from '@/const/url';
9
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
9
+ import { useKnowledgeBaseStore } from '@/store/library';
10
10
 
11
11
  import EmptyNavItem from '../../../../../../../../features/NavPanel/components/EmptyNavItem';
12
12
  import SkeletonList from '../../../../../../../../features/NavPanel/components/SkeletonList';
@@ -4,7 +4,7 @@ import { PencilLine, Trash } from 'lucide-react';
4
4
  import { useCallback } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
7
+ import { useKnowledgeBaseStore } from '@/store/library';
8
8
 
9
9
  interface ProjectItemDropdownMenuProps {
10
10
  id: string;
@@ -1,7 +1,7 @@
1
1
  import { Input, Popover } from '@lobehub/ui';
2
2
  import { memo, useCallback, useEffect, useState } from 'react';
3
3
 
4
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
4
+ import { useKnowledgeBaseStore } from '@/store/library';
5
5
 
6
6
  interface EditingProps {
7
7
  id: string;
@@ -7,7 +7,7 @@ import { useNavigate } from 'react-router-dom';
7
7
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
8
8
  import RepoIcon from '@/components/LibIcon';
9
9
  import NavItem from '@/features/NavPanel/components/NavItem';
10
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
10
+ import { useKnowledgeBaseStore } from '@/store/library';
11
11
 
12
12
  import Actions from './Actions';
13
13
  import Editing from './Editing';
@@ -4,7 +4,7 @@ import { PencilLine, Trash } from 'lucide-react';
4
4
  import { useCallback } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
7
+ import { useKnowledgeBaseStore } from '@/store/library';
8
8
 
9
9
  interface ActionProps {
10
10
  id: string;
@@ -1,17 +1,21 @@
1
1
  'use client';
2
2
 
3
- import React, { memo } from 'react';
3
+ import { Flexbox } from '@lobehub/ui';
4
+ import { memo } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
  import { useNavigate } from 'react-router-dom';
6
7
 
7
8
  import { useCreateNewModal } from '@/features/LibraryModal';
8
9
  import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
9
10
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
10
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
11
+ import { useKnowledgeBaseStore } from '@/store/library';
11
12
 
12
- import List from './List';
13
+ import Item from './Item';
13
14
 
14
- const KnowledgeBaseList = memo(() => {
15
+ /**
16
+ * Show library list in the sidebar
17
+ */
18
+ const LibraryList = memo(() => {
15
19
  const { t } = useTranslation('file');
16
20
  const useFetchKnowledgeBaseList = useKnowledgeBaseStore((s) => s.useFetchKnowledgeBaseList);
17
21
  const { data, isLoading } = useFetchKnowledgeBaseList();
@@ -32,7 +36,13 @@ const KnowledgeBaseList = memo(() => {
32
36
 
33
37
  if (data?.length === 0) return <EmptyNavItem onClick={handleCreate} title={t('library.new')} />;
34
38
 
35
- return <List />;
39
+ return (
40
+ <Flexbox gap={1} paddingInline={4}>
41
+ {data?.map((item) => (
42
+ <Item id={item.id} key={item.id} name={item.name} />
43
+ ))}
44
+ </Flexbox>
45
+ );
36
46
  });
37
47
 
38
- export default KnowledgeBaseList;
48
+ export default LibraryList;
@@ -8,7 +8,7 @@ import { useCreateNewModal } from '@/features/LibraryModal';
8
8
 
9
9
  import LibraryList from './LibraryList';
10
10
 
11
- const Collection = memo<{ itemKey: string }>(({ itemKey }) => {
11
+ const SidebarBody = memo<{ itemKey: string }>(({ itemKey }) => {
12
12
  const { t } = useTranslation('file');
13
13
  const navigate = useNavigate();
14
14
 
@@ -29,7 +29,6 @@ const Collection = memo<{ itemKey: string }>(({ itemKey }) => {
29
29
  icon={PlusIcon}
30
30
  onClick={handleCreate}
31
31
  size={'small'}
32
- style={{ flex: 'none' }}
33
32
  title={t('library.new')}
34
33
  />
35
34
  }
@@ -47,4 +46,4 @@ const Collection = memo<{ itemKey: string }>(({ itemKey }) => {
47
46
  );
48
47
  });
49
48
 
50
- export default Collection;
49
+ export default SidebarBody;
@@ -6,7 +6,7 @@ import { memo } from 'react';
6
6
  import { NavPanelPortal } from '@/features/NavPanel';
7
7
  import SideBarLayout from '@/features/NavPanel/SideBarLayout';
8
8
 
9
- import Collection from './Body/KnowledgeBase';
9
+ import SidebarBody from './Body';
10
10
  import Header from './Header';
11
11
 
12
12
  export enum GroupKey {
@@ -20,7 +20,7 @@ const Sidebar = memo(() => {
20
20
  body={
21
21
  <Flexbox paddingBlock={8} paddingInline={4}>
22
22
  <Accordion defaultExpandedKeys={[GroupKey.Library]} gap={8}>
23
- <Collection itemKey={GroupKey.Library} />
23
+ <SidebarBody itemKey={GroupKey.Library} />
24
24
  </Accordion>
25
25
  </Flexbox>
26
26
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { memo, useEffect } from 'react';
4
- import { useSearchParams } from 'react-router-dom';
3
+ import { memo, useEffect, useLayoutEffect } from 'react';
4
+ import { useLocation, useSearchParams } from 'react-router-dom';
5
5
 
6
6
  import ResourceManager from '@/features/ResourceManager';
7
7
  import { documentSelectors, useFileStore } from '@/store/file';
@@ -11,6 +11,7 @@ import { useResourceManagerStore } from '../features/store';
11
11
 
12
12
  const ResourceHomePage = memo(() => {
13
13
  const [searchParams] = useSearchParams();
14
+ const location = useLocation();
14
15
  const [setMode, setCurrentViewItemId, setCategory, setLibraryId] = useResourceManagerStore(
15
16
  (s) => [s.setMode, s.setCurrentViewItemId, s.setCategory, s.setLibraryId],
16
17
  );
@@ -23,15 +24,27 @@ const ResourceHomePage = memo(() => {
23
24
  const { data: fileData } = useFetchKnowledgeItem(fileId || undefined);
24
25
  const documentData = useFileStore(documentSelectors.getDocumentById(fileId || undefined));
25
26
 
26
- // Clear libraryId when on home route (only once on mount)
27
- useEffect(() => {
28
- setLibraryId(undefined);
29
- }, [setLibraryId]);
27
+ // Clear libraryId when on home route using useLayoutEffect
28
+ // useLayoutEffect runs synchronously before browser paint, ensuring state is cleared
29
+ // before child components' useEffects run, while avoiding React's setState-in-render error
30
+ // IMPORTANT: Only depend on location.pathname, NOT currentLibraryId to avoid feedback loop
31
+ // When location changes to /resource, clear libraryId
32
+ // Don't clear when location is /library/* (even if this component is still mounted)
33
+ useLayoutEffect(() => {
34
+ const isOnHomeRoute = location.pathname === '/resource' || !location.pathname.includes('/library/');
35
+ if (isOnHomeRoute) {
36
+ setLibraryId(undefined);
37
+ }
38
+ }, [setLibraryId, location.pathname]);
30
39
 
31
- // Sync category from URL
32
- useEffect(() => {
33
- setCategory(categoryParam);
34
- }, [categoryParam, setCategory]);
40
+ // Sync category from URL using useLayoutEffect
41
+ // IMPORTANT: Only sync if we're actually on the home route (not transitioning to library)
42
+ useLayoutEffect(() => {
43
+ const isOnHomeRoute = location.pathname === '/resource' || !location.pathname.includes('/library/');
44
+ if (isOnHomeRoute) {
45
+ setCategory(categoryParam);
46
+ }
47
+ }, [categoryParam, setCategory, location.pathname]);
35
48
 
36
49
  // Sync file view mode from URL
37
50
  useEffect(() => {
@@ -60,6 +60,7 @@ interface DragState {
60
60
 
61
61
  const DragStateContext = createContext<{
62
62
  currentDrag: DragState | null;
63
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
64
  setCurrentDrag: (state: DragState | null) => void;
64
65
  }>({
65
66
  currentDrag: null,
@@ -77,10 +78,8 @@ export const DndContextWrapper = memo<PropsWithChildren>(({ children }) => {
77
78
  const { message } = App.useApp();
78
79
  const [currentDrag, setCurrentDrag] = useState<DragState | null>(null);
79
80
  const overlayRef = useRef<HTMLDivElement>(null);
80
- const updateDocument = useFileStore((s) => s.updateDocument);
81
- const moveFileToFolder = useFileStore((s) => s.moveFileToFolder);
82
- const refreshFileList = useFileStore((s) => s.refreshFileList);
83
- const fileList = useFileStore((s) => s.fileList);
81
+ const moveResource = useFileStore((s) => s.moveResource);
82
+ const resourceList = useFileStore((s) => s.resourceList);
84
83
  const selectedFileIds = useResourceManagerStore((s) => s.selectedFileIds);
85
84
  const setSelectedFileIds = useResourceManagerStore((s) => s.setSelectedFileIds);
86
85
  const libraryId = useResourceManagerStore((s) => s.libraryId);
@@ -143,8 +142,8 @@ export const DndContextWrapper = memo<PropsWithChildren>(({ children }) => {
143
142
  }
144
143
 
145
144
  // Save current drag data before clearing state
146
- const draggedItemId = currentDrag.id;
147
- const draggedItemData = currentDrag.data;
145
+ // const draggedItemId = currentDrag.id;
146
+ // const draggedItemData = currentDrag.data;
148
147
 
149
148
  // Clear drag state immediately for better UX
150
149
  setCurrentDrag(null);
@@ -153,36 +152,14 @@ export const DndContextWrapper = memo<PropsWithChildren>(({ children }) => {
153
152
  const hideLoading = message.loading(t('FileManager.actions.moving'), 0);
154
153
 
155
154
  try {
156
- // Track source folder IDs before moving
157
- const sourceFolderIds = new Set<string | null>();
158
-
159
- const pools = itemsToMove.map((id) => {
160
- const item = fileList.find((f) => f.id === id);
161
- const itemData = item || (id === draggedItemId ? draggedItemData : null);
162
-
163
- if (!itemData) return Promise.resolve();
164
-
165
- // Track source folder ID
166
- if (item?.parentId !== undefined) {
167
- sourceFolderIds.add(item.parentId);
168
- }
169
-
170
- const isDocument =
171
- itemData.sourceType === 'document' ||
172
- itemData.fileType === 'custom/document' ||
173
- itemData.fileType === 'custom/folder';
174
-
175
- if (isDocument) {
176
- return updateDocument(id, { parentId: targetParentId });
177
- } else {
178
- return moveFileToFolder(id, targetParentId);
179
- }
180
- });
155
+ // Move all items using optimistic moveResource
156
+ const pools = itemsToMove.map((id) => moveResource(id, targetParentId));
181
157
 
182
158
  await Promise.all(pools);
183
159
 
184
- // Refresh file list to invalidate SWR cache for both Explorer and Tree
185
- await refreshFileList();
160
+ // Refetch resources to update the view (items should disappear from current folder)
161
+ const { revalidateResources } = await import('@/store/file/slices/resource/hooks');
162
+ await revalidateResources();
186
163
 
187
164
  // Clear and reload all expanded folders in Tree's module-level cache
188
165
  if (libraryId) {
@@ -230,10 +207,8 @@ export const DndContextWrapper = memo<PropsWithChildren>(({ children }) => {
230
207
  }, [
231
208
  currentDrag,
232
209
  selectedFileIds,
233
- fileList,
234
- updateDocument,
235
- moveFileToFolder,
236
- refreshFileList,
210
+ resourceList,
211
+ moveResource,
237
212
  setSelectedFileIds,
238
213
  message,
239
214
  t,
@@ -1,4 +1,4 @@
1
- import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
1
+ import { useKnowledgeBaseStore } from '@/store/library';
2
2
 
3
3
  export const useKnowledgeBaseItem = (id: string) => {
4
4
  const useFetchKnowledgeBaseItem = useKnowledgeBaseStore((s) => s.useFetchKnowledgeBaseItem);
@@ -130,7 +130,7 @@ export const store: CreateStore = (publicState) => (set, get) => ({
130
130
  onActionClick: async (type) => {
131
131
  const { selectedFileIds, libraryId } = get();
132
132
  const { useFileStore } = await import('@/store/file');
133
- const { useKnowledgeBaseStore } = await import('@/store/knowledgeBase');
133
+ const { useKnowledgeBaseStore } = await import('@/store/library');
134
134
  const { isChunkingUnsupported } = await import('@/utils/isChunkingUnsupported');
135
135
 
136
136
  const fileStore = useFileStore.getState();
@@ -138,37 +138,9 @@ export const store: CreateStore = (publicState) => (set, get) => ({
138
138
 
139
139
  switch (type) {
140
140
  case 'delete': {
141
- // Separate documents/pages from regular files
142
- const documentsToDelete: string[] = [];
143
- const filesToDelete: string[] = [];
144
-
145
- selectedFileIds.forEach((id) => {
146
- const item = fileStore.fileList.find((f) => f.id === id);
147
- if (!item) return;
148
-
149
- const isPage = item.sourceType === 'document' || item.fileType === 'custom/document';
150
- const isFolder = item.fileType === 'custom/folder';
151
-
152
- if (isPage || isFolder) {
153
- documentsToDelete.push(id);
154
- } else {
155
- filesToDelete.push(id);
156
- }
157
- });
158
-
159
- // Delete documents using batch delete endpoint
160
- if (documentsToDelete.length > 0) {
161
- const { documentService } = await import('@/services/document');
162
- await documentService.deleteDocuments(documentsToDelete);
163
- }
164
-
165
- // Delete regular files using file service
166
- if (filesToDelete.length > 0) {
167
- await fileStore.removeFiles(filesToDelete);
168
- } else {
169
- // If only documents were deleted, still refresh the file list
170
- await fileStore.refreshFileList();
171
- }
141
+ // Use optimistic deleteResource for instant batch delete
142
+ // All items disappear immediately, sync happens in background
143
+ await Promise.all(selectedFileIds.map((id) => fileStore.deleteResource(id)));
172
144
 
173
145
  set({ selectedFileIds: [] });
174
146
  return;
@@ -197,8 +169,8 @@ export const store: CreateStore = (publicState) => (set, get) => ({
197
169
 
198
170
  case 'batchChunking': {
199
171
  const chunkableFileIds = selectedFileIds.filter((id) => {
200
- const file = fileStore.fileList.find((f) => f.id === id);
201
- return file && !isChunkingUnsupported(file.fileType);
172
+ const resource = fileStore.resourceMap?.get(id);
173
+ return resource && !isChunkingUnsupported(resource.fileType);
202
174
  });
203
175
  await fileStore.parseFilesToChunks(chunkableFileIds, { skipExist: true });
204
176
  set({ selectedFileIds: [] });
@@ -246,7 +218,7 @@ export const store: CreateStore = (publicState) => (set, get) => ({
246
218
  set({ isTransitioning });
247
219
  },
248
220
 
249
- setLibraryId: async (libraryId) => {
221
+ setLibraryId: (libraryId) => {
250
222
  set({ libraryId });
251
223
 
252
224
  // Reset pagination state when switching libraries to prevent showing stale data
@@ -255,10 +227,8 @@ export const store: CreateStore = (publicState) => (set, get) => ({
255
227
  fileListOffset: 0,
256
228
  });
257
229
 
258
- // Invalidate SWR cache to prevent showing stale data from previous library
259
- const { useFileStore } = await import('@/store/file');
260
- const fileStore = useFileStore.getState();
261
- await fileStore.refreshFileList();
230
+ // Note: No need to manually refresh - Explorer's useEffect will automatically
231
+ // call fetchResources when libraryId changes
262
232
  },
263
233
 
264
234
  setMode: (mode) => {
@@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom';
9
9
  import { useDragActive } from '@/app/[variants]/(main)/resource/features/DndContextWrapper';
10
10
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
11
11
  import RepoIcon from '@/components/LibIcon';
12
- import { knowledgeBaseSelectors, useKnowledgeBaseStore } from '@/store/knowledgeBase';
12
+ import { knowledgeBaseSelectors, useKnowledgeBaseStore } from '@/store/library';
13
13
 
14
14
  const styles = createStaticStyles(({ css, cssVar }) => ({
15
15
  clickableHeader: css`