waveshaker commited on
Commit
d16dd39
·
verified ·
1 Parent(s): d825655

Update models_manager_v8.4.py

Browse files
Files changed (1) hide show
  1. models_manager_v8.4.py +159 -30
models_manager_v8.4.py CHANGED
@@ -317,6 +317,9 @@ class ModelsManager(tk.Tk):
317
  # 3. 管理全局 KV
318
  ttk.Button(top, text="🔧 管理全局 KV", command=self.open_fields_manager).pack(side="left", padx=6)
319
 
 
 
 
320
  # 4. 加载本地
321
  ttk.Button(top, text="📂 加载本地 models.json", command=self.load_local).pack(side="left", padx=6)
322
 
@@ -471,6 +474,25 @@ class ModelsManager(tk.Tk):
471
 
472
  ttk.Label(win, text="注意:操作会直接修改内存中的数据,完成后再去点【保存本地】", foreground="red").pack(pady=10)
473
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  def open_dataset_repo(self):
475
  import webbrowser
476
  webbrowser.open(f"https://huggingface.co/datasets/{DATASET_REPO_ID}/tree/main")
@@ -626,7 +648,17 @@ class ModelsManager(tk.Tk):
626
  meta.get("showcase", "")
627
  ]
628
  for col in dynamic_cols:
629
- row_values.append(meta.get(col, ""))
 
 
 
 
 
 
 
 
 
 
630
 
631
  self.tree.insert("", "end", iid=path, text=path, values=row_values, tags=tags)
632
 
@@ -747,19 +779,32 @@ class ModelsManager(tk.Tk):
747
 
748
  # 🌟 核心修改:深拷贝一份用于上传
749
  upload_items = copy.deepcopy(sorted_items)
 
 
 
 
750
 
751
  for path, meta in upload_items.items():
752
  if not isinstance(meta, dict): continue
753
 
754
- if meta.get("dataset"):
755
- meta["dataset"] = meta["dataset"].replace(f"{DATASET_BASE}/", "").replace(DATASET_BASE, "")
 
 
 
 
 
 
756
 
757
  if meta.get("showcase"):
758
- prefix = f"{MODEL_BASE}/tree/main/{SHOWCASE_DIR}/"
759
- meta["showcase"] = meta["showcase"].replace(prefix, "")
 
 
760
 
761
- if meta.get("guide"):
762
- meta["guide"] = meta["guide"].replace(f"{GUIDE_BASE}/", "").replace(GUIDE_BASE, "")
 
763
 
764
  tmp = TEMP_UPLOAD_JSON
765
  with open(tmp, "w", encoding="utf-8") as f:
@@ -1150,15 +1195,13 @@ class ModelsManager(tk.Tk):
1150
  try:
1151
  from huggingface_hub import CommitOperationAdd
1152
 
1153
- # 🌟 开始上传,禁用按钮,重置进度条
1154
  self.after(0, lambda: self.btn_upload.configure(state="disabled"))
1155
  self.after(0, lambda: self.progress_var.set(0))
1156
- self.after(0, lambda: self.status_model_var.set("正在打包并上传..."))
1157
 
1158
- # 1. 准备操作列表
1159
  operations = []
1160
  for local_file, remote_path in to_upload:
1161
- # 极度关键:防止 Windows 反斜杠导致远端多建文件夹
1162
  safe_remote_path = remote_path.replace("\\", "/")
1163
  operations.append(
1164
  CommitOperationAdd(
@@ -1167,55 +1210,141 @@ class ModelsManager(tk.Tk):
1167
  )
1168
  )
1169
 
1170
- # 2. 准备 commit message
1171
  remote_dir = self.remote_dir_var.get().strip().replace("\\", "/").strip("/")
1172
  commit_msg = f"Upload files to {remote_dir}" if remote_dir else "Upload files to root"
1173
-
1174
- self.log_model(f"开始打包 {len(operations)} 个文件,一次性 Commit...")
1175
- self.log_model(f"Commit message: {commit_msg}")
1176
 
1177
- # 🌟 3. 假进度条控制变量
1178
  is_uploading = [True]
1179
  def update_progress():
1180
- if not is_uploading[0]: return # 如果上传结束,停止动画
1181
  current = self.progress_var.get()
1182
- # 慢慢爬升到 95%,最后留 5% 给真正完成时冲刺
1183
  if current < 95:
1184
  self.progress_var.set(current + 2)
1185
  self.after(300, update_progress)
1186
-
1187
- self.after(100, update_progress) # 启动动画
1188
 
