bep40 commited on
Commit
179cea2
·
1 Parent(s): 767f2c9

fix(wall): conservative cleanup — only delete truly garbage posts, preserve FPT/auto_rewrite with valid images

Browse files

- _wall_posts_for_view: VIEW-ONLY filtering (non-destructive) of placeholder
image auto posts (pollinations.ai, vnexpress logo, empty). Posts with valid
images are always kept. Sort newest-first, mixed sources.
- _post_has_slide_info: accept YouTube/TikTok/FB embed iframe videos as valid
slide-AI content (Short HOT oEmbed fallback case), not just /api/ai/short-file
- _wall_cleanup_once (startup): only persist-delete auto posts with NO valid
image AND no video AND no slides (true garbage). Never delete FPT embed posts
or auto_rewrite posts with real images — prevents data loss.
- /api/wall/cleanup: same conservative deletion logic

Files changed (1) hide show
  1. app_v2_entry.py +67 -33
app_v2_entry.py CHANGED
@@ -938,16 +938,23 @@ def _post_has_valid_image(p):
938
  return False
939
 
940
  def _post_has_slide_info(p):
941
- """True if the post has usable slide/rewrite AI content (designed slides
942
- or a generated short video with a thumbnail). Posts with neither slides
943
- nor a short cannot be properly viewed as 'slide AI' content."""
 
 
944
  slides = p.get("slides") or []
945
  if any((s.get("image") or "").strip() and (s.get("text") or "").strip() for s in slides if isinstance(s, dict)):
946
  return True
947
- # A generated short with a real thumbnail counts as slide-AI content
948
- if _post_has_valid_image(p) and (p.get("short_thumb") or "").strip():
949
- vid = (p.get("video") or "").strip()
950
- if vid and "api/ai/short-file/" in vid:
 
 
 
 
 
951
  return True
952
  return False
953
 
@@ -983,11 +990,14 @@ def _created_ts(p):
983
 
984
  def _wall_posts_for_view():
985
  """Apply the user-facing wall filtering + sorting:
986
- 1) Drop auto-generated posts that have no valid image (broken/placeholder
987
- thumb) OR no slide-AI info (summary-style auto posts with a wrong
988
- image and no slides/video). User-authored posts (kind/source unset or
989
- 'user'/'vtv_recorder') are always kept.
990
- 2) Sort newest-first by creation timestamp (mixed sources)."""
 
 
 
991
  posts = _load_wall_posts()
992
  if not posts:
993
  return []
@@ -995,11 +1005,10 @@ def _wall_posts_for_view():
995
  for p in posts:
996
  if not isinstance(p, dict):
997
  continue
998
- if _is_auto_post(p):
999
- # auto post: must have a valid (non-placeholder) image AND some
1000
- # slide-AI content, otherwise it shows the wrong image / no content
1001
- if not _post_has_valid_image(p) or not _post_has_slide_info(p):
1002
- continue
1003
  filtered.append(p)
1004
  filtered.sort(key=_created_ts, reverse=True)
1005
  return filtered
@@ -1244,10 +1253,14 @@ def api_wall_delete(post_id: str):
1244
 
1245
  @app.post('/api/wall/cleanup')
1246
  def api_wall_cleanup():
1247
- """Permanently remove auto-generated wall posts that have a wrong/missing
1248
- image (placeholder) or no slide-AI content, and persist the cleaned store.
1249
- Also sorts the remaining posts newest-first so the wall is consistent.
1250
- Returns the number of removed posts and the new post count."""
 
 
 
 
1251
  posts = _load_wall_posts()
1252
  if not isinstance(posts, list):
1253
  return JSONResponse({"error": "No posts"}, status_code=404)
@@ -1258,7 +1271,7 @@ def api_wall_cleanup():
1258
  if not isinstance(p, dict):
1259
  removed.append(str(p))
1260
  continue
