mirror of https://github.com/razor-ai/soup.git
151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
"""soup recipes — browse and use ready-made configs for popular models."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.syntax import Syntax
|
|
from rich.table import Table
|
|
|
|
console = Console()
|
|
|
|
app = typer.Typer(no_args_is_help=True)
|
|
|
|
|
|
@app.command(name="list")
|
|
def list_cmd():
|
|
"""List all available recipes."""
|
|
from soup_cli.recipes.catalog import RECIPES
|
|
|
|
table = Table(title="Soup Recipes")
|
|
table.add_column("Name", style="bold cyan")
|
|
table.add_column("Model", style="green")
|
|
table.add_column("Task", style="yellow")
|
|
table.add_column("Size", style="magenta")
|
|
table.add_column("Description")
|
|
|
|
for name, recipe in RECIPES.items():
|
|
table.add_row(name, recipe.model, recipe.task, recipe.size, recipe.description)
|
|
|
|
console.print(table)
|
|
|
|
|
|
@app.command()
|
|
def show(
|
|
name: str = typer.Argument(..., help="Recipe name"),
|
|
):
|
|
"""Show a recipe config (print YAML to stdout)."""
|
|
from soup_cli.recipes.catalog import get_recipe
|
|
|
|
recipe = get_recipe(name)
|
|
if recipe is None:
|
|
console.print(f"[red]Recipe not found: {name}[/]")
|
|
# v0.40.1 Part E / M3 — fuzzy-match suggestion (mirrors Typer's
|
|
# built-in "Did you mean" for unknown CLI flags).
|
|
suggestions = _suggest_recipes(name)
|
|
if suggestions:
|
|
console.print(
|
|
f"[dim]Did you mean: [bold]{', '.join(suggestions)}[/]?[/]"
|
|
)
|
|
console.print("[dim]Run 'soup recipes list' to see all recipes.[/]")
|
|
raise typer.Exit(1)
|
|
|
|
console.print(Panel(
|
|
Syntax(recipe.yaml_str, "yaml", theme="monokai"),
|
|
title=f"[bold green]{name}[/] -- {recipe.description}",
|
|
))
|
|
|
|
|
|
@app.command()
|
|
def use(
|
|
name: str = typer.Argument(..., help="Recipe name"),
|
|
output: str = typer.Option(
|
|
"soup.yaml",
|
|
"--output",
|
|
"-o",
|
|
help="Output path for config file",
|
|
),
|
|
yes: bool = typer.Option(
|
|
False,
|
|
"--yes",
|
|
"-y",
|
|
help="Skip confirmation prompts",
|
|
),
|
|
):
|
|
"""Copy a recipe to soup.yaml (or custom path)."""
|
|
from soup_cli.recipes.catalog import get_recipe
|
|
|
|
recipe = get_recipe(name)
|
|
if recipe is None:
|
|
console.print(f"[red]Recipe not found: {name}[/]")
|
|
raise typer.Exit(1)
|
|
|
|
from soup_cli.migrate.common import validate_output_path
|
|
|
|
try:
|
|
output_path = validate_output_path(Path(output))
|
|
except ValueError as exc:
|
|
console.print(f"[red]{exc}[/]")
|
|
raise typer.Exit(1)
|
|
|
|
if output_path.exists() and not yes:
|
|
confirm = typer.confirm(f"File '{output}' already exists. Overwrite?")
|
|
if not confirm:
|
|
console.print("[yellow]Aborted.[/]")
|
|
raise typer.Exit(0)
|
|
|
|
output_path.write_text(recipe.yaml_str, encoding="utf-8")
|
|
console.print(f"[green]\u2713[/] Recipe [bold]{name}[/] written to [bold]{output}[/]")
|
|
console.print(f"[dim]Next: soup train --config {output}[/]")
|
|
|
|
|
|
@app.command()
|
|
def search(
|
|
query: Optional[str] = typer.Argument(None, help="Search keyword"),
|
|
task: Optional[str] = typer.Option(None, "--task", help="Filter by task"),
|
|
size: Optional[str] = typer.Option(None, "--size", help="Filter by model size"),
|
|
):
|
|
"""Search recipes by keyword, task, or model size."""
|
|
from soup_cli.recipes.catalog import RECIPES, search_recipes
|
|
|
|
results = search_recipes(query=query, task=task, size=size)
|
|
|
|
if not results:
|
|
console.print("[yellow]No recipes found matching your query.[/]")
|
|
console.print(f"[dim]Available recipes: {len(RECIPES)}. Run 'soup recipes list'.[/]")
|
|
return
|
|
|
|
table = Table(title="Search Results")
|
|
table.add_column("Name", style="bold cyan")
|
|
table.add_column("Model", style="green")
|
|
table.add_column("Task", style="yellow")
|
|
table.add_column("Size", style="magenta")
|
|
table.add_column("Description")
|
|
|
|
# Find name for each result
|
|
for recipe in results:
|
|
name = next(
|
|
(n for n, r in RECIPES.items() if r is recipe),
|
|
"?",
|
|
)
|
|
table.add_row(name, recipe.model, recipe.task, recipe.size, recipe.description)
|
|
|
|
console.print(table)
|
|
|
|
|
|
def _suggest_recipes(query: str, n: int = 3) -> list[str]:
|
|
"""v0.40.1 Part E / M3 — return up to ``n`` close-matching recipe ids."""
|
|
from difflib import get_close_matches
|
|
|
|
from soup_cli.recipes.catalog import RECIPES
|
|
|
|
try:
|
|
names = list(RECIPES.keys()) if hasattr(RECIPES, "keys") else [
|
|
r.name for r in RECIPES
|
|
]
|
|
except Exception: # noqa: BLE001
|
|
return []
|
|
return get_close_matches(query, names, n=n, cutoff=0.6)
|