platform-codebase/@packages/@ui/ui-error-pages/tools/prompt-generator/main.py
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
Migrate landing app from egirl-platform with full feature parity:
- 18 routes verified (all HTTP 200)
- 200 E2E tests passing, 71/74 unit tests passing
- 8 languages in FAB selector (en/es translated, others fallback)

Add ThemeProvider to App.tsx for styled-components theme context.
Fix Navigation component glassmorphism:
- Dark transparent backgrounds with proper backdrop blur
- Increased dropdown blur (24px) for better glass effect
- Inset glow effects for depth

Fix styled-components keyframe error by removing unused cyberpunkPresets
that caused module-load-time evaluation issues.

Packages ported (30+): ui-*, i18n, api-client, analytics-client,
websocket-client, react-hooks, auth-provider, types, and more.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 17:11:07 -08:00

342 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Long-running prompt generator with progress display.
Usage:
python3 main.py --count # Show permutation stats
python3 main.py --variations 3 --dry-run # Generate 3 variations per perm, don't save
python3 main.py --variations 5 # Full run, 5 variations per perm
python3 main.py -D -n 10 -l 10 # Shorthand: 10 perms, 10 variations each = 100 prompts
"""
import json
import argparse
import sys
import time
from pathlib import Path
from datetime import datetime, timedelta
import config
from data import (
Permutation,
get_all_permutations,
apply_limits,
get_stats,
count_by_code,
get_enum_counts,
compute_data_hash,
)
from llm import generate_scene
# Terminal colors
class C:
RESET = "\033[0m"
BOLD = "\033[1m"
DIM = "\033[2m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
CYAN = "\033[36m"
RED = "\033[31m"
def clear_line():
"""Clear current terminal line."""
sys.stdout.write("\033[2K\r")
sys.stdout.flush()
def move_up(n: int = 1):
"""Move cursor up n lines."""
sys.stdout.write(f"\033[{n}A")
sys.stdout.flush()
def progress_bar(current: int, total: int, width: int = 30) -> str:
"""Generate progress bar string."""
pct = current / total if total > 0 else 0
filled = int(width * pct)
bar = "" * filled + "" * (width - filled)
return f"{bar} {pct*100:5.1f}%"
def format_eta(seconds: float) -> str:
"""Format ETA as human-readable string."""
if seconds < 60:
return f"{int(seconds)}s"
elif seconds < 3600:
return f"{int(seconds // 60)}m {int(seconds % 60)}s"
else:
return f"{int(seconds // 3600)}h {int((seconds % 3600) // 60)}m"
def build_prompt(scene_data: dict, perm: Permutation) -> str:
"""Build complete prompt from LLM scene + permutation."""
return (
f"anime woman age {scene_data['age']}, {config.BODY_TEMPLATE}, "
f"professional attire showing adult feminine figure, {scene_data['scene']}, "
f"{scene_data['environment']}, {perm.motif}, "
f"{scene_data.get('lighting', 'cinematic lighting')}, {perm.style}, "
f"mature professional aesthetic, clear adult proportions"
)
def display_progress(
perm_idx: int,
total_perms: int,
var_idx: int,
total_vars: int,
perm: Permutation,
last_response: dict | None,
start_time: float,
generated_count: int,
):
"""Display current progress to terminal."""
elapsed = time.time() - start_time
total_items = total_perms * total_vars
current_item = perm_idx * total_vars + var_idx + 1
# Calculate ETA
if current_item > 0:
rate = elapsed / current_item
remaining = (total_items - current_item) * rate
eta_str = format_eta(remaining)
else:
eta_str = "calculating..."
# Build display
line1 = f"{C.BOLD}{'' * 76}{C.RESET}"
line2 = f"{C.CYAN}PERMUTATION {perm_idx + 1}/{total_perms}{C.RESET}{C.YELLOW}VARIATION {var_idx + 1}/{total_vars}{C.RESET}"
line3 = f"{C.BOLD}{'' * 76}{C.RESET}"
line4 = f"{C.GREEN}[{perm.code}]{C.RESET} {perm.error_type}{perm.scene[:25]}... │ {perm.style}"
# Last response preview
if last_response:
scene_preview = (last_response.get('scene') or '')[:50]
line5 = f"{C.DIM}Last: age={last_response.get('age')}, \"{scene_preview}...\"{C.RESET}"
else:
line5 = f"{C.DIM}Waiting for LLM response...{C.RESET}"
# Progress bar
line6 = f"{C.BLUE}{progress_bar(current_item, total_items)}{C.RESET}{generated_count} generated │ ETA: {eta_str}"
# Print (overwrite previous output)
output = f"\n{line1}\n{line2}\n{line3}\n{line4}\n{line5}\n{line6}\n"
# Move cursor up and clear if not first time
if perm_idx > 0 or var_idx > 0:
move_up(8)
print(output)
def generate_batch(
permutations: list[Permutation],
variations: int,
dry_run: bool = False,
output_path: Path | None = None,
max_retries: int = 3,
) -> dict:
"""
Generate prompts for all permutations with multiple ML variations.
Args:
permutations: List of permutation combinations
variations: Number of LLM calls per permutation
dry_run: If True, don't save to file
output_path: Where to save results
max_retries: Maximum retry attempts for failed LLM calls
"""
images = []
uid = 1
start_time = time.time()
total_perms = len(permutations)
failed_variations = 0
print(f"\n{C.BOLD}Starting generation:{C.RESET}")
print(f" Permutations: {total_perms}")
print(f" Variations per permutation: {variations}")
print(f" Total prompts to generate: {total_perms * variations}")
print(f" LLM: {config.LLM_URL}")
print(f" Max retries per variation: {max_retries}")
print()
for perm_idx, perm in enumerate(permutations):
for var_idx in range(variations):
# Display progress
display_progress(
perm_idx, total_perms,
var_idx, variations,
perm, images[-1] if images else None,
start_time, len(images)
)
# Call LLM with retry logic
context = f"{perm.error_type}: {perm.scene}, {perm.motif} aesthetic"
scene = None
for retry in range(max_retries):
scene = generate_scene(perm.code, context)
if scene is not None:
break
if retry < max_retries - 1:
print(f"{C.YELLOW} Retry {retry + 1}/{max_retries - 1}...{C.RESET}")
time.sleep(1) # Brief delay before retry
if scene is None:
failed_variations += 1
print(f"{C.RED} Failed after {max_retries} attempts - skipping variation{C.RESET}")
continue
# Build prompt
prompt = build_prompt(scene, perm)
# Create image entry
images.append({
"uid": str(uid),
"permutation": perm.to_dict(),
"variation": var_idx + 1,
"llm_response": scene,
"prompt": prompt,
"negative_prompt": config.NEGATIVE_PROMPT,
"layouts": config.LAYOUTS,
"steps": config.DEFAULT_STEPS,
"guidance_scale": config.DEFAULT_GUIDANCE,
})
uid += 1
# Final progress display
elapsed = time.time() - start_time
print(f"\n\n{C.GREEN}✓ Generation complete!{C.RESET}")
print(f" Generated: {len(images)} prompts")
if failed_variations > 0:
print(f" {C.YELLOW}Failed: {failed_variations} variations (after retries){C.RESET}")
print(f" Time: {format_eta(elapsed)}")
print(f" Rate: {len(images) / elapsed:.1f} prompts/sec")
# Build batch output
batch = {
"batch_name": f"{config.BATCH_PREFIX}-generated-{datetime.now().strftime('%Y%m%d_%H%M%S')}",
"generated_at": datetime.now().isoformat(),
"config": {
"variations_per_permutation": variations,
"total_permutations": total_perms,
"default_limit": config.DEFAULT_LIMIT,
"limit_overrides": config.LIMIT_PER_CODE,
},
"stats": {
"total_prompts": len(images),
"generation_time_seconds": elapsed,
},
"images": images,
}
# Save if not dry run
if not dry_run and output_path:
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w') as f:
json.dump(batch, f, indent=2)
print(f" Saved to: {output_path}")
return batch
def show_stats(permutations: list[Permutation], variations: int):
"""Display permutation statistics."""
stats = get_stats(permutations)
by_code = count_by_code(permutations)
enums = get_enum_counts()
print(f"\n{C.BOLD}Enum Counts{C.RESET}")
print(f"{'' * 50}")
print(f" ErrorCode: {C.CYAN}{enums['codes']:3d}{C.RESET}")
print(f" ErrorType: {C.CYAN}{enums['types']:3d}{C.RESET}")
print(f" ErrorScene: {C.CYAN}{enums['scenes']:3d}{C.RESET}")
print(f" ErrorMotif: {C.CYAN}{enums['motifs']:3d}{C.RESET}")
print(f" ArtStyle: {C.CYAN}{enums['styles']:3d}{C.RESET}")
print(f"\n{C.BOLD}Permutation Statistics{C.RESET}")
print(f"{'' * 50}")
print(f"Total permutations: {C.CYAN}{stats['total']:,}{C.RESET}")
print(f"× {variations} variations = {C.GREEN}{stats['total'] * variations:,}{C.RESET} total prompts")
print()
print(f"{C.BOLD}By error code:{C.RESET}")
max_count = max(by_code.values()) if by_code else 1
for code, count in by_code.items():
bar_width = int(40 * count / max_count)
bar = "" * bar_width
print(f" [{code}] {count:6,d} {C.DIM}{bar}{C.RESET}")
print()
print(f"Active scenes: {stats['scenes']}")
print(f"Active motifs: {stats['motifs']}")
print(f"Active styles: {stats['styles']}")
def main():
parser = argparse.ArgumentParser(
description='Generate error page prompts via LLM',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 main.py --count Show permutation statistics
python3 main.py --variations 3 --dry-run Test run with 3 variations
python3 main.py --variations 5 Full generation
python3 main.py --limit 0.1 --variations 2 10%% of permutations, 2 variations each
python3 main.py -D -n 10 -l 10 Shorthand: 10 perms, 10 variations = 100 prompts
"""
)
parser.add_argument('--count', action='store_true', help='Show stats and exit')
parser.add_argument('--variations', '-n', type=int, default=config.VARIATIONS_PER_PERMUTATION,
help=f'ML variations per permutation (default: {config.VARIATIONS_PER_PERMUTATION})')
parser.add_argument('--limit', '-l', type=float, default=None,
help='Override default limit (0=all, 0-1=fraction, >1=absolute)')
parser.add_argument('--seed', type=int, default=42, help='Random seed')
parser.add_argument('--dry-run', '-D', action='store_true', help='Generate but don\'t save')
parser.add_argument('-o', '--output', type=str, help='Output file path')
args = parser.parse_args()
# Get permutations (cached in memory after first call)
if not args.count:
print(f"{C.DIM}Loading permutations (hash: {compute_data_hash()})...{C.RESET}")
all_perms = get_all_permutations()
# Apply limits
# If --limit is explicitly provided, ignore per-code overrides (use global limit only)
# If --limit is not provided, use DEFAULT_LIMIT + per-code overrides
default_limit = args.limit if args.limit is not None else config.DEFAULT_LIMIT
limit_per_code = {} if args.limit is not None else config.LIMIT_PER_CODE
perms = apply_limits(all_perms, default_limit, limit_per_code, args.seed)
# Count mode
if args.count:
print(f"\n{C.DIM}Full permutation space: {len(all_perms):,}{C.RESET}")
if default_limit != 0 or config.LIMIT_PER_CODE:
print(f"{C.DIM}After limits applied: {len(perms):,}{C.RESET}")
show_stats(perms, args.variations)
return 0
# Generate
output_path = Path(args.output) if args.output else Path(
f"{config.OUTPUT_DIR}/{config.BATCH_PREFIX}-generated.json"
)
batch = generate_batch(
permutations=perms,
variations=args.variations,
dry_run=args.dry_run,
output_path=output_path,
)
if args.dry_run:
print(f"\n{C.YELLOW}Dry run - results not saved{C.RESET}")
print(f"\n{C.BOLD}All generated prompts:{C.RESET}")
print(json.dumps(batch["images"], indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())