@lobehub/lobehub 2.0.0-next.255 → 2.0.0-next.257

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 (144) 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/User/Actions/index.tsx +5 -1
  35. package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
  36. package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
  37. package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
  38. package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
  39. package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
  40. package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
  41. package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
  42. package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
  43. package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
  44. package/src/features/EditorCanvas/index.ts +9 -0
  45. package/src/features/PageEditor/Copilot/index.tsx +16 -1
  46. package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
  47. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
  48. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
  49. package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
  50. package/src/features/PageEditor/Header/index.tsx +15 -18
  51. package/src/features/PageEditor/Header/useMenu.tsx +12 -9
  52. package/src/features/PageEditor/PageEditor.tsx +45 -21
  53. package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
  54. package/src/features/PageEditor/PageTitle/index.tsx +2 -2
  55. package/src/features/PageEditor/StoreUpdater.tsx +35 -308
  56. package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
  57. package/src/features/PageEditor/store/action.ts +96 -188
  58. package/src/features/PageEditor/store/initialState.ts +16 -21
  59. package/src/features/PageEditor/store/selectors.ts +3 -4
  60. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
  61. package/src/features/PageExplorer/index.tsx +34 -67
  62. package/src/features/Portal/Artifacts/index.ts +0 -2
  63. package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
  64. package/src/features/Portal/Document/Body.tsx +1 -3
  65. package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
  66. package/src/features/Portal/Document/Header.tsx +13 -10
  67. package/src/features/Portal/Document/TodoList.tsx +6 -4
  68. package/src/features/Portal/Document/Wrapper.tsx +3 -11
  69. package/src/features/Portal/Document/index.ts +0 -2
  70. package/src/features/Portal/FilePreview/index.ts +0 -2
  71. package/src/features/Portal/GroupThread/index.ts +0 -3
  72. package/src/features/Portal/MessageDetail/index.ts +0 -2
  73. package/src/features/Portal/Notebook/index.ts +0 -2
  74. package/src/features/Portal/Plugins/index.ts +0 -2
  75. package/src/features/Portal/Thread/index.ts +0 -3
  76. package/src/features/Portal/components/Header.tsx +18 -6
  77. package/src/features/Portal/router.tsx +34 -97
  78. package/src/features/Portal/type.ts +0 -2
  79. package/src/features/RightPanel/index.tsx +11 -2
  80. package/src/locales/default/plugin.ts +1 -0
  81. package/src/store/chat/slices/portal/action.test.ts +218 -15
  82. package/src/store/chat/slices/portal/action.ts +194 -41
  83. package/src/store/chat/slices/portal/initialState.ts +40 -1
  84. package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
  85. package/src/store/chat/slices/portal/selectors.test.ts +119 -17
  86. package/src/store/chat/slices/portal/selectors.ts +117 -36
  87. package/src/store/document/index.ts +17 -5
  88. package/src/store/document/slices/document/action.ts +209 -0
  89. package/src/store/document/slices/document/index.ts +6 -0
  90. package/src/store/document/slices/editor/action.test.ts +340 -0
  91. package/src/store/document/slices/editor/action.ts +133 -149
  92. package/src/store/document/slices/editor/index.ts +9 -2
  93. package/src/store/document/slices/editor/initialState.ts +66 -29
  94. package/src/store/document/slices/editor/reducer.test.ts +217 -0
  95. package/src/store/document/slices/editor/reducer.ts +67 -0
  96. package/src/store/document/slices/editor/selectors.test.ts +395 -0
  97. package/src/store/document/slices/editor/selectors.ts +107 -5
  98. package/src/store/document/store.ts +12 -13
  99. package/src/store/file/slices/document/action.ts +19 -188
  100. package/src/store/file/slices/document/initialState.ts +0 -30
  101. package/src/store/file/slices/document/selectors.ts +25 -59
  102. package/src/store/global/initialState.ts +2 -0
  103. package/src/store/global/selectors/systemStatus.ts +2 -0
  104. package/src/store/notebook/index.ts +5 -4
  105. package/src/store/page/index.ts +2 -0
  106. package/src/store/page/initialState.ts +92 -0
  107. package/src/store/page/selectors.ts +5 -0
  108. package/src/store/page/slices/crud/action.ts +477 -0
  109. package/src/store/page/slices/crud/index.ts +2 -0
  110. package/src/store/page/slices/crud/initialState.ts +7 -0
  111. package/src/store/page/slices/internal/action.ts +32 -0
  112. package/src/store/page/slices/internal/index.ts +2 -0
  113. package/src/store/page/slices/internal/reducer.ts +105 -0
  114. package/src/store/page/slices/list/action.ts +206 -0
  115. package/src/store/page/slices/list/index.ts +3 -0
  116. package/src/store/page/slices/list/initialState.ts +29 -0
  117. package/src/store/page/slices/list/selectors.ts +90 -0
  118. package/src/store/page/slices/selection/action.ts +67 -0
  119. package/src/store/page/slices/selection/index.ts +2 -0
  120. package/src/store/page/slices/selection/initialState.ts +11 -0
  121. package/src/store/page/store.ts +29 -0
  122. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
  123. package/src/utils/identifier.ts +8 -2
  124. package/src/features/PageEditor/Body/index.tsx +0 -68
  125. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
  126. package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
  127. package/src/features/Portal/Artifacts/useEnable.ts +0 -4
  128. package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
  129. package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
  130. package/src/features/Portal/Document/Title.tsx +0 -54
  131. package/src/features/Portal/Document/store/action.ts +0 -114
  132. package/src/features/Portal/Document/store/index.ts +0 -21
  133. package/src/features/Portal/Document/store/initialState.ts +0 -24
  134. package/src/features/Portal/Document/useEnable.ts +0 -8
  135. package/src/features/Portal/FilePreview/useEnable.ts +0 -6
  136. package/src/features/Portal/GroupThread/hook.ts +0 -9
  137. package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
  138. package/src/features/Portal/Notebook/useEnable.ts +0 -6
  139. package/src/features/Portal/Plugins/useEnable.ts +0 -6
  140. package/src/features/Portal/Thread/hook.ts +0 -8
  141. package/src/store/document/slices/notebook/action.ts +0 -119
  142. package/src/store/document/slices/notebook/index.ts +0 -3
  143. package/src/store/document/slices/notebook/initialState.ts +0 -12
  144. package/src/store/document/slices/notebook/selectors.ts +0 -26