1261
- if _is_auto_post(p) and not (_post_has_valid_image(p) and _post_has_slide_info(p)):
1262
  removed.append(p.get('id', '?'))
1263
  else:
1264
  kept.append(p)
@@ -2532,10 +2545,15 @@ def _scheduler_loop():
2532
  threading.Thread(target=_scheduler_loop, daemon=True, name='auto-rewrite-scheduler').start()
2533
 
2534
  def _wall_cleanup_once():
2535
- """One-time startup cleanup: remove auto-generated wall posts that have a
2536
- wrong/placeholder image or no slide-AI content, and sort the remaining
2537
- posts newest-first. Runs once a few seconds after import so the persistent
2538
- wall store is clean for every reader."""
 
 
 
 
 
2539
  try:
2540
  time.sleep(8)
2541
  posts = _load_wall_posts()
@@ -2544,19 +2562,35 @@ def _wall_cleanup_once():
2544
  kept = []
2545
  removed = 0
2546
  removed_ids = []
 
2547
  for p in posts:
2548
  if not isinstance(p, dict):
2549
  removed += 1
2550
  continue
2551
- if _is_auto_post(p) and not (_post_has_valid_image(p) and _post_has_slide_info(p)):
2552
- removed += 1
2553
- removed_ids.append(p.get('id', '?'))
2554
- else:
2555
- kept.append(p)
2556
- if removed > 0 or len(removed_ids) > 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2557
  kept.sort(key=_created_ts, reverse=True)
2558
  _save_wall_posts(kept)
2559
- print(f"[wall] startup cleanup removed {removed} broken placeholder auto posts ({removed_ids[:10]}), kept {len(kept)}")
2560
  except Exception as e:
2561
  print(f"[wall] startup cleanup error: {e}")
2562
 
 
938
  return False
939
 
940
  def _post_has_slide_info(p):
941
+ """True if the post has usable slide/rewrite AI content:
942
+ - designed slides with text+image, OR
943
+ - a generated short video (/api/ai/short-file/) with a real thumbnail, OR
944
+ - an oEmbed iframe (YouTube/TikTok/Facebook embed) with a real thumbnail
945
+ (Short HOT fallback case — embeddable, viewable as a slide)."""
946
  slides = p.get("slides") or []
947
  if any((s.get("image") or "").strip() and (s.get("text") or "").strip() for s in slides if isinstance(s, dict)):
948
  return True
949
+ # Generated short video with a real thumbnail
950
+ vid = (p.get("video") or "").strip()
951
+ if vid and "api/ai/short-file/" in vid and (p.get("short_thumb") or "").strip():
952
+ return True
953
+ # oEmbed iframe (YouTube/TikTok/FB) with a valid image -> viewable embed slide
954
+ if vid and ("youtube.com/embed" in vid or "tiktok.com/embed" in vid or
955
+ "facebook.com/plugins" in vid or "instagram.com" in vid or
956
+ "twitter.com" in vid or "x.com" in vid or "player.vimeo" in vid):
957
+ if _post_has_valid_image(p):
958
  return True
959
  return False
960
 
 
990
 
991
  def _wall_posts_for_view():
992
  """Apply the user-facing wall filtering + sorting:
993
+ 1) Hide auto-generated posts that have a wrong/placeholder image (no
994
+ valid image at all pollinations.ai, vnexpress logo, empty) or have
995
+ no slide-AI content (no slides / no short-video / no embed). These
996
+ are the 'bài đăng tự động bị sai ảnh / ko thông tin slide AI' that
997
+ don't match the original article. This filter is VIEW-ONLY — it does
998
+ NOT delete from the persistent store.
999
+ 2) Sort newest-first by creation timestamp, mixing all sources (FPT,
1000
+ slide rewrite AI, etc.) without source separation."""
1001
  posts = _load_wall_posts()
1002
  if not posts:
1003
  return []
 
1005
  for p in posts:
1006
  if not isinstance(p, dict):
1007
  continue
