@@ -210,6 +210,25 @@ def delete_database(self, db_id: str) -> dict:
210210
211211 return {"message" : "删除成功" }
212212
213+ def create_folder (self , db_id : str , folder_name : str , parent_id : str | None = None ) -> dict :
214+ """Create a folder in the database."""
215+ import uuid
216+ folder_id = f"folder-{ uuid .uuid4 ()} "
217+
218+ self .files_meta [folder_id ] = {
219+ "file_id" : folder_id ,
220+ "filename" : folder_name ,
221+ "is_folder" : True ,
222+ "parent_id" : parent_id ,
223+ "database_id" : db_id ,
224+ "created_at" : utc_isoformat (),
225+ "status" : "done" ,
226+ "path" : folder_name ,
227+ "file_type" : "folder"
228+ }
229+ self ._save_metadata ()
230+ return self .files_meta [folder_id ]
231+
213232 @abstractmethod
214233 async def add_content (self , db_id : str , items : list [str ], params : dict | None = None ) -> list [dict ]:
215234 """
@@ -300,14 +319,16 @@ def get_database_info(self, db_id: str) -> dict | None:
300319 if file_info .get ("database_id" ) == db_id :
301320 created_at = self ._normalize_timestamp (file_info .get ("created_at" ))
302321 db_files [file_id ] = {
303- "file_id" : file_id ,
304- "filename" : file_info .get ("filename" , "" ),
305- "path" : file_info .get ("path" , "" ),
306- "type" : file_info .get ("file_type" , "" ),
307- "status" : file_info .get ("status" , "done" ),
308- "created_at" : created_at ,
309- "processing_params" : file_info .get ("processing_params" , None ),
310- }
322+ "file_id" : file_id ,
323+ "filename" : file_info .get ("filename" , "" ),
324+ "path" : file_info .get ("path" , "" ),
325+ "type" : file_info .get ("file_type" , "" ),
326+ "status" : file_info .get ("status" , "done" ),
327+ "created_at" : created_at ,
328+ "processing_params" : file_info .get ("processing_params" , None ),
329+ "is_folder" : file_info .get ("is_folder" , False ),
330+ "parent_id" : file_info .get ("parent_id" , None ),
331+ }
311332
312333 # 按创建时间倒序排序文件列表
313334 sorted_files = dict (
@@ -350,6 +371,8 @@ def get_databases(self) -> dict:
350371 "type" : file_info .get ("file_type" , "" ),
351372 "status" : file_info .get ("status" , "done" ),
352373 "created_at" : created_at ,
374+ "is_folder" : file_info .get ("is_folder" , False ),
375+ "parent_id" : file_info .get ("parent_id" , None ),
353376 }
354377
355378 # 按创建时间倒序排序文件列表
@@ -439,6 +462,71 @@ def _check_and_fix_processing_status(self, db_id: str) -> None:
439462 except Exception as e :
440463 logger .error (f"Error checking processing status for database { db_id } : { e } " )
441464
465+ async def delete_folder (self , db_id : str , folder_id : str ) -> None :
466+ """
467+ Recursively delete a folder and its content.
468+
469+ Args:
470+ db_id: Database ID
471+ folder_id: Folder ID to delete
472+ """
473+ # Find all children
474+ children = [
475+ fid for fid , meta in self .files_meta .items ()
476+ if meta .get ("database_id" ) == db_id and meta .get ("parent_id" ) == folder_id
477+ ]
478+
479+ for child_id in children :
480+ child_meta = self .files_meta .get (child_id )
481+ if child_meta and child_meta .get ("is_folder" ):
482+ await self .delete_folder (db_id , child_id )
483+ else :
484+ await self .delete_file (db_id , child_id )
485+
486+ # Delete the folder itself
487+ # We call delete_file which should handle the actual removal.
488+ # Implementations should ensure they handle folder deletion gracefully (e.g. skip vector deletion)
489+ await self .delete_file (db_id , folder_id )
490+
491+ async def move_file (self , db_id : str , file_id : str , new_parent_id : str | None ) -> dict :
492+ """
493+ Move a file or folder to a new parent folder.
494+
495+ Args:
496+ db_id: Database ID
497+ file_id: File/Folder ID to move
498+ new_parent_id: New parent folder ID (None for root)
499+
500+ Returns:
501+ dict: Updated metadata
502+ """
503+ if file_id not in self .files_meta :
504+ raise ValueError (f"File { file_id } not found" )
505+
506+ meta = self .files_meta [file_id ]
507+ if meta .get ("database_id" ) != db_id :
508+ raise ValueError (f"File { file_id } does not belong to database { db_id } " )
509+
510+ # Basic cycle detection for folders
511+ if meta .get ("is_folder" ) and new_parent_id :
512+ # Check if new_parent_id is a child of file_id (or is file_id itself)
513+ if new_parent_id == file_id :
514+ raise ValueError ("Cannot move a folder into itself" )
515+
516+ # Walk up the tree from new_parent_id
517+ current = new_parent_id
518+ while current :
519+ parent_meta = self .files_meta .get (current )
520+ if not parent_meta :
521+ break # Should not happen if integrity is maintained
522+ if current == file_id :
523+ raise ValueError ("Cannot move a folder into its own subfolder" )
524+ current = parent_meta .get ("parent_id" )
525+
526+ meta ["parent_id" ] = new_parent_id
527+ self ._save_metadata ()
528+ return meta
529+
442530 @abstractmethod
443531 async def delete_file (self , db_id : str , file_id : str ) -> None :
444532 """
0 commit comments