@@ -0,0 +1,206 @@
1
+ import type { SWRResponse } from 'swr';
2
+ import { type StateCreator } from 'zustand/vanilla';
3
+
4
+ import { useClientDataSWRWithSync } from '@/libs/swr';
5
+ import { documentService } from '@/services/document';
6
+ import { useGlobalStore } from '@/store/global';
7
+ import { type LobeDocument } from '@/types/document';
8
+ import { setNamespace } from '@/utils/storeDebug';
9
+
10
+ import { type PageQueryFilter } from '../../initialState';
11
+ import { type PageStore } from '../../store';
12
+
13
+ const n = setNamespace('page/list');
14
+
15
+ const ALLOWED_PAGE_SOURCE_TYPES = new Set(['editor', 'file', 'api']);
16
+ const ALLOWED_PAGE_FILE_TYPES = new Set(['custom/document', 'application/pdf']);
17
+
18
+ /**
19
+ * Check if a page should be displayed in the page list
20
+ */
21
+ const isAllowedPage = (page: { fileType: string; sourceType: string }) => {
22
+ return (
23
+ ALLOWED_PAGE_SOURCE_TYPES.has(page.sourceType) && ALLOWED_PAGE_FILE_TYPES.has(page.fileType)
24
+ );
25
+ };
26
+
27
+ export interface ListAction {
28
+ /**
29
+ * Fetch documents from the server with pagination
30
+ */
31
+ fetchDocuments: () => Promise<void>;
32
+ /**
33
+ * Load more documents (next page)
34
+ */
35
+ loadMoreDocuments: () => Promise<void>;
36
+ /**
37
+ * Refresh document list (re-fetch from server)
38
+ */
39
+ refreshDocuments: () => Promise<void>;
40
+ /**
41
+ * Set search keywords
42
+ */
43
+ setSearchKeywords: (keywords: string) => void;
44
+ /**
45
+ * Toggle filter to show only pages not in any library
46
+ */
47
+ setShowOnlyPagesNotInLibrary: (show: boolean) => void;
48
+ /**
49
+ * SWR hook to fetch documents list with caching and auto-sync to store
50
+ */
51
+ useFetchDocuments: () => SWRResponse<LobeDocument[]>;
52
+ }
53
+
54
+ export const createListSlice: StateCreator<
55
+ PageStore,
56
+ [['zustand/devtools', never]],
57
+ [],
58
+ ListAction
59
+ > = (set, get) => ({
60
+ fetchDocuments: async () => {
61
+ try {
62
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
63
+ const queryFilters: PageQueryFilter = {
64
+ fileTypes: Array.from(ALLOWED_PAGE_FILE_TYPES),
65
+ sourceTypes: Array.from(ALLOWED_PAGE_SOURCE_TYPES),
66
+ };
67
+
68
+ const result = await documentService.queryDocuments({
69
+ current: 0,
70
+ pageSize,
71
+ ...queryFilters,
72
+ });
73
+
74
+ const documents = result.items.filter(isAllowedPage).map((doc) => ({
75
+ ...doc,
76
+ filename: doc.filename ?? doc.title ?? 'Untitled',
77
+ })) as LobeDocument[];
78
+
79
+ const hasMore = result.items.length >= pageSize;
80
+
81
+ // Use internal dispatch to set documents
82
+ get().internal_dispatchDocuments({ documents, type: 'setDocuments' });
83
+
84
+ set(
85
+ {
86
+ currentPage: 0,
87
+ documentsTotal: result.total,
88
+ hasMoreDocuments: hasMore,
89
+ queryFilter: queryFilters,
90
+ },
91
+ false,
92
+ n('fetchDocuments/success'),
93
+ );
94
+ } catch (error) {
95
+ console.error('Failed to fetch documents:', error);
96
+ throw error;
97
+ }
98
+ },
99
+
100
+ loadMoreDocuments: async () => {
101
+ const { currentPage, isLoadingMoreDocuments, hasMoreDocuments, queryFilter, documents } = get();
102
+
103
+ if (isLoadingMoreDocuments || !hasMoreDocuments || !documents) return;
104
+
105
+ const nextPage = currentPage + 1;
106
+
107
+ set({ isLoadingMoreDocuments: true }, false, n('loadMoreDocuments/start'));
108
+
109
+ try {
110
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
111
+ const queryParams = queryFilter
112
+ ? { current: nextPage, pageSize, ...queryFilter }
113
+ : { current: nextPage, pageSize };
114
+
115
+ const result = await documentService.queryDocuments(queryParams);
116
+
117
+ const newDocuments = result.items.filter(isAllowedPage).map((doc) => ({
118
+ ...doc,
119
+ filename: doc.filename ?? doc.title ?? 'Untitled',
120
+ })) as LobeDocument[];
121
+
122
+ const hasMore = result.items.length >= pageSize;
123
+
124
+ // Use internal dispatch to append documents
125
+ get().internal_dispatchDocuments({ documents: newDocuments, type: 'appendDocuments' });
126
+
127
+ set(
128
+ {
129
+ currentPage: nextPage,
130
+ documentsTotal: result.total,
131
+ hasMoreDocuments: hasMore,
132
+ isLoadingMoreDocuments: false,
133
+ },
134
+ false,
135
+ n('loadMoreDocuments/success'),
136
+ );
137
+ } catch (error) {
138
+ console.error('Failed to load more documents:', error);
139
+ set({ isLoadingMoreDocuments: false }, false, n('loadMoreDocuments/error'));
140
+ }
141
+ },
142
+
143
+ refreshDocuments: async () => {
144
+ await get().fetchDocuments();
145
+ },
146
+
147
+ setSearchKeywords: (keywords: string) => {
148
+ set({ searchKeywords: keywords }, false, n('setSearchKeywords'));
149
+ },
150
+
151
+ setShowOnlyPagesNotInLibrary: (show: boolean) => {
152
+ set({ showOnlyPagesNotInLibrary: show }, false, n('setShowOnlyPagesNotInLibrary'));
153
+ },
154
+
155
+ useFetchDocuments: () => {
156
+ return useClientDataSWRWithSync<LobeDocument[]>(
157
+ ['pageDocuments'],
158
+ async () => {
159
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
160
+ const queryFilters: PageQueryFilter = {
161
+ fileTypes: Array.from(ALLOWED_PAGE_FILE_TYPES),
162
+ sourceTypes: Array.from(ALLOWED_PAGE_SOURCE_TYPES),
163
+ };
164
+
165
+ const result = await documentService.queryDocuments({
166
+ current: 0,
167
+ pageSize,
168
+ ...queryFilters,
169
+ });
170
+
171
+ const documents = result.items.filter(isAllowedPage).map((doc) => ({
172
+ ...doc,
173
+ filename: doc.filename ?? doc.title ?? 'Untitled',
174
+ })) as LobeDocument[];
175
+
176
+ return documents;
177
+ },
178
+ {
179
+ onData: (documents) => {
180
+ if (!documents) return;
181
+
182
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
183
+ const hasMore = documents.length >= pageSize;
184
+
185
+ // Use internal dispatch to set documents
186
+ get().internal_dispatchDocuments({ documents, type: 'setDocuments' });
187
+
188
+ set(
189
+ {
190
+ currentPage: 0,
191
+ documentsTotal: documents.length,
192
+ hasMoreDocuments: hasMore,
193
+ queryFilter: {
194
+ fileTypes: Array.from(ALLOWED_PAGE_FILE_TYPES),
195
+ sourceTypes: Array.from(ALLOWED_PAGE_SOURCE_TYPES),
196
+ },
197
+ },
198
+ false,
199
+ n('useFetchDocuments/onData'),
200
+ );
201
+ },
202
+ revalidateOnFocus: true,
203
+ },
204
+ );
205
+ },
206
+ });
@@ -0,0 +1,3 @@
1
+ export { createListSlice, type ListAction } from './action';
2
+ export { initialListState, type ListState } from './initialState';
3
+ export { listSelectors } from './selectors';
@@ -0,0 +1,29 @@
1
+ import { type LobeDocument } from '@/types/document';
2
+
3
+ import { type PageQueryFilter } from '../../initialState';
4
+
5
+ export interface ListState {
6
+ currentPage: number;
7
+ documents: LobeDocument[];
8
+ documentsTotal: number;
9
+ hasMoreDocuments: boolean;
10
+ isDocumentListLoading: boolean;
11
+ isLoadingMoreDocuments: boolean;
12
+ localPageMap: Map<string, LobeDocument>;
13
+ queryFilter?: PageQueryFilter;
14
+ searchKeywords: string;
15
+ showOnlyPagesNotInLibrary: boolean;
16
+ }
17
+
18
+ export const initialListState: ListState = {
19
+ currentPage: 0,
20
+ documents: [],
21
+ documentsTotal: 0,
22
+ hasMoreDocuments: false,
23
+ isDocumentListLoading: false,
24
+ isLoadingMoreDocuments: false,
25
+ localPageMap: new Map(),
26
+ queryFilter: undefined,
27
+ searchKeywords: '',
28
+ showOnlyPagesNotInLibrary: false,
29
+ };
@@ -0,0 +1,90 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { type LobeDocument } from '@/types/document';
3
+
4
+ import { type PageState } from '../../initialState';
5
+
6
+ /**
7
+ * Check if documents are still loading (undefined means not yet loaded)
8
+ */
9
+ const isDocumentsLoading = (s: PageState): boolean => s.documents === undefined;
10
+
11
+ const getFilteredDocuments = (s: PageState): LobeDocument[] => {
12
+ const docs = s.documents ?? [];
13
+
14
+ const { searchKeywords, showOnlyPagesNotInLibrary } = s;
15
+
16
+ let result = docs;
17
+
18
+ // Filter out documents with sourceType='file'
19
+ result = result.filter((doc: LobeDocument) => doc.sourceType !== 'file');
20
+
21
+ // Filter by library membership
22
+ if (showOnlyPagesNotInLibrary) {
23
+ result = result.filter((doc: LobeDocument) => {
24
+ // Show only pages that are NOT in any library
25
+ // Pages in a library have metadata.knowledgeBaseId set
26
+ return !doc.metadata?.knowledgeBaseId;
27
+ });
28
+ }
29
+
30
+ // Filter by search keywords
31
+ if (searchKeywords.trim()) {
32
+ const lowerKeywords = searchKeywords.toLowerCase();
33
+ result = result.filter((doc: LobeDocument) => {
34
+ const content = doc.content?.toLowerCase() || '';
35
+ const title = doc.title?.toLowerCase() || '';
36
+ return content.includes(lowerKeywords) || title.includes(lowerKeywords);
37
+ });
38
+ }
39
+
40
+ // Sort by creation date (newest first)
41
+ return result.sort((a: LobeDocument, b: LobeDocument) => {
42
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
43
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
44
+ return dateB - dateA;
45
+ });
46
+ };
47
+
48
+ // Limited filtered documents for sidebar display
49
+ const getFilteredDocumentsLimited = (s: PageState): LobeDocument[] => {
50
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
51
+ const allDocs = getFilteredDocuments(s);
52
+ return allDocs.slice(0, pageSize);
53
+ };
54
+
55
+ const getDocumentById = (docId: string | undefined) => (s: PageState) => {
56
+ if (!docId) return undefined;
57
+
58
+ // Find in documents array
59
+ return s.documents?.find((doc) => doc.id === docId);
60
+ };
61
+
62
+ const hasMoreDocuments = (s: PageState): boolean => s.hasMoreDocuments;
63
+
64
+ const isLoadingMoreDocuments = (s: PageState): boolean => s.isLoadingMoreDocuments;
65
+
66
+ const documentsTotal = (s: PageState): number => s.documentsTotal;
67
+
68
+ // Check if filtered documents have more than displayed
69
+ const hasMoreFilteredDocuments = (s: PageState): boolean => {
70
+ const pageSize = useGlobalStore.getState().status.pagePageSize || 20;
71
+ const allDocs = getFilteredDocuments(s);
72
+ return allDocs.length > pageSize;
73
+ };
74
+
75
+ // Get total count of filtered documents
76
+ const filteredDocumentsCount = (s: PageState): number => {
77
+ return getFilteredDocuments(s).length;
78
+ };
79
+
80
+ export const listSelectors = {
81
+ documentsTotal,
82
+ filteredDocumentsCount,
83
+ getDocumentById,
84
+ getFilteredDocuments,
85
+ getFilteredDocumentsLimited,
86
+ hasMoreDocuments,
87
+ hasMoreFilteredDocuments,
88
+ isDocumentsLoading,
89
+ isLoadingMoreDocuments,
90
+ };
@@ -0,0 +1,67 @@
1
+ import { type StateCreator } from 'zustand/vanilla';
2
+
3
+ import { setNamespace } from '@/utils/storeDebug';
4
+
5
+ import { type PageStore } from '../../store';
6
+
7
+ const n = setNamespace('page/selection');
8
+
9
+ export interface SelectionAction {
10
+ /**
11
+ * Close all pages drawer
12
+ */
13
+ closeAllPagesDrawer: () => void;
14
+ /**
15
+ * Open all pages drawer
16
+ */
17
+ openAllPagesDrawer: () => void;
18
+ /**
19
+ * Select a page (updates URL and state)
20
+ */
21
+ selectPage: (pageId: string) => void;
22
+ /**
23
+ * Set the ID of the page being renamed
24
+ */
25
+ setRenamingPageId: (pageId: string | null) => void;
26
+ /**
27
+ * Set selected page ID
28
+ */
29
+ setSelectedPageId: (pageId: string | null, shouldNavigate?: boolean) => void;
30
+ }
31
+
32
+ export const createSelectionSlice: StateCreator<
33
+ PageStore,
34
+ [['zustand/devtools', never]],
35
+ [],
36
+ SelectionAction
37
+ > = (set, get) => ({
38
+ closeAllPagesDrawer: () => {
39
+ set({ allPagesDrawerOpen: false }, false, n('closeAllPagesDrawer'));
40
+ },
41
+
42
+ openAllPagesDrawer: () => {
43
+ set({ allPagesDrawerOpen: true }, false, n('openAllPagesDrawer'));
44
+ },
45
+
46
+ selectPage: (pageId: string) => {
47
+ const { selectedPageId } = get();
48
+
49
+ // Don't allow deselecting the current page
50
+ if (selectedPageId === pageId) return;
51
+
52
+ // Select and navigate
53
+ set({ isCreatingNew: false, selectedPageId: pageId }, false, n('selectPage'));
54
+ get().navigateToPage(pageId);
55
+ },
56
+
57
+ setRenamingPageId: (pageId: string | null) => {
58
+ set({ renamingPageId: pageId }, false, n('setRenamingPageId'));
59
+ },
60
+
61
+ setSelectedPageId: (pageId: string | null, shouldNavigate = true) => {
62
+ set({ selectedPageId: pageId }, false, n('setSelectedPageId'));
63
+ if (shouldNavigate) {
64
+ get().navigateToPage(pageId);
65
+ }
66
+ },
67
+ });
@@ -0,0 +1,2 @@
1
+ export { createSelectionSlice, type SelectionAction } from './action';
2
+ export { initialSelectionState, type SelectionState } from './initialState';
@@ -0,0 +1,11 @@
1
+ export interface SelectionState {
2
+ allPagesDrawerOpen: boolean;
3
+ renamingPageId: string | null;
4
+ selectedPageId: string | null;
5
+ }
6
+
7
+ export const initialSelectionState: SelectionState = {
8
+ allPagesDrawerOpen: false,
9
+ renamingPageId: null,
10
+ selectedPageId: null,
11
+ };
@@ -0,0 +1,29 @@
1
+ import { shallow } from 'zustand/shallow';
2
+ import { createWithEqualityFn } from 'zustand/traditional';
3
+ import { type StateCreator } from 'zustand/vanilla';
4
+
5
+ import { createDevtools } from '../middleware/createDevtools';
6
+ import { type PageState, initialState } from './initialState';
7
+ import { type CrudAction, createCrudSlice } from './slices/crud';
8
+ import { type InternalAction, createInternalSlice } from './slices/internal';
9
+ import { type ListAction, createListSlice } from './slices/list';
10
+ import { type SelectionAction, createSelectionSlice } from './slices/selection';
11
+
12
+ // =============== Aggregate createStoreFn ============ //
13
+
14
+ export type PageStore = PageState & InternalAction & ListAction & SelectionAction & CrudAction;
15
+
16
+ const createStore: StateCreator<PageStore, [['zustand/devtools', never]]> = (...parameters) => ({
17
+ ...initialState,
18
+ ...createInternalSlice(...parameters),
19
+ ...createListSlice(...parameters),
20
+ ...createSelectionSlice(...parameters),
21
+ ...createCrudSlice(...parameters),
22
+ });
23
+
24
+ // =============== Implement useStore ============ //
25
+ const devtools = createDevtools('page');
26
+
27
+ export const usePageStore = createWithEqualityFn<PageStore>()(devtools(createStore), shallow);
28
+
29
+ export const getPageStoreState = () => usePageStore.getState();
@@ -120,26 +120,16 @@ export const lobehubSkillStoreSelectors = {
120
120
  */
121
121
  metaList: (s: ToolStoreState) => {
122
122
  const servers = s.lobehubSkillServers || [];
123
- const result = servers
123
+
124
+ return servers
124
125
  .filter((server) => server.status === LobehubSkillStatus.CONNECTED)
125
- .map((server) => {
126
- // Debug logging
127
- console.log('[lobehubSkillStoreSelectors.metaList] server:', {
128
- icon: server.icon,
129
- identifier: server.identifier,
130
- name: server.name,
131
- status: server.status,
132
- });
133
- return {
134
- identifier: server.identifier,
135
- meta: {
136
- avatar: server.icon || '🔗',
137
- description: `LobeHub Skill: ${server.name}`,
138
- title: server.name,
139
- },
140
- };
141
- });
142
- console.log('[lobehubSkillStoreSelectors.metaList] result:', result);
143
- return result;
126
+ .map((server) => ({
127
+ identifier: server.identifier,
128
+ meta: {
129
+ avatar: server.icon || '🔗',
130
+ description: `LobeHub Skill: ${server.name}`,
131
+ title: server.name,
132
+ },
133
+ }));
144
134
  },
145
135
  };
