@lobehub/lobehub 2.0.0-next.254 → 2.0.0-next.256

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 (148) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/plugin.json +1 -0
  4. package/locales/zh-CN/plugin.json +1 -0
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-notebook/src/client/Placeholder/CreateDocument.tsx +6 -6
  7. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +23 -10
  8. package/packages/builtin-tool-notebook/src/client/Streaming/CreateDocument/index.tsx +1 -1
  9. package/packages/database/src/models/__tests__/agent.test.ts +91 -4
  10. package/packages/database/src/models/agent.ts +15 -7
  11. package/packages/editor-runtime/src/EditorRuntime.ts +1 -1
  12. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +65 -4
  13. package/packages/editor-runtime/src/__tests__/__snapshots__/EditorRuntime.real.test.ts.snap +108 -17
  14. package/packages/editor-runtime/src/__tests__/fixtures/remove-then-add.json +636 -0
  15. package/packages/editor-runtime/src/__tests__/fixtures/remove.json +1 -0
  16. package/packages/types/src/agent/agentConfig.ts +8 -8
  17. package/src/app/[variants]/(main)/chat/features/Portal/_layout/Mobile.tsx +2 -1
  18. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +2 -1
  19. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +2 -2
  20. package/src/app/[variants]/(main)/page/{features/PageTitle → PageTitle}/index.tsx +0 -1
  21. package/src/app/[variants]/(main)/page/[id]/index.tsx +43 -1
  22. package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/Content.tsx +15 -15
  23. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +3 -3
  24. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +7 -12
  25. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +5 -5
  26. package/src/app/[variants]/(main)/page/_layout/Body/List/index.tsx +7 -7
  27. package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +15 -9
  28. package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +3 -3
  29. package/src/app/[variants]/(main)/page/_layout/DataSync.tsx +15 -0
  30. package/src/app/[variants]/(main)/page/_layout/Header/AddButton.tsx +2 -2
  31. package/src/app/[variants]/(main)/page/_layout/index.tsx +2 -0
  32. package/src/app/[variants]/(main)/page/index.tsx +3 -7
  33. package/src/components/Editor/AutoSaveHint.tsx +1 -1
  34. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/Intervention/ApprovalActions.tsx +2 -2
  35. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +18 -15
  36. package/src/features/Conversation/Messages/AssistantGroup/components/Group.tsx +3 -3
  37. package/src/features/Conversation/Messages/Contexts/MessageAggregationContext.ts +15 -0
  38. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +3 -3
  39. package/src/features/Conversation/Messages/User/Actions/index.tsx +5 -1
  40. package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
  41. package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
  42. package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
  43. package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
  44. package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
  45. package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
  46. package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
  47. package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
  48. package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
  49. package/src/features/EditorCanvas/index.ts +9 -0
  50. package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
  51. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
  52. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
  53. package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
  54. package/src/features/PageEditor/Header/index.tsx +15 -18
  55. package/src/features/PageEditor/Header/useMenu.tsx +12 -9
  56. package/src/features/PageEditor/PageEditor.tsx +45 -21
  57. package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
  58. package/src/features/PageEditor/PageTitle/index.tsx +2 -2
  59. package/src/features/PageEditor/StoreUpdater.tsx +35 -308
  60. package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
  61. package/src/features/PageEditor/store/action.ts +96 -188
  62. package/src/features/PageEditor/store/initialState.ts +16 -21
  63. package/src/features/PageEditor/store/selectors.ts +3 -4
  64. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
  65. package/src/features/PageExplorer/index.tsx +34 -67
  66. package/src/features/Portal/Artifacts/index.ts +0 -2
  67. package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
  68. package/src/features/Portal/Document/Body.tsx +1 -3
  69. package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
  70. package/src/features/Portal/Document/Header.tsx +13 -10
  71. package/src/features/Portal/Document/TodoList.tsx +6 -4
  72. package/src/features/Portal/Document/Wrapper.tsx +3 -11
  73. package/src/features/Portal/Document/index.ts +0 -2
  74. package/src/features/Portal/FilePreview/index.ts +0 -2
  75. package/src/features/Portal/GroupThread/index.ts +0 -3
  76. package/src/features/Portal/MessageDetail/index.ts +0 -2
  77. package/src/features/Portal/Notebook/index.ts +0 -2
  78. package/src/features/Portal/Plugins/index.ts +0 -2
  79. package/src/features/Portal/Thread/index.ts +0 -3
  80. package/src/features/Portal/components/Header.tsx +18 -6
  81. package/src/features/Portal/router.tsx +34 -97
  82. package/src/features/Portal/type.ts +0 -2
  83. package/src/libs/next/config/define-config.ts +4 -1
  84. package/src/locales/default/plugin.ts +1 -0
  85. package/src/store/chat/slices/portal/action.test.ts +218 -15
  86. package/src/store/chat/slices/portal/action.ts +194 -41
  87. package/src/store/chat/slices/portal/initialState.ts +40 -1
  88. package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
  89. package/src/store/chat/slices/portal/selectors.test.ts +119 -17
  90. package/src/store/chat/slices/portal/selectors.ts +117 -36
  91. package/src/store/document/index.ts +17 -5
  92. package/src/store/document/slices/document/action.ts +209 -0
  93. package/src/store/document/slices/document/index.ts +6 -0
  94. package/src/store/document/slices/editor/action.test.ts +340 -0
  95. package/src/store/document/slices/editor/action.ts +133 -149
  96. package/src/store/document/slices/editor/index.ts +9 -2
  97. package/src/store/document/slices/editor/initialState.ts +66 -29
  98. package/src/store/document/slices/editor/reducer.test.ts +217 -0
  99. package/src/store/document/slices/editor/reducer.ts +67 -0
  100. package/src/store/document/slices/editor/selectors.test.ts +395 -0
  101. package/src/store/document/slices/editor/selectors.ts +107 -5
  102. package/src/store/document/store.ts +12 -13
  103. package/src/store/file/slices/document/action.ts +19 -188
  104. package/src/store/file/slices/document/initialState.ts +0 -30
  105. package/src/store/file/slices/document/selectors.ts +25 -59
  106. package/src/store/notebook/index.ts +5 -4
  107. package/src/store/page/index.ts +2 -0
  108. package/src/store/page/initialState.ts +92 -0
  109. package/src/store/page/selectors.ts +5 -0
  110. package/src/store/page/slices/crud/action.ts +477 -0
  111. package/src/store/page/slices/crud/index.ts +2 -0
  112. package/src/store/page/slices/crud/initialState.ts +7 -0
  113. package/src/store/page/slices/internal/action.ts +32 -0
  114. package/src/store/page/slices/internal/index.ts +2 -0
  115. package/src/store/page/slices/internal/reducer.ts +105 -0
  116. package/src/store/page/slices/list/action.ts +206 -0
  117. package/src/store/page/slices/list/index.ts +3 -0
  118. package/src/store/page/slices/list/initialState.ts +29 -0
  119. package/src/store/page/slices/list/selectors.ts +90 -0
  120. package/src/store/page/slices/selection/action.ts +67 -0
  121. package/src/store/page/slices/selection/index.ts +2 -0
  122. package/src/store/page/slices/selection/initialState.ts +11 -0
  123. package/src/store/page/store.ts +29 -0
  124. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
  125. package/src/utils/identifier.ts +8 -2
  126. package/src/features/Conversation/Messages/AssistantGroup/components/GroupContext.ts +0 -15
  127. package/src/features/Conversation/Messages/Supervisor/components/GroupContext.ts +0 -15
  128. package/src/features/PageEditor/Body/index.tsx +0 -68
  129. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
  130. package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
  131. package/src/features/Portal/Artifacts/useEnable.ts +0 -4
  132. package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
  133. package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
  134. package/src/features/Portal/Document/Title.tsx +0 -54
  135. package/src/features/Portal/Document/store/action.ts +0 -114
  136. package/src/features/Portal/Document/store/index.ts +0 -21
  137. package/src/features/Portal/Document/store/initialState.ts +0 -24
  138. package/src/features/Portal/Document/useEnable.ts +0 -8
  139. package/src/features/Portal/FilePreview/useEnable.ts +0 -6
  140. package/src/features/Portal/GroupThread/hook.ts +0 -9
  141. package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
  142. package/src/features/Portal/Notebook/useEnable.ts +0 -6
  143. package/src/features/Portal/Plugins/useEnable.ts +0 -6
  144. package/src/features/Portal/Thread/hook.ts +0 -8
  145. package/src/store/document/slices/notebook/action.ts +0 -119
  146. package/src/store/document/slices/notebook/index.ts +0 -3
  147. package/src/store/document/slices/notebook/initialState.ts +0 -12
  148. package/src/store/document/slices/notebook/selectors.ts +0 -26
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { PortalContent } from '@/features/Portal/router';
9
9
  import { useChatStore } from '@/store/chat';
