更新功能
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Explicitly remove attachments belonging to conversations without v2 image observations.
|
||||
|
||||
The command is dry-run by default. It only mutates data when ``--apply`` is
|
||||
provided, and it never removes CAD task artifacts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
BACKEND_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(BACKEND_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(BACKEND_ROOT))
|
||||
|
||||
from app.services.storage import read_json, write_json
|
||||
from app.settings import get_settings
|
||||
|
||||
|
||||
def _has_v2_observation(record: dict) -> bool:
|
||||
for message in record.get("messages") or []:
|
||||
if not isinstance(message, dict):
|
||||
continue
|
||||
for part in message.get("parts") or []:
|
||||
if not isinstance(part, dict) or part.get("type") != "data-cad-image-analysis":
|
||||
continue
|
||||
data = part.get("data")
|
||||
if isinstance(data, dict) and str(data.get("schemaVersion") or data.get("schema_version") or "") == "cad.image-observation.v2":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def plan_cleanup(root: Path) -> list[tuple[Path, str]]:
|
||||
planned: list[tuple[Path, str]] = []
|
||||
for conversation_dir in sorted(root.glob("conv_*")):
|
||||
record_path = conversation_dir / "conversation.json"
|
||||
record = read_json(record_path)
|
||||
if not isinstance(record, dict) or _has_v2_observation(record):
|
||||
continue
|
||||
uploads = conversation_dir / "uploads"
|
||||
if uploads.is_dir():
|
||||
planned.append((uploads, "legacy upload directory"))
|
||||
planning = conversation_dir / "planning"
|
||||
if planning.is_dir():
|
||||
planned.append((planning, "legacy planning directory"))
|
||||
if record.get("attachments"):
|
||||
planned.append((record_path, "remove legacy attachment metadata and image-analysis parts"))
|
||||
return planned
|
||||
|
||||
|
||||
def apply_cleanup(root: Path) -> int:
|
||||
changed = 0
|
||||
for conversation_dir in sorted(root.glob("conv_*")):
|
||||
record_path = conversation_dir / "conversation.json"
|
||||
record = read_json(record_path)
|
||||
if not isinstance(record, dict) or _has_v2_observation(record):
|
||||
continue
|
||||
for relative in ("uploads", "planning"):
|
||||
target = conversation_dir / relative
|
||||
if target.is_dir():
|
||||
shutil.rmtree(target)
|
||||
changed += 1
|
||||
record["attachments"] = []
|
||||
for message in record.get("messages") or []:
|
||||
if not isinstance(message, dict):
|
||||
continue
|
||||
message["parts"] = [
|
||||
part for part in message.get("parts") or []
|
||||
if not (isinstance(part, dict) and part.get("type") == "data-cad-image-analysis")
|
||||
]
|
||||
write_json(record_path, record)
|
||||
changed += 1
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Remove legacy conversation attachments")
|
||||
parser.add_argument("--apply", action="store_true", help="perform deletion; default is dry-run")
|
||||
parser.add_argument("--dry-run", action="store_true", help="list deletion targets without changing files")
|
||||
args = parser.parse_args()
|
||||
root = get_settings().conversation_root.resolve()
|
||||
if root.name != "conversations":
|
||||
raise SystemExit(f"Refusing unexpected conversation root: {root}")
|
||||
planned = plan_cleanup(root)
|
||||
for path, reason in planned:
|
||||
print(f"{'DELETE' if args.apply else 'WOULD DELETE'} {path} ({reason})")
|
||||
if not args.apply:
|
||||
print(f"Dry-run: {len(planned)} targets. Re-run with --apply to delete.")
|
||||
return 0
|
||||
print(f"Deleted {apply_cleanup(root)} conversation records/directories.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user