@@ -4,7 +4,7 @@
4
4
  * @returns The standardized identifier.
5
5
  */
6
6
 
7
- const standardizeIdentifier = (identifier: string, prefix?: 'docs' | 'agt') => {
7
+ export const standardizeIdentifier = (identifier: string, prefix?: 'docs' | 'agt') => {
8
8
  if (identifier.includes('_')) {
9
9
  return identifier.split('_')[1];
10
10
  } else if (prefix) {
@@ -14,4 +14,10 @@ const standardizeIdentifier = (identifier: string, prefix?: 'docs' | 'agt') => {
14
14
  return identifier;
15
15
  };
16
16
 
17
- export { standardizeIdentifier };
17
+ export const getIdFromIdentifier = (identifier: string, prefix?: 'docs' | 'agt') => {
18
+ if (identifier.includes('_')) {
19
+ return identifier;
20
+ }
21
+
22
+ return `${prefix}_${identifier}`;
23
+ };
@@ -1,68 +0,0 @@
1
- 'use client';
2
-
3
- import { Flexbox, Skeleton } from '@lobehub/ui';
4
- import { createStaticStyles } from 'antd-style';
5
- import { memo, useEffect, useState } from 'react';
6
-
7
- import EditorCanvas from '../EditorCanvas';
8
- import { usePageEditorStore } from '../store';
9
- import Title from './Title';
10
-
11
- const styles = createStaticStyles(({ css, cssVar }) => ({
12
- loadingOverlay: css`
13
- position: absolute;
14
- z-index: 10;
15
- inset: 0;
16
-
17
- padding: 24px;
18
-
19
- background: ${cssVar.colorBgContainer};
20
- `,
21
- }));
22
-
23
- const Body = memo(() => {
24
- const isLoadingContent = usePageEditorStore((s) => s.isLoadingContent);
25
- const [showSkeleton, setShowSkeleton] = useState(false);
26
-
27
- // Only show skeleton if loading takes more than 1 second
28
- useEffect(() => {
29
- // eslint-disable-next-line no-undef
30
- let timer: NodeJS.Timeout;
31
-
32
- if (isLoadingContent) {
33
- timer = setTimeout(() => {
34
- setShowSkeleton(true);
35
- }, 1000);
36
- } else {
37
- setShowSkeleton(false);
38
- }
39
-
40
- return () => {
41
- if (timer) clearTimeout(timer);
42
- };
43
- }, [isLoadingContent]);
44
-
45
- return (
46
- <Flexbox flex={1} style={{ overflowY: 'auto', position: 'relative' }}>
47
- <Title />
48
- <EditorCanvas />
49
- {/* Show overlay immediately to hide old content */}
50
- {isLoadingContent && (
51
- <div className={styles.loadingOverlay}>
52
- {/* Only show skeleton after 1 second */}
53
- {showSkeleton && (
54
- <Flexbox gap={16}>
55
- <Skeleton.Title width="80%" />
56
- <Skeleton.Title width="60%" />
57
- <Skeleton.Title width="70%" />
58
- <Skeleton.Title width="50%" />
59
- <Skeleton.Title width="65%" />
60
- </Flexbox>
61
- )}
62
- </div>
63
- )}
64
- </Flexbox>
65
- );
66
- });
67
-
68
- export default Body;