Files
slahiriandClaude Opus 4.5 5086598eb8 feat: major refactor with prompt editor, improved templates, and reasoning control
## New Features
- Add web-based Prompt Editor at /sid/prompt-editor
  - Monaco editor with TOML syntax highlighting
  - File browser for prompt templates
  - Save with TOML validation and auto-backup
  - Reset to git default functionality

- Add prompt_generator_core.py for standalone batch processing
- Add sid_prompt_generator_v2.py with simplified interface
- Add sid_prompt_debug.py for prompt quality evaluation
- Add cv_analyzer.py for YOLO-based human detection

## Prompt Template Improvements
- Restructure prompts into separate TOML files (portrait, scene, classification)
- Add sentence-by-sentence structure for local models
- Add comprehensive hair texture vocabulary (straight/wavy/curly/bangs)
- Add makeup vocabulary (smoky eyes, lipstick colors)
- Improve basic tier prompts to output sentences instead of keyword lists

## API Call Optimization
- Standard mode: Single API call (ignores reasoning)
- Detailed + reasoning OFF: Single API call with detailed prompts
- Detailed + reasoning ON: Multi-step agentic analysis

## Dependencies
- Add aiohttp for web routes
- Add tomlkit for TOML editing with comment preservation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 14:05:01 +05:30

