documentStore.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /**
  2. * Document Store Module
  3. *
  4. * Zustand store for managing document state and operations.
  5. * Handles:
  6. * - Current document state (currentDocument)
  7. * - Document list with pagination
  8. * - Save status tracking
  9. * - CRUD operations for documents
  10. *
  11. * @module stores/documentStore
  12. */
  13. import { create } from 'zustand';
  14. import type { DocumentStoreState } from '../types/store';
  15. import type { Document, DocumentListFilters, CreateDocumentRequest } from '../types/document';
  16. import type { ListFilters } from '../types/api';
  17. import * as documentService from '../services/documentService';
  18. /**
  19. * Initial pagination state
  20. */
  21. const initialPagination = {
  22. page: 1,
  23. pageSize: 20,
  24. total: 0,
  25. totalPages: 0,
  26. };
  27. /**
  28. * Document store
  29. *
  30. * Manages document state and provides actions for document operations.
  31. * All async actions automatically handle errors and update the store state.
  32. *
  33. * @example
  34. * ```typescript
  35. * // Create a new document
  36. * await useDocumentStore.getState().createDocument({
  37. * title: 'My Document',
  38. * content: '# Hello World',
  39. * format: 'markdown',
  40. * source: 'chat',
  41. * });
  42. *
  43. * // Update current document content (local only)
  44. * useDocumentStore.getState().updateContent('# Updated content');
  45. *
  46. * // Fetch document list
  47. * await useDocumentStore.getState().fetchDocumentList({
  48. * source: 'chat',
  49. * sortBy: 'createdAt',
  50. * sortOrder: 'desc',
  51. * });
  52. * ```
  53. */
  54. export const useDocumentStore = create<DocumentStoreState>((set, get) => ({
  55. // ============ State ============
  56. currentDocument: null,
  57. documents: [],
  58. pagination: initialPagination,
  59. saveStatus: 'saved',
  60. // ============ Actions ============
  61. /**
  62. * Create a new document
  63. *
  64. * Creates a document via the API and sets it as the current document.
  65. * Save status is set to 'saved' after successful creation.
  66. *
  67. * @param req - Document creation request (userId, fileUrl, sessionId)
  68. * @throws {Error} When document creation fails
  69. */
  70. createDocument: async (req: CreateDocumentRequest) => {
  71. try {
  72. set({ saveStatus: 'saving' });
  73. // Call API to create document
  74. const response = await documentService.createDocument(req);
  75. // Fetch the full document to set as current
  76. const fullDocument = await documentService.getDocument(response.documentId);
  77. // Update state with new document
  78. set({
  79. currentDocument: fullDocument,
  80. saveStatus: 'saved',
  81. });
  82. // Refresh document list to include the new document
  83. await get().fetchDocumentList();
  84. } catch (error) {
  85. set({ saveStatus: 'error' });
  86. throw error;
  87. }
  88. },
  89. /**
  90. * Fetch a document by ID
  91. *
  92. * Retrieves a document from the API and sets it as the current document.
  93. * Optionally includes blocks array.
  94. *
  95. * @param id - Document ID to fetch
  96. * @param includeBlocks - Whether to include blocks in response
  97. * @throws {Error} When document fetch fails
  98. */
  99. fetchDocument: async (id: string, includeBlocks: boolean = false) => {
  100. try {
  101. const document = await documentService.getDocument(id, { includeBlocks });
  102. set({
  103. currentDocument: document,
  104. saveStatus: 'saved',
  105. });
  106. } catch (error) {
  107. set({ currentDocument: null });
  108. throw error;
  109. }
  110. },
  111. /**
  112. * Fetch document list with optional filters
  113. *
  114. * Retrieves a paginated list of documents from the API.
  115. * Updates the documents array and pagination info.
  116. *
  117. * @param filters - Optional filters for listing (source, sortBy, sortOrder, page, pageSize)
  118. * @throws {Error} When document list fetch fails
  119. */
  120. fetchDocumentList: async (filters?: ListFilters) => {
  121. try {
  122. // 转换前端的camelCase字段名为后端的snake_case
  123. const apiFilters: DocumentListFilters = {
  124. userId: 'default-user', // TODO: 从认证上下文获取
  125. page: filters?.page,
  126. pageSize: filters?.pageSize,
  127. sortBy: filters?.sortBy === 'createdAt' ? 'created_at' :
  128. filters?.sortBy === 'updatedAt' ? 'updated_at' :
  129. undefined,
  130. sortOrder: filters?.sortOrder,
  131. };
  132. const response = await documentService.listDocuments(apiFilters);
  133. set({
  134. documents: response.documents,
  135. pagination: response.pagination,
  136. });
  137. } catch (error) {
  138. set({ documents: [], pagination: initialPagination });
  139. throw error;
  140. }
  141. },
  142. /**
  143. * Delete documents by session ID
  144. *
  145. * Deletes all documents for a session via the API.
  146. * If any deleted document is the current document, clears the current document state.
  147. *
  148. * @param sessionId - Session ID whose documents to delete
  149. * @throws {Error} When document deletion fails
  150. */
  151. deleteDocumentsBySession: async (sessionId: string) => {
  152. await documentService.deleteDocuments(sessionId);
  153. // Clear current document if it belonged to this session
  154. const currentDoc = get().currentDocument;
  155. if (currentDoc && currentDoc.sessionId === sessionId) {
  156. set({ currentDocument: null, saveStatus: 'saved' });
  157. }
  158. // Refresh document list to reflect deletion
  159. await get().fetchDocumentList();
  160. },
  161. /**
  162. * Set current document
  163. *
  164. * Directly sets the current document in state (local only).
  165. * Used when document is already loaded.
  166. *
  167. * @param document - Document to set as current
  168. */
  169. setCurrentDocument: (document: Document | null) => {
  170. set({
  171. currentDocument: document,
  172. saveStatus: 'saved',
  173. });
  174. },
  175. /**
  176. * Set save status
  177. *
  178. * Manually updates the save status indicator.
  179. * Used by external components like auto-save hooks.
  180. *
  181. * @param status - New save status
  182. */
  183. setSaveStatus: (status) => {
  184. set({ saveStatus: status });
  185. },
  186. }));
  187. export default useDocumentStore;