1189
- # 4. 一次性提交多个文件 (同步阻塞操作)
1190
  api.create_commit(
1191
  repo_id=MODEL_REPO_ID,
1192
  repo_type="model",
1193
  operations=operations,
1194
  commit_message=commit_msg
1195
  )
 
1196
 
1197
- # 🌟 5. 上传成功:停止动画,进度条瞬间满格
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  is_uploading[0] = False
1199
  self.after(0, lambda: self.progress_var.set(100))
1200
  self.after(0, lambda: self.status_model_var.set("上传完成 ✅"))
1201
  self.after(0, lambda: self.btn_upload.configure(state="normal"))
1202
-
1203
- self.log_model("打包上传成功!")
1204
- self.after(0, lambda: messagebox.showinfo("完成", "上传成功!"))
1205
- self.after(0, self.load_remote) # 自动刷新 JSON
1206
 
 
 
1207
  except Exception as e:
1208
- # 🌟 失败时:停止动画,重置进度条,恢复按钮
1209
  is_uploading[0] = False
1210
  self.after(0, lambda: self.progress_var.set(0))
1211
  self.after(0, lambda: self.btn_upload.configure(state="normal"))
1212
- self.after(0, lambda: self.status_model_var.set("上传失败 ❌"))
1213
-
1214
- self.log_exc("打包上传失败", is_model=True)
1215
  self.after(0, lambda: messagebox.showerror("失败", "详见日志"))
1216
 
1217
  threading.Thread(target=upload_worker, daemon=True).start()
1218
 
 
 
 
 
 
 
 
 
 
1219
  # ==========================================================
1220
  # ==================== 区域 3: Dataset 上传 =================
1221
  # ==========================================================
 
317
  # 3. 管理全局 KV
318
  ttk.Button(top, text="🔧 管理全局 KV", command=self.open_fields_manager).pack(side="left", padx=6)
319
 
320
+ # 🌟 新增:删除选中条目
321
+ ttk.Button(top, text="❌ 删除选中", command=self.delete_selected_item).pack(side="left", padx=6)
322
+
323
  # 4. 加载本地
324
  ttk.Button(top, text="📂 加载本地 models.json", command=self.load_local).pack(side="left", padx=6)
325
 
 
474
 
475
  ttk.Label(win, text="注意:操作会直接修改内存中的数据,完成后再去点【保存本地】", foreground="red").pack(pady=10)
476
 
477
+ def delete_selected_item(self):
478
+ """从内存中删除选中的条目"""
479
+ sel = self.tree.focus()
480
+ if not sel or sel not in self.items:
481
+ messagebox.showwarning("提示", "请先在表格中选中要删除的条目!")
482
+ return
483
+
484
+ if not messagebox.askyesno("确认删除", f"确定要删除这条记录吗?\n\n{sel}\n\n注意:这只会从本地 JSON 中移除,不会删除云端实际的模型文件。"):
485
+ return
486
+
487
+ # 从内存中移除
488
+ self.items.pop(sel, None)
489
+
490
+ # 如果它恰好是新增的,也从 new_paths 里移除
491
+ if sel in self.new_paths:
492
+ self.new_paths.remove(sel)
493
+
494
+ self.log_json(f"🗑️ 已在本地删除条目: {sel}")
495
+ self.refresh_tree()
496
  def open_dataset_repo(self):
497
  import webbrowser
498
  webbrowser.open(f"https://huggingface.co/datasets/{DATASET_REPO_ID}/tree/main")
 
648
  meta.get("showcase", "")
649
  ]
650
  for col in dynamic_cols:
651
+ val = meta.get(col, "")
652
+ # 🌟 新增:如果列名是 update_time,把毫秒时间戳转成可读日期展示
653
+ if col == "update_time" and val:
654
+ try:
655
+ # 转换成本地时间的 YYYY-MM-DD 格式
656
+ import datetime
657
+ dt = datetime.datetime.fromtimestamp(int(val) / 1000.0)
658
+ val = dt.strftime('%Y-%m-%d')
659
+ except Exception:
660
+ pass
661
+ row_values.append(val)
662
 
663
  self.tree.insert("", "end", iid=path, text=path, values=row_values, tags=tags)
664
 
 
779
 
780
  # 🌟 核心修改:深拷贝一份用于上传
781
  upload_items = copy.deepcopy(sorted_items)
782
+
783
+ # 🌟 精准时间戳策略:只为从未有过时间戳的新文件打上当前时间,老文件保留原本时间
784
+ import time
785
+ current_ts = int(time.time() * 1000)
786
 
787
  for path, meta in upload_items.items():
788
  if not isinstance(meta, dict): continue
