From 6c45a7560ec8345f6310c4b4221e1a9332135a1b Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Fri, 21 Aug 2026 21:29:00 +0300 Subject: [PATCH] Add capture wanted and harden Assistent wanted-queue merge. Co-authored-by: Cursor --- docs/cli.md | 1 + docs/models.md | 3 +- src/gpu_rent/capture.py | 66 ++++++++++++++++++++++++++++++++++++----- src/gpu_rent/cli.py | 16 ++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 3e6bb0f..70b780a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -79,6 +79,7 @@ gpu-rent up --yes --ollama | `gpu-rent pull-output` | VM `Output/` → `./Output` | | `gpu-rent capture` / `capture all` | Инвентарь VM → merge **ссылок** в `models.yaml` + `extensions.yaml` (веса не качать) | | `gpu-rent capture models` / `--kind lora` | Только модели (фильтр по типу) | +| `gpu-rent capture wanted` | Очередь Assistent Cards → `models.yaml` | | `gpu-rent capture extensions` | Только git Extensions / DLNodes | | `gpu-rent capture --dry-run` | Отчёт без записи файлов | | `gpu-rent resize-data --gb 400` | Data volume **только вверх** | diff --git a/docs/models.md b/docs/models.md index 0a16aeb..2bb3519 100644 --- a/docs/models.md +++ b/docs/models.md @@ -23,11 +23,12 @@ copy models.example.yaml models.yaml ```powershell .\gpu-rent.ps1 capture models # merge Civitai url → models.yaml .\gpu-rent.ps1 capture models --kind lora +.\gpu-rent.ps1 capture wanted # только очередь Assistent Cards → models.yaml .\gpu-rent.ps1 capture --dry-run # models + extensions, без записи .\gpu-rent.ps1 capture all ``` -Берёт `{stem}.civitai.json` или SHA256 → Civitai `by-hash`. Также мержит очередь Assistent `/mnt/swarm_data/.gpu-rent-wanted-models.yaml` (модели, добавленные из вкладки Cards). Дубли `modelVersionId` не дублируются. Перед записью — `models.yaml.bak`. Неизвестные файлы (нет в Civitai) — только в отчёте. +Берёт `{stem}.civitai.json` или SHA256 → Civitai `by-hash`. Также мержит очередь Assistent `/mnt/swarm_data/.gpu-rent-wanted-models.yaml` (модели, добавленные из вкладки Cards / Enqueue wanted) — на `capture models|wanted|all` и перед `seed-models` / `up`. Дубли `modelVersionId` не дублируются. Перед записью — `models.yaml.bak`. Неизвестные файлы (нет в Civitai) — только в отчёте. Локальный SwarmUI на `7801` не зеркалируем. Только дерево `./Models` приложения. diff --git a/src/gpu_rent/capture.py b/src/gpu_rent/capture.py index 2ab0341..3ceea1e 100644 --- a/src/gpu_rent/capture.py +++ b/src/gpu_rent/capture.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re import shutil from collections.abc import Callable from dataclasses import dataclass, field @@ -420,14 +421,15 @@ def merge_wanted_models_from_vm(cfg: Config, host: str, log: Log) -> int: ).strip() if not raw: return 0 - try: - data = yaml.safe_load(raw) or {} - except Exception as exc: - log(f"wanted-models: bad yaml — {exc}") - return 0 - if not isinstance(data, dict): - return 0 items: list[ModelCaptureItem] = [] + data: dict = {} + try: + parsed = yaml.safe_load(raw) or {} + if isinstance(parsed, dict): + data = parsed + except Exception as exc: + log(f"wanted-models: yaml parse — {exc}; fallback line scan") + for kind, rows in data.items(): if kind not in MODEL_DIRS or not isinstance(rows, list): continue @@ -451,6 +453,56 @@ def merge_wanted_models_from_vm(cfg: Config, host: str, log: Log) -> int: source="wanted", ) ) + + # Recover entries lost to duplicate YAML keys (old Assistent writers appended + # a fresh `lora:` block each time; PyYAML keeps only the last). + kind = "lora" + pending_url = None + pending_title = None + pending_vid = None + seen_urls = {i.url for i in items} + + def flush_pending() -> None: + nonlocal pending_url, pending_title, pending_vid + if not pending_url or pending_url in seen_urls: + pending_url = pending_title = pending_vid = None + return + vid_i = pending_vid or extract_version_id(pending_url) + items.append( + ModelCaptureItem( + kind=kind if kind in MODEL_DIRS else "lora", + url=pending_url, + title=str(pending_title or Path(pending_url).name), + version_id=vid_i, + source="wanted", + ) + ) + seen_urls.add(pending_url) + pending_url = pending_title = pending_vid = None + + for line in raw.splitlines(): + t = line.strip() + m_kind = re.match(r"^([A-Za-z0-9_-]+):\s*$", t) + if m_kind and not t.startswith("-"): + flush_pending() + kind = m_kind.group(1).strip().lower() + continue + m_url = re.match(r"^-\s*url:\s*[\"']?(.+?)[\"']?\s*$", t) + if m_url: + flush_pending() + pending_url = m_url.group(1).strip() + continue + if not pending_url: + continue + m_title = re.match(r"^title:\s*[\"']?(.+?)[\"']?\s*$", t) + if m_title: + pending_title = m_title.group(1).strip() + continue + m_vid = re.match(r"^version_id:\s*(\d+)\s*$", t) + if m_vid: + pending_vid = int(m_vid.group(1)) + flush_pending() + if not items: return 0 added, skipped = merge_models_yaml(Path(cfg.models_manifest), items, dry_run=False) diff --git a/src/gpu_rent/cli.py b/src/gpu_rent/cli.py index 58cd8e2..4404b51 100644 --- a/src/gpu_rent/cli.py +++ b/src/gpu_rent/cli.py @@ -986,6 +986,22 @@ def capture_models_cmd( _die(exc) +@capture_app.command("wanted") +def capture_wanted_cmd() -> None: + """Только очередь Assistent (.gpu-rent-wanted-models.yaml) → models.yaml.""" + try: + from gpu_rent.capture import merge_wanted_models_from_vm + + cfg, host = _live() + n = merge_wanted_models_from_vm(cfg, host, log) + if n: + log(f"wanted→models.yaml: +{n}") + else: + log("wanted→models.yaml: пусто или уже есть") + except GpuRentError as exc: + _die(exc) + + @capture_app.command("extensions") def capture_extensions_cmd( dry_run: bool = typer.Option(False, "--dry-run"),