1008
+ if _is_auto_post(p) and not (_post_has_valid_image(p) and _post_has_slide_info(p)):
1009
+ # auto post with a wrong/placeholder/missing image or no slide AI
1010
+ # content -> hide from the wall (non-destructive)
1011
+ continue
 
1012
  filtered.append(p)
1013
  filtered.sort(key=_created_ts, reverse=True)
1014
  return filtered
 
1253
 
1254
  @app.post('/api/wall/cleanup')
1255
  def api_wall_cleanup():
1256
+ """Permanently remove auto-generated wall posts that are completely
1257
+ unrecoverable (no valid image, no video, no slides, no embed — i.e. a
1258
+ post that can never be displayed), and sort the remaining posts
1259
+ newest-first. Returns the number of removed posts and the new count.
1260
+
1261
+ NOTE: Conservative — auto posts that HAVE a valid image or a video/embed
1262
+ are preserved (they remain visible at view time via
1263
+ _wall_posts_for_view). Only use this to purge genuine garbage."""
1264
  posts = _load_wall_posts()
1265
  if not isinstance(posts, list):
1266
  return JSONResponse({"error": "No posts"}, status_code=404)
 
1271
  if not isinstance(p, dict):
1272
  removed.append(str(p))
1273
  continue
1274
+ if _is_auto_post(p) and not _post_has_valid_image(p) and not _post_has_slide_info(p) and not (p.get("video") or "").strip():
1275
  removed.append(p.get('id', '?'))
1276
  else:
1277
  kept.append(p)
 
2545
  threading.Thread(target=_scheduler_loop, daemon=True, name='auto-rewrite-scheduler').start()
2546
 
2547
  def _wall_cleanup_once():
2548
+ """One-time startup cleanup: sort the persistent wall store newest-first
2549
+ and remove truly-garbage auto posts (no valid image at ALL, no video, no
2550
+ slides, no embed URL i.e. a post that can never be displayed).
2551
+
2552
+ NOTE: This is intentionally conservative. Posts with a valid image or a
2553
+ valid embed/video are NEVER deleted here — they are only hidden at view
2554
+ time by _wall_posts_for_view() when the image is a placeholder. This
2555
+ prevents accidental data loss of FPT Short AI posts whose image is valid
2556
+ but may temporarily lack a short_thumb."""
2557
  try:
2558
  time.sleep(8)
2559
  posts = _load_wall_posts()
 
2562
  kept = []
2563
  removed = 0
2564
  removed_ids = []
2565
+ needs_sort = False
2566
  for p in posts:
2567
  if not isinstance(p, dict):
2568
  removed += 1
2569
  continue
2570
+ if _is_auto_post(p):
2571
+ has_img = _post_has_valid_image(p)
2572
+ has_content = _post_has_slide_info(p)
2573
+ vid = (p.get("video") or "").strip()
2574
+ # Only delete auto posts that are completely unrecoverable:
2575
+ # no valid image AND no video/slides/embed at all.
2576
+ if not has_img and not has_content and not vid:
2577
+ removed += 1
2578
+ removed_ids.append(p.get('id', '?'))
2579
+ continue
2580
+ kept.append(p)
2581
+ # Sort if any post lacks proper ordering
2582
+ prev_ts = None
2583
+ is_sorted = True
2584
+ for p in kept:
2585
+ ts = _created_ts(p)
2586
+ if prev_ts is not None and ts > prev_ts:
2587
+ is_sorted = False
2588
+ break
2589
+ prev_ts = ts
2590
+ if removed > 0 or not is_sorted:
2591
  kept.sort(key=_created_ts, reverse=True)
2592
  _save_wall_posts(kept)
2593
+ print(f"[wall] startup cleanup: removed {removed} garbage posts ({removed_ids[:10]}), kept {len(kept)}, sorted newest-first")
2594
  except Exception as e:
2595
  print(f"[wall] startup cleanup error: {e}")
2596