barunsaha's picture
Use uniform format for displaying the supported models names
68eff7b
"""
Command-line interface for SlideDeck AI.
"""
import argparse
import sys
import shutil
from typing import Any
from slidedeckai.core import SlideDeckAI
from slidedeckai.global_config import GlobalConfig
def group_models_by_provider(models: list[str]) -> dict[str, list[str]]:
"""
Group model names by their provider.
Args:
models (list[str]): List of model names.
Returns:
dict[str, list[str]]: Dictionary mapping provider codes to lists of model names.
"""
provider_models = {}
for model in sorted(models):
if match := GlobalConfig.PROVIDER_REGEX.match(model):
provider = match.group(1)
if provider not in provider_models:
provider_models[provider] = []
provider_models[provider].append(model.strip())
return provider_models
def format_models_as_bullets(models: list[str]) -> str:
"""
Format models as a bulleted list, grouped by provider.
Args:
models (list[str]): List of model names.
Returns:
str: Formatted string of models.
"""
provider_models = group_models_by_provider(models)
lines = []
for provider in sorted(provider_models.keys()):
lines.append(f'\n{provider}:')
for model in sorted(provider_models[provider]):
lines.append(f' • {model}')
return '\n'.join(lines)
class CustomHelpFormatter(argparse.HelpFormatter):
"""
Custom formatter for argparse that improves the display of choices.
"""
def _format_action_invocation(self, action: Any) -> str:
if not action.option_strings or action.nargs == 0:
return super()._format_action_invocation(action)
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
# If there are choices, and it's the model argument, handle it specially
if action.choices and '--model' in action.option_strings:
return ', '.join(action.option_strings) + ' MODEL'
return f"{', '.join(action.option_strings)} {args_string}"
def _split_lines(self, text: str, width: int) -> list[str]:
if text.startswith('Model choices:') or text.startswith('choose from'):
# Special handling for model choices and error messages
lines = []
header = 'Available models:'
separator = '------------------------' # Fixed-length separator
lines.append(header)
lines.append(separator)
# Extract models from text
if text.startswith('choose from'):
models = [
m.strip("' ") for m in text.replace('choose from', '').split(',')
]
else:
models = text.split('\n')[1:]
# Use the centralized formatting
lines.extend(format_models_as_bullets(models).split('\n'))
return lines
return super()._split_lines(text, width)
class CustomArgumentParser(argparse.ArgumentParser):
"""
Custom argument parser that formats error messages better.
"""
def error(self, message: str) -> None:
"""Custom error handler that formats model choices better"""
if 'invalid choice' in message and '--model' in message:
# Extract models from the error message
choices_str = message[message.find('(choose from'):]
models = [
m.strip("' ") for m in choices_str.replace(
'(choose from', ''
).rstrip(')').split(',')
]
error_lines = ['Error: Invalid model choice. Available models:']
error_lines.extend(format_models_as_bullets(models).split('\n'))
self.print_help()
print('\n' + '\n'.join(error_lines), file=sys.stderr)
sys.exit(2)
super().error(message)
def format_models_list() -> str:
"""Format the models list in a nice grouped format with descriptions."""
header = 'Supported SlideDeck AI models:\n'
models = list(GlobalConfig.VALID_MODELS.keys())
return header + format_models_as_bullets(models)
def format_model_help() -> str:
"""Format model choices as a grouped bulleted list for help text."""
return format_models_as_bullets(list(GlobalConfig.VALID_MODELS.keys()))
def main():
"""
The main function for the CLI.
"""
parser = CustomArgumentParser(
description='Generate slide decks with SlideDeck AI.',
formatter_class=CustomHelpFormatter
)
subparsers = parser.add_subparsers(dest='command')
# Top-level flag to list supported models
parser.add_argument(
'-l',
'--list-models',
action='store_true',
help='List supported model keys and exit.',
)
# 'generate' command
parser_generate = subparsers.add_parser(
'generate',
help='Generate a new slide deck.',
formatter_class=CustomHelpFormatter
)
parser_generate.add_argument(
'--model',
required=True,
choices=GlobalConfig.VALID_MODELS.keys(),
help=(
'Model name to use. Must be one of the supported models in the'
' `[provider-code]model_name` format.' + format_model_help()
),
metavar='MODEL'
)
parser_generate.add_argument(
'--topic',
required=True,
help='The topic of the slide deck.',
)
parser_generate.add_argument(
'--api-key',
help=(
'The API key for the LLM provider. Alternatively, set the appropriate API key'
' in the environment variable.'
),
)
parser_generate.add_argument(
'--template-id',
type=int,
default=0,
help='The index of the PowerPoint template to use.',
)
parser_generate.add_argument(
'--output-path',
help='The path to save the generated .pptx file.',
)
# Note: the 'launch' command has been intentionally disabled.
# If no arguments are provided, show help and exit
if len(sys.argv) == 1:
parser.print_help()
return
args = parser.parse_args()
# If --list-models flag was provided, print models and exit
if getattr(args, 'list_models', False):
print(format_models_list())
return
if args.command == 'generate':
slide_generator = SlideDeckAI(
model=args.model,
topic=args.topic,
api_key=args.api_key,
template_idx=args.template_id,
)
pptx_path = slide_generator.generate()
if args.output_path:
shutil.move(str(pptx_path), args.output_path)
print(f'\n🤖 Slide deck saved to: {args.output_path}')
else:
print(f'\n🤖 Slide deck saved to: {pptx_path}')
if __name__ == '__main__':
main()