diff --git a/.coverage b/.coverage index ee3e97a..c3b6e90 100644 Binary files a/.coverage and b/.coverage differ diff --git a/gguf.sh b/gguf.sh new file mode 100755 index 0000000..87f5058 --- /dev/null +++ b/gguf.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Convert all safetensors to q8_0 GGUF format (skip if already exists) + +set -euo pipefail + +for sf in *.safetensors; do + [[ -f "$sf" ]] || continue + + base="${sf%.safetensors}" + gguf="${base}-q8_0.gguf" + + if [[ -f "$gguf" ]]; then + echo "Skip: $gguf exists" + continue + fi + + echo "Converting: $sf -> $gguf" + sd -M convert -m "$sf" -o "$gguf" --type q8_0 +done diff --git a/tensors/cli.py b/tensors/cli.py index d0b3b1c..5b12c12 100644 --- a/tensors/cli.py +++ b/tensors/cli.py @@ -48,6 +48,18 @@ from tensors.safetensor import compute_sha256, get_base_name, read_safetensor_me # Key masking threshold MIN_KEY_LENGTH_FOR_MASKING = 8 +# Size threshold for GB display +_MB_PER_GB = 1024 + + +def _format_size_mb(size_mb: float | None) -> str: + """Format size in MB to human-readable string.""" + if not size_mb: + return "" + if size_mb >= _MB_PER_GB: + return f"{size_mb / _MB_PER_GB:.1f} GB" + return f"{size_mb:.0f} MB" + def _version_callback(value: bool) -> None: if value: @@ -1016,16 +1028,25 @@ def models_list( return table = Table(title="Available Models", show_header=True, header_style="bold magenta") - table.add_column("Status", style="dim", width=3) + table.add_column("ID", style="dim", width=8) table.add_column("Name", style="cyan") - table.add_column("Path", style="dim") + table.add_column("File", style="white") + table.add_column("Size", style="green", justify="right") for model in models: path = model.get("path", "") name = model.get("name", Path(path).stem if path else "") is_active = active in {path, name} - status = "[green]✓[/green]" if is_active else "" - table.add_row(status, name, path) + + civitai_id = model.get("civitai_model_id") + id_str = str(civitai_id) if civitai_id else "" + display_name = model.get("display_name", name) + if is_active: + display_name = f"[green]✓[/green] {display_name}" + filename = model.get("filename", Path(path).name if path else "") + size_str = _format_size_mb(model.get("size_mb")) + + table.add_row(id_str, display_name, filename, size_str) console.print(table) @@ -1091,13 +1112,22 @@ def models_loras( return table = Table(title="Available LoRAs", show_header=True, header_style="bold magenta") + table.add_column("ID", style="dim", width=8) table.add_column("Name", style="cyan") - table.add_column("Path", style="dim") + table.add_column("File", style="white") + table.add_column("Size", style="green", justify="right") for lora in loras: path = lora.get("path", "") name = lora.get("name", Path(path).stem if path else "") - table.add_row(name, path) + + civitai_id = lora.get("civitai_model_id") + id_str = str(civitai_id) if civitai_id else "" + display_name = lora.get("display_name", name) + filename = lora.get("filename", Path(path).name if path else "") + size_str = _format_size_mb(lora.get("size_mb")) + + table.add_row(id_str, display_name, filename, size_str) console.print(table) diff --git a/tensors/server/generate_routes.py b/tensors/server/generate_routes.py index 2a52a87..75bc8ec 100644 --- a/tensors/server/generate_routes.py +++ b/tensors/server/generate_routes.py @@ -5,6 +5,7 @@ from __future__ import annotations import base64 import logging import time +from pathlib import Path from typing import Any import httpx @@ -55,8 +56,16 @@ class GenerateRequest(PydanticBaseModel): def _build_sd_request(req: GenerateRequest) -> dict[str, Any]: """Build request body for sd-server.""" + prompt = req.prompt + + # sd-server expects LoRA in prompt as syntax + if req.lora: + lora_name = Path(req.lora.path).stem + lora_tag = f"" + prompt = f"{prompt} {lora_tag}" + body: dict[str, Any] = { - "prompt": req.prompt, + "prompt": prompt, "negative_prompt": req.negative_prompt, "width": req.width, "height": req.height, @@ -69,9 +78,6 @@ def _build_sd_request(req: GenerateRequest) -> dict[str, Any]: body["sampler_name"] = req.sampler_name if req.scheduler: body["scheduler"] = req.scheduler - # sd-server expects LoRA as JSON array, not in prompt - if req.lora: - body["lora"] = [{"path": req.lora.path, "multiplier": req.lora.multiplier}] return body