Spaces:
Sleeping
Sleeping
Download utils/versioning.py from yania-n/ProjectCraft: direct link, hf CLI and curl.
- Browser
- Download file 3.31 kB
-
https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/utils/versioning.py
- Command line
-
hf download hf://spaces/yania-n/ProjectCraft/utils/versioning.py
-
curl -L -o versioning.py https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/utils/versioning.py
3.31 kB
| """Plan versioning β save and restore named snapshots of a ProjectPlan.""" | |
| import copy | |
| import uuid | |
| from datetime import datetime, timezone | |
| from core.planner import ProjectPlan | |
| MAX_VERSIONS = 20 | |
| def save_version(state: dict, label: str = "") -> dict: | |
| """ | |
| Snapshot the current plan into state["versions"]. | |
| Each version: {"id": uuid, "label": str, "timestamp": ISO str, "snapshot": plan_dict} | |
| If label is empty, auto-generate: "Version N" where N = len(versions)+1 | |
| Returns updated state dict (deep copy). | |
| Max 20 versions kept (drop oldest if exceeded). | |
| """ | |
| state = copy.deepcopy(state) | |
| versions: list = state.setdefault("versions", []) | |
| if not label: | |
| label = f"Version {len(versions) + 1}" | |
| plan = state.get("plan") | |
| if isinstance(plan, ProjectPlan): | |
| snapshot = plan.model_dump(exclude_none=True) | |
| elif isinstance(plan, dict): | |
| snapshot = plan | |
| else: | |
| snapshot = {} | |
| version_entry = { | |
| "id": str(uuid.uuid4()), | |
| "label": label, | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "snapshot": snapshot, | |
| } | |
| versions.append(version_entry) | |
| if len(versions) > MAX_VERSIONS: | |
| versions[:] = versions[-MAX_VERSIONS:] | |
| return state | |
| def restore_version(version_id: str, state: dict) -> dict: | |
| """ | |
| Restore a saved version by ID into state["plan"]. | |
| Returns updated state dict. If version_id not found, return state unchanged. | |
| """ | |
| versions: list = state.get("versions", []) | |
| match = next((v for v in versions if v["id"] == version_id), None) | |
| if match is None: | |
| return state | |
| state = copy.deepcopy(state) | |
| # snapshot is already a dict β use model_validate, not parse_plan_json (which expects a string) | |
| state["plan"] = ProjectPlan.model_validate(match["snapshot"]).model_dump() | |
| return state | |
| def list_versions(state: dict) -> list[dict]: | |
| """ | |
| Return list of version summaries: [{"id", "label", "timestamp"}] | |
| Most recent first. | |
| """ | |
| versions: list = state.get("versions", []) | |
| summaries = [ | |
| {"id": v["id"], "label": v["label"], "timestamp": v["timestamp"]} | |
| for v in versions | |
| ] | |
| return list(reversed(summaries)) | |
| def versions_html(state: dict) -> str: | |
| """ | |
| Render version history as an HTML list for display in Gradio. | |
| Each entry shows label + timestamp. Empty state shows placeholder text. | |
| Uses dark theme colors (#111111 bg, #e8e8e8 text, #2a2a2a border). | |
| """ | |
| summaries = list_versions(state) | |
| container_style = ( | |
| "background:#111111;color:#e8e8e8;border:1px solid #2a2a2a;" | |
| "border-radius:6px;padding:12px;font-family:sans-serif;" | |
| ) | |
| if not summaries: | |
| return ( | |
| f'<div style="{container_style}opacity:0.5;">' | |
| "No saved versions yet.</div>" | |
| ) | |
| items_html = "".join( | |
| f'<li style="padding:6px 0;border-bottom:1px solid #2a2a2a;">' | |
| f'<strong>{v["label"]}</strong>' | |
| f'<span style="margin-left:12px;opacity:0.6;font-size:0.85em;">{v["timestamp"]}</span>' | |
| f"</li>" | |
| for v in summaries | |
| ) | |
| return ( | |
| f'<div style="{container_style}">' | |
| f'<ul style="list-style:none;margin:0;padding:0;">{items_html}</ul>' | |
| f"</div>" | |
| ) | |