789
 
790
+ # 只给全新文件(没有 update_time)打时间戳,绝不覆盖历史时间
791
+ if not meta.get("update_time"):
792
+ meta["update_time"] = current_ts
793
+
794
+ # 剥离前缀
795
+ if meta.get("dataset") and meta["dataset"].startswith(DATASET_BASE):
796
+ stripped = meta["dataset"][len(DATASET_BASE):].lstrip("/")
797
+ if stripped: meta["dataset"] = stripped
798
 
799
  if meta.get("showcase"):
800
+ prefix = f"{MODEL_BASE}/tree/main/{SHOWCASE_DIR}"
801
+ if meta["showcase"].startswith(prefix):
802
+ stripped = meta["showcase"][len(prefix):].lstrip("/")
803
+ if stripped: meta["showcase"] = stripped
804
 
805
+ if meta.get("guide") and meta["guide"].startswith(GUIDE_BASE):
806
+ stripped = meta["guide"][len(GUIDE_BASE):].lstrip("/")
807
+ if stripped: meta["guide"] = stripped
808
 
809
  tmp = TEMP_UPLOAD_JSON
810
  with open(tmp, "w", encoding="utf-8") as f:
 
1195
  try:
1196
  from huggingface_hub import CommitOperationAdd
1197
 
 
1198
  self.after(0, lambda: self.btn_upload.configure(state="disabled"))
1199
  self.after(0, lambda: self.progress_var.set(0))
1200
+ self.after(0, lambda: self.status_model_var.set("正在上传模型文件..."))
1201
 
1202
+ # ========== 步骤 1:上传物理文件 ==========
1203
  operations = []
1204
  for local_file, remote_path in to_upload:
 
1205
  safe_remote_path = remote_path.replace("\\", "/")
1206
  operations.append(
1207
  CommitOperationAdd(
 
1210
  )
1211
  )
1212
 
 
1213
  remote_dir = self.remote_dir_var.get().strip().replace("\\", "/").strip("/")
1214
  commit_msg = f"Upload files to {remote_dir}" if remote_dir else "Upload files to root"
 
 
 
1215
 
 
1216
  is_uploading = [True]
1217
  def update_progress():
1218
+ if not is_uploading[0]: return
1219
  current = self.progress_var.get()
 
1220
  if current < 95:
1221
  self.progress_var.set(current + 2)
1222
  self.after(300, update_progress)
1223
+ self.after(100, update_progress)
 
1224
 
1225
+ self.log_model(f"开始打包 {len(operations)} 个文件,一次性 Commit...")
1226
  api.create_commit(
1227
  repo_id=MODEL_REPO_ID,
1228
  repo_type="model",
1229
  operations=operations,
1230
  commit_message=commit_msg
1231
  )
1232
+ self.log_model("模型文件上传成功!正在同步 models.json...")
1233
 
