Nguyễn Quốc Vỹ commited on
Commit
ab66a23
·
1 Parent(s): 0bcda63

Fix lỗi upload file không được lưu trữ lại lâu dài

Browse files
Files changed (3) hide show
  1. backend/admin_services.py +7 -0
  2. backend/db.py +5 -0
  3. backend/db_sync.py +139 -3
backend/admin_services.py CHANGED
@@ -16,6 +16,7 @@ if ROOT_DIR not in sys.path:
16
  from backend import db
17
  from backend.auth import is_admin
18
  from backend.runtime_paths import PDF_DIR
 
19
 
20
 
21
  # ======================== UserService ========================
@@ -108,6 +109,7 @@ def create_system_doc(file_path: str, filename: str = None) -> tuple[bool, str]:
108
  else:
109
  shutil.copy2(file_path, dest)
110
  db.insert_tai_lieu_he_thong(ma_tai_lieu=ma, ten_file=filename, duong_dan=os.path.abspath(dest))
 
111
  return True, "Đã thêm tài liệu. Hãy chạy 'Tái lập chỉ mục' để cập nhật RAG."
112
  except Exception as e:
113
  return False, str(e)
@@ -131,6 +133,7 @@ def update_system_doc(ma_tai_lieu: str, file_path: str = None, ten_file: str = N
131
  return False, str(e)
132
  # Cập nhật tên trong DB nếu đổi tên (cần hàm update trong db - hiện chỉ có insert upsert)
133
  db.insert_tai_lieu_he_thong(ma_tai_lieu=ma_tai_lieu, ten_file=new_name, duong_dan=old_path)
 
134
  return True, "Đã cập nhật. Chạy 'Tái lập chỉ mục' nếu đã thay file."
135
 
136
 
@@ -140,12 +143,15 @@ def delete_system_doc(ma_tai_lieu: str) -> tuple[bool, str]:
140
  if not docs:
141
  return False, "Không tìm thấy tài liệu"
142
  path = docs[0].get("duong_dan")
 
143
  try:
144
  if path and os.path.isfile(path):
145
  os.remove(path)
146
  except OSError:
147
  pass
148
  db.delete_tai_lieu_he_thong(ma_tai_lieu)
 
 
149
  return True, "Đã xóa tài liệu. Chạy 'Tái lập chỉ mục' để cập nhật RAG."
150
 
151
 
@@ -170,6 +176,7 @@ def reindex_all() -> tuple[bool, str]:
170
  except Exception:
171
  pass
172
  create_vector_database(chunks)
 
173
  return True, f"Đã tái lập chỉ mục: {len(documents)} tài liệu, {len(chunks)} chunks."
174
  except Exception as e:
175
  return False, f"Lỗi: {str(e)}"
 
16
  from backend import db
17
  from backend.auth import is_admin
18
  from backend.runtime_paths import PDF_DIR
19
+ from backend.db_sync import schedule_pdf_upload, schedule_pdf_delete, schedule_vector_sync
20
 
21
 
22
  # ======================== UserService ========================
 
109
  else:
110
  shutil.copy2(file_path, dest)
111
  db.insert_tai_lieu_he_thong(ma_tai_lieu=ma, ten_file=filename, duong_dan=os.path.abspath(dest))
112
+ schedule_pdf_upload(dest, filename)
113
  return True, "Đã thêm tài liệu. Hãy chạy 'Tái lập chỉ mục' để cập nhật RAG."
114
  except Exception as e:
115
  return False, str(e)
 
133
  return False, str(e)
134
  # Cập nhật tên trong DB nếu đổi tên (cần hàm update trong db - hiện chỉ có insert upsert)
135
  db.insert_tai_lieu_he_thong(ma_tai_lieu=ma_tai_lieu, ten_file=new_name, duong_dan=old_path)
136
+ schedule_pdf_upload(old_path, new_name)
137
  return True, "Đã cập nhật. Chạy 'Tái lập chỉ mục' nếu đã thay file."
138
 
139
 
 
143
  if not docs:
144
  return False, "Không tìm thấy tài liệu"
145
  path = docs[0].get("duong_dan")
146
+ filename = docs[0].get("ten_file") or (os.path.basename(path) if path else "")
147
  try:
148
  if path and os.path.isfile(path):
149
  os.remove(path)
150
  except OSError:
151
  pass
152
  db.delete_tai_lieu_he_thong(ma_tai_lieu)
153
+ if filename:
154
+ schedule_pdf_delete(filename)
155
  return True, "Đã xóa tài liệu. Chạy 'Tái lập chỉ mục' để cập nhật RAG."
156
 
157
 
 
176
  except Exception:
177
  pass
178
  create_vector_database(chunks)
179
+ schedule_vector_sync()
180
  return True, f"Đã tái lập chỉ mục: {len(documents)} tài liệu, {len(chunks)} chunks."
181
  except Exception as e:
182
  return False, f"Lỗi: {str(e)}"
backend/db.py CHANGED
@@ -482,6 +482,7 @@ def save_tai_lieu(ma_tai_lieu: str, ma_nguoi_dung: str, ten_file: str,
482
  conn.commit()
483
  finally:
484
  conn.close()
 
485
 
486
 
487
  def update_trang_thai_tai_lieu(ma_tai_lieu: str, trang_thai: str):
@@ -495,6 +496,7 @@ def update_trang_thai_tai_lieu(ma_tai_lieu: str, trang_thai: str):
495
  conn.commit()
496
  finally:
497
  conn.close()
 
498
 
499
 
500
  def load_tai_lieu_by_user(ma_nguoi_dung: str) -> list:
@@ -520,6 +522,7 @@ def delete_tai_lieu(ma_tai_lieu: str):
520
  conn.commit()
521
  finally:
522
  conn.close()
 
523
 
524
 
525
  # ======================== PHẢN HỒI NGƯỜI DÙNG ========================
@@ -725,6 +728,7 @@ def insert_tai_lieu_he_thong(ma_tai_lieu: str, ten_file: str, duong_dan: str):
725
  conn.commit()
726
  finally:
727
  conn.close()
 
728
 
729
 
730
  def delete_tai_lieu_he_thong(ma_tai_lieu: str):
@@ -735,6 +739,7 @@ def delete_tai_lieu_he_thong(ma_tai_lieu: str):
735
  conn.commit()
736
  finally:
737
  conn.close()
 
738
 
739
 
740
  # Khởi tạo DB khi import module
 
482
  conn.commit()
483
  finally:
484
  conn.close()
485
+ _schedule_sync()
486
 
487
 
488
  def update_trang_thai_tai_lieu(ma_tai_lieu: str, trang_thai: str):
 
496
  conn.commit()
497
  finally:
498
  conn.close()
499
+ _schedule_sync()
500
 
501
 
502
  def load_tai_lieu_by_user(ma_nguoi_dung: str) -> list:
 
522
  conn.commit()
523
  finally:
524
  conn.close()
525
+ _schedule_sync()
526
 
527
 
528
  # ======================== PHẢN HỒI NGƯỜI DÙNG ========================
 
728
  conn.commit()
729
  finally:
730
  conn.close()
731
+ _schedule_sync()
732
 
733
 
734
  def delete_tai_lieu_he_thong(ma_tai_lieu: str):
 
739
  conn.commit()
740
  finally:
741
  conn.close()
742
+ _schedule_sync()
743
 
744
 
745
  # Khởi tạo DB khi import module
backend/db_sync.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Đồng bộ chatbot.db lên Hugging Face Dataset repo để dữ liệu bền vững.
3
 
4
  Chỉ hoạt động khi chạy trên HF Space và có cấu hình:
5
  - HF_DATASET_REPO
@@ -13,6 +13,7 @@ import shutil
13
  import sqlite3
14
  import threading
15
  import time
 
16
 
17
  _lock = threading.Lock()
18
  _last_sync: float = 0
@@ -77,6 +78,142 @@ def _do_upload(db_path: str, repo: str, token: str, revision: str) -> bool:
77
  return False
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def schedule_sync(db_path: str | None = None) -> None:
81
  """
82
  Lên lịch đồng bộ DB lên dataset repo (chạy background, có rate-limit).
@@ -109,5 +246,4 @@ def schedule_sync(db_path: str | None = None) -> None:
109
  _last_sync = time.time()
110
  _do_upload(db_path, repo, token, revision)
111
 
112
- thread = threading.Thread(target=_sync, daemon=True)
113
- thread.start()
 
1
  """
2
+ Đồng bộ dữ liệu runtime lên Hugging Face Dataset repo để dữ liệu bền vững.
3
 
4
  Chỉ hoạt động khi chạy trên HF Space và có cấu hình:
5
  - HF_DATASET_REPO
 
13
  import sqlite3
14
  import threading
15
  import time
16
+ from datetime import datetime, timezone
17
 
18
  _lock = threading.Lock()
19
  _last_sync: float = 0
 
78
  return False
79
 
80
 
81
+ def _spawn_sync(task) -> None:
82
+ """Chạy tác vụ sync trong background thread."""
83
+ thread = threading.Thread(target=task, daemon=True)
84
+ thread.start()
85
+
86
+
87
+ def _upload_manifest(repo: str, token: str, revision: str, include_pdf: bool, include_vector: bool) -> None:
88
+ """Cập nhật manifest.json cơ bản trong dataset repo."""
89
+ try:
90
+ from huggingface_hub import HfApi
91
+ import json
92
+ import tempfile
93
+
94
+ payload = {
95
+ "generated_at": datetime.now(timezone.utc).isoformat(),
96
+ "includes": {
97
+ "chatbot_db": True,
98
+ "pdf": include_pdf,
99
+ "csdl_vector": include_vector,
100
+ },
101
+ }
102
+ with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as f:
103
+ json.dump(payload, f, ensure_ascii=False, indent=2)
104
+ temp_manifest = f.name
105
+
106
+ try:
107
+ api = HfApi(token=token)
108
+ api.upload_file(
109
+ path_or_fileobj=temp_manifest,
110
+ path_in_repo="manifest.json",
111
+ repo_id=repo,
112
+ repo_type="dataset",
113
+ revision=revision,
114
+ commit_message="Auto-update manifest",
115
+ )
116
+ finally:
117
+ try:
118
+ os.remove(temp_manifest)
119
+ except OSError:
120
+ pass
121
+ except Exception as exc:
122
+ print(f"[DB_SYNC] Manifest update failed: {exc}")
123
+
124
+
125
+ def schedule_pdf_upload(local_pdf_path: str, remote_filename: str | None = None) -> None:
126
+ """Upload 1 file PDF mới/chỉnh sửa lên dataset repo tại pdf/<filename>."""
127
+ config = _get_config()
128
+ if not config:
129
+ return
130
+ if not os.path.exists(local_pdf_path):
131
+ return
132
+
133
+ repo, token, revision = config
134
+ remote_name = remote_filename or os.path.basename(local_pdf_path)
135
+
136
+ def _task():
137
+ with _lock:
138
+ try:
139
+ from huggingface_hub import HfApi
140
+ api = HfApi(token=token)
141
+ api.upload_file(
142
+ path_or_fileobj=local_pdf_path,
143
+ path_in_repo=f"pdf/{remote_name}",
144
+ repo_id=repo,
145
+ repo_type="dataset",
146
+ revision=revision,
147
+ commit_message=f"Auto-sync PDF: {remote_name}",
148
+ )
149
+ _upload_manifest(repo, token, revision, include_pdf=True, include_vector=False)
150
+ print(f"[DB_SYNC] Uploaded pdf/{remote_name} to {repo}")
151
+ except Exception as exc:
152
+ print(f"[DB_SYNC] PDF upload failed: {exc}")
153
+
154
+ _spawn_sync(_task)
155
+
156
+
157
+ def schedule_pdf_delete(remote_filename: str) -> None:
158
+ """Xóa 1 file PDF từ dataset repo tại pdf/<filename>."""
159
+ config = _get_config()
160
+ if not config or not remote_filename:
161
+ return
162
+ repo, token, revision = config
163
+
164
+ def _task():
165
+ with _lock:
166
+ try:
167
+ from huggingface_hub import HfApi
168
+ api = HfApi(token=token)
169
+ api.delete_file(
170
+ path_in_repo=f"pdf/{remote_filename}",
171
+ repo_id=repo,
172
+ repo_type="dataset",
173
+ revision=revision,
174
+ commit_message=f"Auto-remove PDF: {remote_filename}",
175
+ )
176
+ _upload_manifest(repo, token, revision, include_pdf=True, include_vector=False)
177
+ print(f"[DB_SYNC] Deleted pdf/{remote_filename} from {repo}")
178
+ except Exception as exc:
179
+ print(f"[DB_SYNC] PDF delete failed: {exc}")
180
+
181
+ _spawn_sync(_task)
182
+
183
+
184
+ def schedule_vector_sync() -> None:
185
+ """Upload toàn bộ thư mục csdl_vector lên dataset repo."""
186
+ config = _get_config()
187
+ if not config:
188
+ return
189
+ from backend.runtime_paths import VECTOR_DIR
190
+
191
+ if not os.path.isdir(VECTOR_DIR):
192
+ return
193
+
194
+ repo, token, revision = config
195
+
196
+ def _task():
197
+ with _lock:
198
+ try:
199
+ from huggingface_hub import HfApi
200
+ api = HfApi(token=token)
201
+ api.upload_folder(
202
+ folder_path=VECTOR_DIR,
203
+ path_in_repo="csdl_vector",
204
+ repo_id=repo,
205
+ repo_type="dataset",
206
+ revision=revision,
207
+ commit_message="Auto-sync vector store",
208
+ )
209
+ _upload_manifest(repo, token, revision, include_pdf=True, include_vector=True)
210
+ print(f"[DB_SYNC] Uploaded csdl_vector to {repo}")
211
+ except Exception as exc:
212
+ print(f"[DB_SYNC] Vector sync failed: {exc}")
213
+
214
+ _spawn_sync(_task)
215
+
216
+
217
  def schedule_sync(db_path: str | None = None) -> None:
218
  """
219
  Lên lịch đồng bộ DB lên dataset repo (chạy background, có rate-limit).
 
246
  _last_sync = time.time()
247
  _do_upload(db_path, repo, token, revision)
248
 
249
+ _spawn_sync(_sync)