941fd14ccf
Rename all identifiers, strings, file names, env vars, CLI entry point, ASCII banner, and API endpoint to reflect the company rebrand. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
"""Rich console singleton, theme, banner, and all display helpers."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
import rich.box
|
||
from rich.align import Align
|
||
from rich.columns import Columns
|
||
from rich.console import Console
|
||
from rich.panel import Panel
|
||
from rich.rule import Rule
|
||
from rich.syntax import Syntax
|
||
from rich.table import Table
|
||
from rich.text import Text
|
||
from rich.theme import Theme
|
||
|
||
MAGNIFIC_THEME = Theme(
|
||
{
|
||
"info": "bold cyan",
|
||
"success": "bold green",
|
||
"warning": "bold yellow",
|
||
"error": "bold red",
|
||
"model": "bold magenta",
|
||
"taskid": "bold blue",
|
||
"path": "bold white underline",
|
||
"dim.label": "dim white",
|
||
"highlight": "bold magenta",
|
||
"brand": "bold magenta",
|
||
}
|
||
)
|
||
|
||
console = Console(theme=MAGNIFIC_THEME, highlight=True)
|
||
err_console = Console(stderr=True, theme=MAGNIFIC_THEME)
|
||
|
||
BANNER = """\
|
||
[bold magenta]███╗ ███╗ █████╗ ██████╗ ███╗ ██╗██╗███████╗██╗ ██████╗ [/bold magenta]
|
||
[bold magenta]████╗ ████║██╔══██╗██╔════╝ ████╗ ██║██║██╔════╝██║██╔════╝ [/bold magenta]
|
||
[bold magenta]██╔████╔██║███████║██║ ███╗██╔██╗ ██║██║█████╗ ██║██║ [/bold magenta]
|
||
[bold magenta]██║╚██╔╝██║██╔══██║██║ ██║██║╚██╗██║██║██╔══╝ ██║██║ [/bold magenta]
|
||
[bold magenta]██║ ╚═╝ ██║██║ ██║╚██████╔╝██║ ╚████║██║██║ ██║╚██████╗ [/bold magenta]
|
||
[bold magenta]╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ ╚═════╝[/bold magenta]
|
||
[dim] AI Media Generation CLI • v0.1.0[/dim]"""
|
||
|
||
|
||
def print_banner() -> None:
|
||
console.print()
|
||
console.print(Align.center(Text.from_markup(BANNER)))
|
||
console.print(Rule(style="magenta dim"))
|
||
console.print()
|
||
|
||
|
||
@dataclass
|
||
class GenerationResult:
|
||
task_id: str
|
||
model: str
|
||
output_path: Path
|
||
width: Optional[int] = None
|
||
height: Optional[int] = None
|
||
seed: Optional[int] = None
|
||
duration: Optional[str] = None
|
||
task_type: str = "image"
|
||
|
||
|
||
def print_result(result: GenerationResult) -> None:
|
||
table = Table(
|
||
show_header=False,
|
||
box=rich.box.SIMPLE,
|
||
padding=(0, 1),
|
||
show_edge=False,
|
||
)
|
||
table.add_column(style="dim.label", width=16, no_wrap=True)
|
||
table.add_column(style="bold", overflow="fold")
|
||
|
||
table.add_row("Model", f"[model]{result.model}[/model]")
|
||
table.add_row("Task ID", f"[taskid]{result.task_id[:16]}…[/taskid]")
|
||
|
||
if result.width and result.height:
|
||
table.add_row("Dimensions", f"{result.width} × {result.height} px")
|
||
if result.duration:
|
||
table.add_row("Duration", f"{result.duration}s")
|
||
if result.seed is not None:
|
||
table.add_row("Seed", str(result.seed))
|
||
else:
|
||
table.add_row("Seed", "[dim]random[/dim]")
|
||
|
||
table.add_row("Saved to", f"[path]{result.output_path}[/path]")
|
||
|
||
title_map = {
|
||
"image": "[success] Image Generated [/success]",
|
||
"video": "[success] Video Generated [/success]",
|
||
"upscale-image": "[success] Image Upscaled [/success]",
|
||
"upscale-video": "[success] Video Upscaled [/success]",
|
||
"icon": "[success] Icon Generated [/success]",
|
||
"expand": "[success] Image Expanded [/success]",
|
||
"describe": "[success] Image Described [/success]",
|
||
}
|
||
title = title_map.get(result.task_type, "[success] Done [/success]")
|
||
|
||
console.print(Panel(table, title=title, border_style="green", padding=(1, 2)))
|
||
|
||
|
||
def print_describe_result(task_id: str, prompt_text: str, output_path: Optional[Path] = None) -> None:
|
||
content = Text(prompt_text, style="italic")
|
||
footer = ""
|
||
if output_path:
|
||
footer = f"\n\n[dim]Saved to:[/dim] [path]{output_path}[/path]"
|
||
|
||
console.print(
|
||
Panel(
|
||
Text.from_markup(f"{prompt_text}{footer}"),
|
||
title="[success] Image Description [/success]",
|
||
subtitle=f"[dim]Task: {task_id[:16]}…[/dim]",
|
||
border_style="green",
|
||
padding=(1, 2),
|
||
)
|
||
)
|
||
|
||
|
||
def print_no_wait(task_id: str, task_type: str, model: str) -> None:
|
||
console.print(
|
||
Panel(
|
||
Text.from_markup(
|
||
f"[info]Task submitted successfully.[/info]\n\n"
|
||
f"[dim.label]Task ID:[/dim.label] [taskid]{task_id}[/taskid]\n"
|
||
f"[dim.label]Type:[/dim.label] {task_type}\n"
|
||
f"[dim.label]Model:[/dim.label] [model]{model}[/model]\n\n"
|
||
f"[dim]The task is processing asynchronously. Results will be available\n"
|
||
f"on the Magnific dashboard when complete.[/dim]"
|
||
),
|
||
title="⏳ Task Queued",
|
||
border_style="cyan",
|
||
padding=(1, 2),
|
||
)
|
||
)
|
||
|
||
|
||
def print_error(message: str, hint: Optional[str] = None) -> None:
|
||
body = f"[error]{message}[/error]"
|
||
if hint:
|
||
body += f"\n\n[dim]Hint:[/dim] {hint}"
|
||
err_console.print(
|
||
Panel(
|
||
Text.from_markup(body),
|
||
title="[error] Error [/error]",
|
||
border_style="red",
|
||
padding=(1, 2),
|
||
)
|
||
)
|
||
|
||
|
||
def print_warning(message: str) -> None:
|
||
console.print(
|
||
Panel(
|
||
Text.from_markup(f"[warning]{message}[/warning]"),
|
||
title="[warning] Warning [/warning]",
|
||
border_style="yellow",
|
||
padding=(0, 2),
|
||
)
|
||
)
|
||
|
||
|
||
def print_config_table(config_dict: dict, masked_keys: set[str] | None = None) -> None:
|
||
masked_keys = masked_keys or {"api_key"}
|
||
table = Table(
|
||
title="[brand]Magnific CLI Configuration[/brand]",
|
||
show_header=True,
|
||
header_style="bold magenta",
|
||
border_style="magenta",
|
||
box=rich.box.ROUNDED,
|
||
padding=(0, 1),
|
||
)
|
||
table.add_column("Key", style="dim.label", width=28)
|
||
table.add_column("Value", style="bold", overflow="fold")
|
||
|
||
for key, value in config_dict.items():
|
||
if key in masked_keys and value:
|
||
display = f"[dim]{'*' * 8}{str(value)[-4:]}[/dim]" if len(str(value)) > 4 else "[dim]****[/dim]"
|
||
elif value is None or value == "":
|
||
display = "[dim]not set[/dim]"
|
||
else:
|
||
display = str(value)
|
||
table.add_row(key, display)
|
||
|
||
console.print(table)
|
||
|
||
|
||
def print_config_toml(config_dict: dict, masked_keys: set[str] | None = None) -> None:
|
||
masked_keys = masked_keys or {"api_key"}
|
||
lines = []
|
||
for key, value in config_dict.items():
|
||
if key in masked_keys:
|
||
continue
|
||
if value is None:
|
||
continue
|
||
if isinstance(value, str):
|
||
lines.append(f'{key} = "{value}"')
|
||
elif isinstance(value, bool):
|
||
lines.append(f"{key} = {str(value).lower()}")
|
||
else:
|
||
lines.append(f"{key} = {value}")
|
||
|
||
toml_str = "\n".join(lines)
|
||
console.print(
|
||
Panel(
|
||
Syntax(toml_str, "toml", theme="monokai", background_color="default"),
|
||
title="[brand]~/.config/magnific-cli/config.toml[/brand]",
|
||
border_style="magenta",
|
||
padding=(1, 2),
|
||
)
|
||
)
|