10
+ import { portalThreadSelectors } from '@/store/chat/selectors';
10
11
 
11
12
  const styles = createStaticStyles(({ css, cssVar }) => ({
12
13
  container: css`
@@ -17,7 +18,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
17
18
  const Layout = () => {
18
19
  const [showMobilePortal, isPortalThread, togglePortal] = useChatStore((s) => [
19
20
  s.showPortal,
20
- !!s.portalThreadId,
21
+ portalThreadSelectors.showThread(s),
21
22
  s.togglePortal,
22
23
  ]);
23
24
  const { t } = useTranslation('portal');
@@ -13,8 +13,8 @@ import { DEFAULT_CHAT_GROUP_CHAT_CONFIG } from '@/const/settings';
13
13
  import { type GroupMemberConfig, chatGroupService } from '@/services/chatGroup';
14
14
  import { useAgentStore } from '@/store/agent';
15
15
  import { useAgentGroupStore } from '@/store/agentGroup';
16
- import { useFileStore } from '@/store/file';
17
16
  import { useHomeStore } from '@/store/home';
17
+ import { usePageStore } from '@/store/page';
18
18
 
19
19
  interface HostConfig {
20
20
  model?: string;
@@ -45,7 +45,7 @@ export const useCreateMenuItems = () => {
45
45
  s.switchToGroup,
46
46
  ]);
47
47
  const [createGroup, loadGroups] = useAgentGroupStore((s) => [s.createGroup, s.loadGroups]);
48
- const createNewPage = useFileStore((s) => s.createNewPage);
48
+ const createNewPage = usePageStore((s) => s.createNewPage);
49
49
 
50
50
  const [isCreatingGroup, setIsCreatingGroup] = useState(false);
51
51
  const [isCreatingSessionGroup, setIsCreatingSessionGroup] = useState(false);
@@ -11,4 +11,3 @@ const Title = memo(() => {
11
11
  Title.displayName = 'PageTitle';
12
12
 
13
13
  export default Title;
14
-
@@ -1 +1,43 @@
1
- export { default } from '../index';
1
+ 'use client';
2
+
3
+ import { useUnmount } from 'ahooks';
4
+ import { Suspense, memo } from 'react';
5
+ import { useParams } from 'react-router-dom';
6
+ import { createStoreUpdater } from 'zustand-utils';
7
+
8
+ import Loading from '@/components/Loading/BrandTextLoading';
9
+ import PageExplorer from '@/features/PageExplorer';
10
+ import { usePageStore } from '@/store/page';
11
+ import { getIdFromIdentifier } from '@/utils/identifier';
12
+
13
+ import PageTitle from '../PageTitle';
14
+
15
+ /**
16
+ * Pages route - dedicated page for managing documents/pages
17
+ * This is extracted from the /resource route to have its own dedicated space
18
+ */
19
+ const PagesPage = memo(() => {
20
+ const storeUpdater = createStoreUpdater(usePageStore);
21
+ const params = useParams<{ id: string }>();
22
+
23
+ const pageId = getIdFromIdentifier(params.id ?? '', 'docs');
24
+ storeUpdater('selectedPageId', pageId);
25
+
26
+ // Clear activeAgentId when unmounting (leaving chat page)
27
+ useUnmount(() => {
28
+ usePageStore.setState({ selectedPageId: undefined });
29
+ });
30
+
31
+ return (
32
+ <>
33
+ <PageTitle />
34
+ <Suspense fallback={<Loading debugId="PagesPage" />}>
35
+ <PageExplorer pageId={pageId} />
36
+ </Suspense>
37
+ </>
38
+ );
39
+ });
40
+
41
+ PagesPage.displayName = 'PagesPage';
42
+
43
+ export default PagesPage;
@@ -6,7 +6,7 @@ import { VList, type VListHandle } from 'virtua';
6
6
 
7
7
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
8
8
  import PageEmpty from '@/features/PageEmpty';
9
- import { documentSelectors, useFileStore } from '@/store/file';
9
+ import { pageSelectors, usePageStore } from '@/store/page';
10
10
  import { type LobeDocument } from '@/types/document';
11
11
 
12
12
  import Item from '../List/Item';
@@ -19,27 +19,27 @@ const Content = memo<ContentProps>(({ searchKeyword }) => {
19
19
  const virtuaRef = useRef<VListHandle>(null);
20
20
  const fetchedCountRef = useRef(-1);
21
21
 
22
- const [hasMore, isLoadingMore, loadMoreDocuments] = useFileStore((s) => [
23
- documentSelectors.hasMoreDocuments(s),
24
- documentSelectors.isLoadingMoreDocuments(s),
22
+ const [hasMore, isLoadingMore, loadMoreDocuments] = usePageStore((s) => [
23
+ pageSelectors.hasMoreDocuments(s),
24
+ pageSelectors.isLoadingMoreDocuments(s),
25
25
  s.loadMoreDocuments,
26
26
  ]);
27
27
 
28
- const allFilteredPages = useFileStore(documentSelectors.getFilteredPages);
28
+ const allFilteredDocuments = usePageStore(pageSelectors.getFilteredDocuments);
29
29
 
30
30
  // Filter by search keyword
31
- const displayPages = useMemo(() => {
32
- if (!searchKeyword.trim()) return allFilteredPages;
31
+ const displayDocuments = useMemo(() => {
32
+ if (!searchKeyword.trim()) return allFilteredDocuments;
33
33
 
34
34
  const keyword = searchKeyword.toLowerCase();
35
- return allFilteredPages.filter((page: LobeDocument) => {
36
- const content = page.content?.toLowerCase() || '';
37
- const title = page.title?.toLowerCase() || '';
35
+ return allFilteredDocuments.filter((doc: LobeDocument) => {
36
+ const content = doc.content?.toLowerCase() || '';
37
+ const title = doc.title?.toLowerCase() || '';
38
38
  return content.includes(keyword) || title.includes(keyword);
39
39
  });
40
- }, [allFilteredPages, searchKeyword]);
40
+ }, [allFilteredDocuments, searchKeyword]);
41
41
 
42
- const count = displayPages.length;
42
+ const count = displayDocuments.length;
43
43
  const isSearching = searchKeyword.trim().length > 0;
44
44
 
45
45
  // Handle scroll - use findItemIndex (official pattern)
@@ -74,9 +74,9 @@ const Content = memo<ContentProps>(({ searchKeyword }) => {
74
74
  ref={virtuaRef}
75
75
  style={{ height: '100%' }}
76
76
  >
77
- {displayPages.map((page) => (
78
- <Flexbox gap={1} key={page.id} padding={'4px 8px'}>
79
- <Item pageId={page.id} />
77
+ {displayDocuments.map((doc) => (
78
+ <Flexbox gap={1} key={doc.id} padding={'4px 8px'}>
79
+ <Item pageId={doc.id} />
80
80
  </Flexbox>
81
81
  ))}
82
82
  {showLoading && (
@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next';
4
4
 
5
5
  import EmojiPicker from '@/components/EmojiPicker';
6
6
  import { useIsDark } from '@/hooks/useIsDark';
7
- import { useFileStore } from '@/store/file';
8
7
  import { useGlobalStore } from '@/store/global';
9
8
  import { globalGeneralSelectors } from '@/store/global/selectors';
9
+ import { usePageStore } from '@/store/page';
10
10
 
11
11
  interface EditingProps {
12
12
  currentEmoji?: string;
@@ -20,7 +20,7 @@ const Editing = memo<EditingProps>(({ documentId, title, currentEmoji, toggleEdi
20
20
  const isDarkMode = useIsDark();
21
21
  const { t } = useTranslation('file');
22
22
 
23
- const editing = useFileStore((s) => s.renamingPageId === documentId);
23
+ const editing = usePageStore((s) => s.renamingPageId === documentId);
24
24
 
25
25
  const [newTitle, setNewTitle] = useState(title);
26
26
  const [newEmoji, setNewEmoji] = useState(currentEmoji);
@@ -35,7 +35,7 @@ const Editing = memo<EditingProps>(({ documentId, title, currentEmoji, toggleEdi
35
35
  if (newTitle && title !== newTitle) updates.title = newTitle;
36
36
  if (newEmoji !== undefined && currentEmoji !== newEmoji) updates.emoji = newEmoji;
37
37
 
38
- await useFileStore.getState().renamePage(documentId, updates.title || title, updates.emoji);
38
+ await usePageStore.getState().renamePage(documentId, updates.title || title, updates.emoji);
39
39
  } catch (error) {
40
40
  console.error('Failed to update page:', error);
41
41
  }
@@ -4,7 +4,7 @@ import { memo, useCallback, useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import NavItem from '@/features/NavPanel/components/NavItem';
7
- import { documentSelectors, useFileStore } from '@/store/file';
7
+ import { pageSelectors, usePageStore } from '@/store/page';
8
8
 
9
9
  import Actions from './Actions';
10
10
  import Editing from './Editing';
@@ -17,18 +17,13 @@ interface DocumentItemProps {
17
17
 
18
18
  const PageListItem = memo<DocumentItemProps>(({ pageId, className }) => {
19
19
  const { t } = useTranslation('file');
20
- const [editing, selectedPageId, document] = useFileStore(
21
- useCallback(
22
- (s) => {
23
- const doc = documentSelectors.getDocumentById(pageId)(s);
24
- return [s.renamingPageId === pageId, s.selectedPageId, doc] as const;
25
- },
26
- [pageId],
27
- ),
28
- );
20
+ const [editing, selectedPageId, document] = usePageStore((s) => {
21
+ const doc = pageSelectors.getDocumentById(pageId)(s);
22
+ return [s.renamingPageId === pageId, s.selectedPageId, doc] as const;
23
+ });
29
24
 
30
- const selectPage = useFileStore((s) => s.selectPage);
31
- const setRenamingPageId = useFileStore((s) => s.setRenamingPageId);
25
+ const selectPage = usePageStore((s) => s.selectPage);
26
+ const setRenamingPageId = usePageStore((s) => s.setRenamingPageId);
32
27
 
33
28
  const active = selectedPageId === pageId;
34
29
  const title = document?.title || t('pageList.untitled');
@@ -4,7 +4,7 @@ import { Copy, CopyPlus, Pencil, Trash2 } from 'lucide-react';
4
4
  import { useCallback } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { useFileStore } from '@/store/file';
7
+ import { usePageStore } from '@/store/page';
8
8
 
9
9
  interface ActionProps {
10
10
  documentContent?: string;
@@ -19,8 +19,8 @@ export const useDropdownMenu = ({
19
19
  }: ActionProps): (() => MenuProps['items']) => {
20
20
  const { t } = useTranslation(['common', 'file']);
21
21
  const { message, modal } = App.useApp();
22
- const removeDocument = useFileStore((s) => s.removeDocument);
23
- const duplicateDocument = useFileStore((s) => s.duplicateDocument);
22
+ const removePage = usePageStore((s) => s.removePage);
23
+ const duplicatePage = usePageStore((s) => s.duplicatePage);
24
24
 
25
25
  const handleDelete = () => {
26
26
  modal.confirm({
@@ -30,7 +30,7 @@ export const useDropdownMenu = ({
30
30
  okText: t('delete'),
31
31
  onOk: async () => {
32
32
  try {
33
- await removeDocument(pageId);
33
+ await removePage(pageId);
34
34
  message.success(t('pageEditor.deleteSuccess', { ns: 'file' }));
35
35
  } catch (error) {
36
36
  console.error('Failed to delete page:', error);
@@ -53,7 +53,7 @@ export const useDropdownMenu = ({
53
53
 
54
54
  const handleDuplicate = async () => {
55
55
  try {
56
- await duplicateDocument(pageId);
56
+ await duplicatePage(pageId);
57
57
  } catch (error) {
58
58
  console.error('Failed to duplicate page:', error);
59
59
  }
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import NavItem from '@/features/NavPanel/components/NavItem';
8
8
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
9
- import { documentSelectors, useFileStore } from '@/store/file';
9
+ import { pageSelectors, usePageStore } from '@/store/page';
10
10
 
11
11
  import Item from './Item';
12
12
 
@@ -16,17 +16,17 @@ import Item from './Item';
16
16
  const PageList = () => {
17
17
  const { t } = useTranslation(['file', 'common']);
18
18
 
19
- const [filteredPages, hasMore, isLoadingMore, openAllPagesDrawer] = useFileStore((s) => [
20
- documentSelectors.getFilteredPagesLimited(s),
21
- documentSelectors.hasMoreFilteredPages(s),
22
- documentSelectors.isLoadingMoreDocuments(s),
19
+ const [filteredDocuments, hasMore, isLoadingMore, openAllPagesDrawer] = usePageStore((s) => [
20
+ pageSelectors.getFilteredDocumentsLimited(s),
21
+ pageSelectors.hasMoreFilteredDocuments(s),
22
+ pageSelectors.isLoadingMoreDocuments(s),
23
23
  s.openAllPagesDrawer,
24
24
  ]);
25
25
 
26
26
  return (
27
27
  <Flexbox gap={1}>
28
- {filteredPages.map((page) => (
29
- <Item key={page.id} pageId={page.id} />
28
+ {filteredDocuments.map((doc) => (
29
+ <Item key={doc.id} pageId={doc.id} />
30
30
  ))}
31
31
  {isLoadingMore && <SkeletonList rows={3} />}
32
32
  {hasMore && !isLoadingMore && (
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import SkeletonList from '@/features/NavPanel/components/SkeletonList';
8
8
  import PageEmpty from '@/features/PageEmpty';
9
- import { documentSelectors, useFileStore } from '@/store/file';
9
+ import { pageSelectors, usePageStore } from '@/store/page';
10
10
 
11
11
  import Actions from './Actions';
12
12
  import AllPagesDrawer from './AllPagesDrawer';
@@ -22,12 +22,18 @@ export enum GroupKey {
22
22
  */
23
23
  const Body = memo(() => {
24
24
  const { t } = useTranslation('file');
25
- const isDocumentListLoading = useFileStore((s) => s.isDocumentListLoading);
26
- const filteredPagesCount = useFileStore(documentSelectors.filteredPagesCount);
27
- const filteredPages = useFileStore(documentSelectors.getFilteredPagesLimited);
28
- const searchKeywords = useFileStore((s) => s.searchKeywords);
25
+
26
+ // Initialize documents list via SWR
27
+ const useFetchDocuments = usePageStore((s) => s.useFetchDocuments);
28
+ useFetchDocuments();
29
+
30
+ const isLoading = usePageStore(pageSelectors.isDocumentsLoading);
31
+
32
+ const filteredDocumentsCount = usePageStore(pageSelectors.filteredDocumentsCount);
33
+ const filteredDocuments = usePageStore(pageSelectors.getFilteredDocumentsLimited);
34
+ const searchKeywords = usePageStore((s) => s.searchKeywords);
29
35
  const dropdownMenu = useDropdownMenu();
30
- const [allPagesDrawerOpen, closeAllPagesDrawer] = useFileStore((s) => [
36
+ const [allPagesDrawerOpen, closeAllPagesDrawer] = usePageStore((s) => [
31
37
  s.allPagesDrawerOpen,
32
38
  s.closeAllPagesDrawer,
33
39
  ]);
@@ -53,16 +59,16 @@ const Body = memo(() => {
53
59
  title={
54
60
  <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
55
61
  {t('pageList.title')}
56
- {filteredPagesCount > 0 && ` ${filteredPagesCount}`}
62
+ {filteredDocumentsCount > 0 && ` ${filteredDocumentsCount}`}
57
63
  </Text>
58
64
  }
59
65
  >
60
66
  <Suspense fallback={<SkeletonList />}>
61
- {isDocumentListLoading ? (
67
+ {isLoading ? (
62
68
  <SkeletonList />
63
69
  ) : (
64
70
  <Flexbox gap={1} paddingBlock={1}>
65
- {filteredPages.length === 0 ? (
71
+ {filteredDocuments.length === 0 ? (
66
72
  <PageEmpty search={Boolean(searchKeywords.trim())} />
67
73
  ) : (
68
74
  <List />
@@ -6,14 +6,14 @@ import { Hash, LucideCheck } from 'lucide-react';
6
6
  import { useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
- import { useFileStore } from '@/store/file';
10
9
  import { useGlobalStore } from '@/store/global';
11
10
  import { systemStatusSelectors } from '@/store/global/selectors';
11
+ import { usePageStore } from '@/store/page';
12
12
 
13
13
  export const useDropdownMenu = (): MenuProps['items'] => {
14
14
  const { t } = useTranslation();
15
- const showOnlyPagesNotInLibrary = useFileStore((s) => s.showOnlyPagesNotInLibrary);
16
- const setShowOnlyPagesNotInLibrary = useFileStore((s) => s.setShowOnlyPagesNotInLibrary);
15
+ const showOnlyPagesNotInLibrary = usePageStore((s) => s.showOnlyPagesNotInLibrary);
16
+ const setShowOnlyPagesNotInLibrary = usePageStore((s) => s.setShowOnlyPagesNotInLibrary);
17
17
 
18
18
  const [pagePageSize, updateSystemStatus] = useGlobalStore((s) => [
19
19
  systemStatusSelectors.pagePageSize(s),
@@ -0,0 +1,15 @@
1
+ import { useNavigate } from 'react-router-dom';
2
+ import { createStoreUpdater } from 'zustand-utils';
3
+
4
+ import { usePageStore } from '@/store/page';
5
+
6
+ const DataSync = () => {
7
+ const usePageStoreUpdater = createStoreUpdater(usePageStore);
8
+
9
+ const navigate = useNavigate();
10
+ usePageStoreUpdater('navigate', navigate);
11
+
12
+ return null;
13
+ };
14
+
15
+ export default DataSync;
@@ -5,12 +5,12 @@ import { SquarePenIcon } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import { useFileStore } from '@/store/file';
8
+ import { usePageStore } from '@/store/page';
9
9
 
10
10
  const AddButton = memo(() => {
11
11
  const { t } = useTranslation('file');
12
12
 
13
- const createNewPage = useFileStore((s) => s.createNewPage);
13
+ const createNewPage = usePageStore((s) => s.createNewPage);
14
14
 
15
15
  const handleNewDocument = () => {
16
16
  const untitledTitle = t('pageList.untitled');
@@ -4,6 +4,7 @@ import { Flexbox } from '@lobehub/ui';
4
4
  import { type FC } from 'react';
5
5
  import { Outlet } from 'react-router-dom';
6
6
 
7
+ import DataSync from './DataSync';
7
8
  import Sidebar from './Sidebar';
8
9
  import { styles } from './style';
9
10
 
@@ -14,6 +15,7 @@ const DesktopPagesLayout: FC = () => {
14
15
  <Flexbox className={styles.mainContainer} flex={1} height={'100%'}>
15
16
  <Outlet />
16
17
  </Flexbox>
18
+ <DataSync />
17
19
  </>
18
20
  );
19
21
  };
@@ -1,26 +1,22 @@
1
1
  'use client';
2
2
 
3
3
  import { Suspense, memo } from 'react';
4
- import { useParams } from 'react-router-dom';
5
4
 
6
5
  import Loading from '@/components/Loading/BrandTextLoading';
7
- import PageExplorer from '@/features/PageExplorer';
8
- import { standardizeIdentifier } from '@/utils/identifier';
6
+ import PageExplorerPlaceholder from '@/features/PageExplorer/PageExplorerPlaceholder';
9
7
 
10
- import PageTitle from './features/PageTitle';
8
+ import PageTitle from './PageTitle';
11
9
 
12
10
  /**
13
11
  * Pages route - dedicated page for managing documents/pages
14
12
  * This is extracted from the /resource route to have its own dedicated space
15
13
  */
16
14
  const PagesPage = memo(() => {
17
- const { id } = useParams<{ id: string }>();
18
-
19
15
  return (
20
16
  <>
21
17
  <PageTitle />
22
18
  <Suspense fallback={<Loading debugId="PagesPage" />}>
23
- <PageExplorer pageId={standardizeIdentifier(id ?? '', 'docs')} />
19
+ <PageExplorerPlaceholder />
24
20
  </Suspense>
25
21
  </>
26
22
  );
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
10
10
  dayjs.extend(relativeTime);
11
11
 
12
12
  interface AutoSaveHintProps {
13
- lastUpdatedTime?: Date | null;
13
+ lastUpdatedTime?: string | Date | null;
14
14
  saveStatus: 'idle' | 'saving' | 'saved';
15
15
  style?: CSSProperties;
16
16
  }
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
  import { useUserStore } from '@/store/user';
8
8
 
9
9
  import { useConversationStore } from '../../../../../store';
10
- import { useGroupMessage } from '../../../components/GroupContext';
10
+ import { useMessageAggregationContext } from '../../../../Contexts/MessageAggregationContext';
11
11
  import { type ApprovalMode } from './index';
12
12
 
13
13
  interface ApprovalActionsProps {
@@ -34,7 +34,7 @@ const ApprovalActions = memo<ApprovalActionsProps>(
34
34
  // Disable actions while message is still being created (temp ID)
35
35
  const isMessageCreating = messageId.startsWith('tmp_');
36
36
 
37
- const { assistantGroupId } = useGroupMessage();
37
+ const { assistantGroupId } = useMessageAggregationContext();
38
38
  const [approveToolCall, rejectToolCall, rejectAndContinueToolCall] = useConversationStore(
39
39
  (s) => [s.approveToolCall, s.rejectToolCall, s.rejectAndContinueToolCall],
40
40
  );
@@ -12,6 +12,7 @@ import { toolSelectors } from '@/store/tool/selectors';
12
12
  import { getBuiltinRender } from '@/tools/renders';
13
13
  import { getBuiltinStreaming } from '@/tools/streamings';
14
14
 
15
+ import { ToolErrorBoundary } from '../../Tool/ErrorBoundary';
15
16
  import Actions from './Actions';
16
17
  import Inspectors from './Inspector';
17
18
 
@@ -145,21 +146,23 @@ const Tool = memo<GroupToolProps>(
145
146
  type={type}
146
147
  />
147
148
  )}
148
- <Render
149
- apiName={apiName}
150
- arguments={requestArgs}
151
- identifier={identifier}
152
- intervention={intervention}
153
- isArgumentsStreaming={isArgumentsStreaming}
154
- isToolCalling={isToolCalling}
155
- messageId={assistantMessageId}
156
- result={result}
157
- setShowPluginRender={setShowPluginRender}
158
- showPluginRender={showPluginRender}
159
- toolCallId={id}
160
- toolMessageId={toolMessageId}
161
- type={type}
162
- />
149
+ <ToolErrorBoundary apiName={apiName} identifier={identifier}>
150
+ <Render
151
+ apiName={apiName}
152
+ arguments={requestArgs}
153
+ identifier={identifier}
154
+ intervention={intervention}
155
+ isArgumentsStreaming={isArgumentsStreaming}
156
+ isToolCalling={isToolCalling}
157
+ messageId={assistantMessageId}
158
+ result={result}
159
+ setShowPluginRender={setShowPluginRender}
160
+ showPluginRender={showPluginRender}
161
+ toolCallId={id}
162
+ toolMessageId={toolMessageId}
163
+ type={type}
164
+ />
165
+ </ToolErrorBoundary>
163
166
  <Divider dashed style={{ marginBottom: 0, marginTop: 8 }} />
164
167
  </Flexbox>
165
168
  </AccordionItem>
@@ -6,8 +6,8 @@ import { memo, useMemo } from 'react';
6
6
  import { type AssistantContentBlock } from '@/types/index';
7
7
 
8
8
  import { messageStateSelectors, useConversationStore } from '../../../store';
9
+ import { MessageAggregationContext } from '../../Contexts/MessageAggregationContext';
9
10
  import { CollapsedMessage } from './CollapsedMessage';
10
- import { GroupMessageContext } from './GroupContext';
11
11
  import GroupItem from './GroupItem';
12
12
 
13
13
  const styles = createStaticStyles(({ css }) => {
@@ -44,7 +44,7 @@ const Group = memo<GroupChildrenProps>(
44
44
  );
45
45
  }
46
46
  return (
47
- <GroupMessageContext value={contextValue}>
47
+ <MessageAggregationContext value={contextValue}>
48
48
  <Flexbox className={styles.container} gap={8}>
49
49
  {blocks.map((item) => {
50
50
  return (
@@ -59,7 +59,7 @@ const Group = memo<GroupChildrenProps>(
59
59
  );
60
60
  })}
61
61
  </Flexbox>
62
- </GroupMessageContext>
62
+ </MessageAggregationContext>
63
63
  );
64
64
  },
65
65
  isEqual,
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ interface GroupMessageContextValue {
4
+ assistantGroupId: string;
5
+ }
6
+
7
+ export const MessageAggregationContext = createContext<GroupMessageContextValue | null>(null);
8
+
9
+ export const useMessageAggregationContext = () => {
10
+ const context = useContext(MessageAggregationContext);
11
+ if (!context) {
12
+ throw new Error('useMessageAggregationContext must be used within MessageAggregationContext');
13
+ }
14
+ return context;
15
+ };
@@ -6,9 +6,9 @@ import { memo, useMemo } from 'react';
6
6
  import { type AssistantContentBlock } from '@/types/index';
7
7
 
8
8
  import { messageStateSelectors, useConversationStore } from '../../../store';
9
+ import { MessageAggregationContext } from '../../Contexts/MessageAggregationContext';
9
10
  import { CollapsedMessage } from './CollapsedMessage';
10
11
  import ContentBlock from './ContentBlock';
11
- import { GroupMessageContext } from './GroupContext';
12
12
 
13
13
  const styles = createStaticStyles(({ css }) => {
14
14
  return {
@@ -43,13 +43,13 @@ const Group = memo<GroupChildrenProps>(({ blocks, id, content }) => {
43
43
  );
44
44
  }
45
45
  return (
46
- <GroupMessageContext value={contextValue}>
46
+ <MessageAggregationContext value={contextValue}>
47
47
  <Flexbox className={styles.container} gap={8}>
48
48
  {blocks.map((item) => {
49
49
  return <ContentBlock {...item} key={id + '.' + item.id} />;
50
50
  })}
51
51
  </Flexbox>
52
- </GroupMessageContext>
52
+ </MessageAggregationContext>
53
53
  );
54
54
  }, isEqual);
55
55
 
@@ -76,7 +76,11 @@ export const UserActionsBar = memo<UserActionsProps>(({ actionsConfig, id, data
76
76
  // Use external config if provided, otherwise use defaults
77
77
  // Append extra actions from factories
78
78
  const barItems = useMemo(() => {
79
- const base = actionsConfig?.bar ?? [defaultActions.regenerate, defaultActions.edit];
79
+ const base = actionsConfig?.bar ?? [
80
+ defaultActions.regenerate,
81
+ defaultActions.edit,
82
+ defaultActions.copy,
83
+ ];
80
84
  return [...base, ...extraBarItems];
81
85
  }, [actionsConfig?.bar, defaultActions.regenerate, defaultActions.edit, extraBarItems]);
82
86
 
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import { type CSSProperties, memo } from 'react';
4
+
5
+ import AutoSaveHintBase from '@/components/Editor/AutoSaveHint';
6
+ import { useDocumentStore } from '@/store/document';
7
+ import { editorSelectors } from '@/store/document/slices/editor';
8
+
9
+ export interface AutoSaveHintProps {
10
+ /**
11
+ * Document ID to get save status from DocumentStore
12
+ */
13
+ documentId: string;
14
+ /**
15
+ * Custom styles
16
+ */
17
+ style?: CSSProperties;
18
+ }
19
+
20
+ /**
21
+ * AutoSave hint component that reads from DocumentStore
22
+ * Use this component externally to display save status for a document
23
+ */
24
+ const AutoSaveHint = memo<AutoSaveHintProps>(({ documentId, style }) => {
25
+ const saveStatus = useDocumentStore((s) => editorSelectors.saveStatus(documentId)(s));
26
+ const lastUpdatedTime = useDocumentStore(
27
+ (s) => editorSelectors.lastUpdatedTime(documentId)(s) ?? null,
28
+ );
29
+
30
+ return (
31
+ <AutoSaveHintBase lastUpdatedTime={lastUpdatedTime} saveStatus={saveStatus} style={style} />
32
+ );
33
+ });
34
+
35
+ AutoSaveHint.displayName = 'AutoSaveHint';
36
+
37
+ export default AutoSaveHint;