1234
+ # ========== 步骤 2:拉取云端最新 JSON ==========
1235
+ self.after(0, lambda: self.status_model_var.set("正在拉取云端最新 JSON..."))
1236
+
1237
+ space_subdomain = SPACE_ID.replace("/", "-").replace("_", "-").lower()
1238
+ url = f"https://{space_subdomain}.static.hf.space/models.json"
1239
+
1240
+ latest_remote_items = {}
1241
+ try:
1242
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
1243
+ with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT) as response:
1244
+ remote_data = json.loads(response.read().decode())
1245
+ latest_remote_items = remote_data.get("items", {})
1246
+ self.log_model(f"成功拉取云端 JSON,包含 {len(latest_remote_items)} 条记录。")
1247
+ except Exception as e:
1248
+ self.log_model(f"拉取云端 JSON 失败,放弃本次同步以防止覆盖: {e}")
1249
+ raise e # 如果拉取失败,直接中断,绝不用空数据覆盖!
1250
+
1251
+ # ========== 步骤 3:精准追加新条目 ==========
1252
+ import time
1253
+ current_ts = int(time.time() * 1000)
1254
+ added_count = 0
1255
+
1256
+ for local_file, remote_path in to_upload:
1257
+ safe_remote_path = remote_path.replace("\\", "/")
1258
+
1259
+ # 🚨 绝对严格过滤:只有 .safetensors 才配进入 models.json
1260
+ if not safe_remote_path.endswith(".safetensors"):
1261
+ continue
1262
+
1263
+ # 如果云端已经有这个模型了,更新它的时间;如果没有,则新增
1264
+ if safe_remote_path in latest_remote_items:
1265
+ latest_remote_items[safe_remote_path]["update_time"] = current_ts
1266
+ else:
1267
+ latest_remote_items[safe_remote_path] = {
1268
+ "title": "", "trigger": "", "dataset": "", "showcase": "", "update_time": current_ts
1269
+ }
1270
+ added_count += 1
1271
+
1272
+ self.log_model(f"已为 {added_count} 个新增模型写入时间戳。")
1273
+
1274
+ # 按原有逻辑重新排序
1275
+ sorted_items = {k: latest_remote_items[k] for k in sorted(latest_remote_items.keys(), key=custom_sort_key)}
1276
+
1277
+ # ========== 步骤 4:剥离前缀并推送 ==========
1278
+ if added_count > 0:
1279
+ self.after(0, lambda: self.status_model_var.set("正在推送到云端..."))
1280
+
1281
+ # 补全 URL 后存入本地(保持本地数据完整)
1282
+ self.items = sorted_items
1283
+ expand_urls(self.items)
1284
+ self.save_local_silent()
1285
+ self.after(0, self.refresh_tree)
1286
+
1287
+ # 深拷贝用于剥离上传
1288
+ import copy
1289
+ upload_items = copy.deepcopy(sorted_items)
1290
+ for p, meta in upload_items.items():
1291
+ if not isinstance(meta, dict): continue
1292
+
1293
+ if meta.get("dataset") and meta["dataset"].startswith(DATASET_BASE):
1294
+ stripped = meta["dataset"][len(DATASET_BASE):].lstrip("/")
1295
+ if stripped: meta["dataset"] = stripped
1296
+ if meta.get("showcase"):
1297
+ prefix = f"{MODEL_BASE}/tree/main/{SHOWCASE_DIR}"
1298
+ if meta["showcase"].startswith(prefix):
1299
+ stripped = meta["showcase"][len(prefix):].lstrip("/")
1300
+ if stripped: meta["showcase"] = stripped
1301
+ if meta.get("guide") and meta["guide"].startswith(GUIDE_BASE):
1302
+ stripped = meta["guide"][len(GUIDE_BASE):].lstrip("/")
1303
+ if stripped: meta["guide"] = stripped
1304
+
1305
+ tmp_json = TEMP_UPLOAD_JSON
1306
+ with open(tmp_json, "w", encoding="utf-8") as f:
1307
+ json.dump({"items": upload_items}, f, indent=2, ensure_ascii=False)
1308
+
1309
+ api.upload_file(
1310
+ path_or_fileobj=tmp_json,
1311
+ path_in_repo=OUTPUT_FILE,
1312
+ repo_id=SPACE_ID,
1313
+ repo_type="space",
1314
+ commit_message=f"Update JSON for new model(s)"
1315
+ )
1316
+ try: os.remove(tmp_json)
1317
+ except OSError: pass
1318
+
1319
+ self.log_model("models.json 已同步更新到云端!")
1320
+
1321
+ # ========== 步骤 5:收尾 ==========
1322
  is_uploading[0] = False
1323
  self.after(0, lambda: self.progress_var.set(100))
1324
  self.after(0, lambda: self.status_model_var.set("上传完成 ✅"))
1325
  self.after(0, lambda: self.btn_upload.configure(state="normal"))
 
 
 
 
1326
 
1327
+ self.after(0, lambda: messagebox.showinfo("完成", "模型与 JSON 均已安全同步!"))
1328
+
1329
  except Exception as e:
 
1330
  is_uploading[0] = False
1331
  self.after(0, lambda: self.progress_var.set(0))
1332
  self.after(0, lambda: self.btn_upload.configure(state="normal"))
1333
+ self.after(0, lambda: self.status_model_var.set("执行失败 ❌"))
1334
+ self.log_exc("上传或同步失败", is_model=True)
 
1335
  self.after(0, lambda: messagebox.showerror("失败", "详见日志"))
1336
 
1337
  threading.Thread(target=upload_worker, daemon=True).start()
1338
 
1339
+
1340
+ def save_local_silent(self):
1341
+ """静默保存本地 JSON,不弹出提示框"""
1342
+ data_items = {k: v for k, v in self.items.items() if k not in self.stale_paths}
1343
+ sorted_items = {k: data_items[k] for k in sorted(data_items.keys(), key=custom_sort_key)}
1344
+ with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
1345
+ json.dump({"items": sorted_items}, f, indent=2, ensure_ascii=False)
1346
+ self.log_json(f"已静默保存本地 {OUTPUT_FILE},{len(sorted_items)} 条")
1347
+
1348
  # ==========================================================
1349
  # ==================== 区域 3: Dataset 上传 =================
1350
  # ==========================================================