348 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
Results Storage Manager
Handles saving debug evaluation results to disk.
"""
import json
import uuid
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional
from PIL import Image
import io
import base64
class ResultsStorage:
"""
Manages storage of debug evaluation results.
Directory structure:
debug_results/
└── YYYY-MM-DD_HH-MM-SS_<session_id>/
├── source.jpg
├── output.jpg
├── prompt.txt
├── evaluation.json
└── metadata.json
"""
def __init__(self, base_path: Optional[Path] = None):
"""
Initialize the results storage.
Args:
base_path: Path to debug_results directory.
Defaults to toolkit's debug_results folder.
"""
if base_path is None:
self.base_path = Path(__file__).parent.parent / "debug_results"
else:
self.base_path = Path(base_path)
# Ensure directory exists
self.base_path.mkdir(parents=True, exist_ok=True)
def create_session(self) -> tuple[str, Path]:
"""
Create a new session directory.
Returns:
Tuple of (session_id, session_path)
"""
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
session_id = str(uuid.uuid4())[:8]
folder_name = f"{timestamp}_{session_id}"
session_path = self.base_path / folder_name
session_path.mkdir(parents=True, exist_ok=True)
return session_id, session_path
def save_image(self, session_path: Path, image_data: Any,
filename: str = "image.jpg") -> Dict[str, Any]:
"""
Save an image to the session directory.
Args:
session_path: Path to session directory
image_data: Image data (PIL Image, base64 string, or tensor)
filename: Output filename
Returns:
Dictionary with image metadata
"""
file_path = session_path / filename
# Convert to PIL Image if needed
if isinstance(image_data, str):
# Base64 string
image_bytes = base64.b64decode(image_data)
pil_image = Image.open(io.BytesIO(image_bytes))
elif hasattr(image_data, 'cpu'):
# PyTorch tensor
import numpy as np
if len(image_data.shape) == 4:
image_data = image_data[0]
np_image = (image_data.cpu().numpy() * 255).astype(np.uint8)
pil_image = Image.fromarray(np_image)
elif isinstance(image_data, Image.Image):
pil_image = image_data
else:
raise ValueError(f"Unsupported image type: {type(image_data)}")
# Save image
pil_image.save(file_path, "JPEG", quality=95)
# Get file size
file_size_kb = file_path.stat().st_size / 1024
return {
"path": str(file_path),
"relative_path": filename,
"dimensions": list(pil_image.size),
"format": "JPEG",
"file_size_kb": round(file_size_kb, 1)
}
def save_prompt(self, session_path: Path, prompt: str) -> Dict[str, Any]:
"""
Save the prompt text to the session directory.
Args:
session_path: Path to session directory
prompt: Prompt text
Returns:
Dictionary with prompt metadata
"""
file_path = session_path / "prompt.txt"
with open(file_path, "w", encoding="utf-8") as f:
f.write(prompt)
return {
"path": str(file_path),
"word_count": len(prompt.split()),
"character_count": len(prompt),
"content": prompt
}
def save_metadata(self, session_path: Path, metadata: Dict[str, Any]) -> bool:
"""
Save session metadata.
Args:
session_path: Path to session directory
metadata: Metadata dictionary
Returns:
True if successful
"""
file_path = session_path / "metadata.json"
# Add timestamp if not present
if "timestamp" not in metadata:
metadata["timestamp"] = datetime.utcnow().isoformat() + "Z"
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(metadata, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"[Storage] Error saving metadata: {e}")
return False
def save_evaluation(self, session_path: Path, evaluation: Dict[str, Any]) -> bool:
"""
Save the full evaluation results.
Args:
session_path: Path to session directory
evaluation: Full evaluation dictionary
Returns:
True if successful
"""
file_path = session_path / "evaluation.json"
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(evaluation, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"[Storage] Error saving evaluation: {e}")
return False
def save_full_session(
self,
source_image: Any,
output_image: Any,
prompt: str,
model_config: Dict[str, Any],
evaluation: Dict[str, Any],
generator_settings: Optional[Dict[str, Any]] = None,
system_info: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Save a complete debug session.
Args:
source_image: Source image data
output_image: Output image data
prompt: Generated prompt
model_config: LLM model configuration
evaluation: Full evaluation results
generator_settings: Optional generator settings
system_info: Optional system information
Returns:
Dictionary with session info and paths
"""
session_id, session_path = self.create_session()
# Save images
source_info = self.save_image(session_path, source_image, "source.jpg")
output_info = self.save_image(session_path, output_image, "output.jpg")
# Save prompt
prompt_info = self.save_prompt(session_path, prompt)
# Build metadata
metadata = {
"session_id": session_id,
"timestamp": datetime.utcnow().isoformat() + "Z",
"model_config": model_config,
}
if generator_settings:
metadata["generator_settings"] = generator_settings
if system_info:
metadata["system_info"] = system_info
# Save metadata
self.save_metadata(session_path, metadata)
# Add input info to evaluation
evaluation["inputs"] = {
"source_image": source_info,
"output_image": output_info,
"prompt": prompt_info,
}
evaluation["debug_session"] = {
"id": session_id,
"timestamp": metadata["timestamp"],
"path": str(session_path),
}
# Save evaluation
self.save_evaluation(session_path, evaluation)
return {
"session_id": session_id,
"path": str(session_path),
"files": {
"source": source_info,
"output": output_info,
"prompt": prompt_info,
"metadata": str(session_path / "metadata.json"),
"evaluation": str(session_path / "evaluation.json"),
}
}
def list_sessions(self, limit: int = 50) -> list[Dict[str, Any]]:
"""
List recent debug sessions.
Args:
limit: Maximum number of sessions to return
Returns:
List of session info dictionaries
"""
sessions = []
if not self.base_path.exists():
return sessions
# Get all session directories
session_dirs = sorted(
[d for d in self.base_path.iterdir() if d.is_dir()],
reverse=True # Most recent first
)[:limit]
for session_dir in session_dirs:
session_info = {
"path": str(session_dir),
"name": session_dir.name,
}
# Try to load metadata
metadata_path = session_dir / "metadata.json"
if metadata_path.exists():
try:
with open(metadata_path, "r") as f:
metadata = json.load(f)
session_info["timestamp"] = metadata.get("timestamp")
session_info["model"] = metadata.get("model_config", {}).get("model")
except:
pass
# Try to get score from evaluation
eval_path = session_dir / "evaluation.json"
if eval_path.exists():
try:
with open(eval_path, "r") as f:
evaluation = json.load(f)
session_info["score"] = evaluation.get("evaluation", {}).get("scores", {}).get("overall", {}).get("score")
except:
pass
sessions.append(session_info)
return sessions
def load_session(self, session_path: str) -> Optional[Dict[str, Any]]:
"""
Load a previous session's data.
Args:
session_path: Path to session directory
Returns:
Session data dictionary or None if not found
"""
path = Path(session_path)
if not path.exists():
return None
session_data = {"path": str(path)}
# Load metadata
metadata_path = path / "metadata.json"
if metadata_path.exists():
with open(metadata_path, "r") as f:
session_data["metadata"] = json.load(f)
# Load evaluation
eval_path = path / "evaluation.json"
if eval_path.exists():
with open(eval_path, "r") as f:
session_data["evaluation"] = json.load(f)
# Load prompt
prompt_path = path / "prompt.txt"
if prompt_path.exists():
with open(prompt_path, "r") as f:
session_data["prompt"] = f.read()
# Check for images
session_data["has_source"] = (path / "source.jpg").exists()
session_data["has_output"] = (path / "output.jpg").exists()
return session_data