diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..f63c61b --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,77 @@ +name: build +on: [push] +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' + - name: 'py39 (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'ubuntu-latest' + - name: 'py39 (windows)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'windows-latest' + - name: 'py39 (macos)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'macos-latest' + - name: 'py310 (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'ubuntu-latest' + - name: 'py310 (windows)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'windows-latest' + - name: 'py310 (macos)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'macos-latest' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.python_arch }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: test + env: + TOXPYTHON: '${{ matrix.toxpython }}' + run: > + tox -e ${{ matrix.tox_env }} -v diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f911cbd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,13 @@ +graft askai +graft images +graft ci +graft tests + + +include pytest.ini +include tox.ini +include .github/workflows/github-actions.yml + +include README.rst + +global-exclude *.py[cod] __pycache__/* *.so *.dylib CNAME \ No newline at end of file diff --git a/askai/constants.py b/askai/constants.py index 5bfc394..4c710be 100644 --- a/askai/constants.py +++ b/askai/constants.py @@ -1,6 +1,5 @@ from pathlib import Path - ASKAI_PATH = Path.home() / ".askai" API_KEY_PATH = ASKAI_PATH / "key" CONFIG_PATH = ASKAI_PATH / "config.yml" diff --git a/askai/entrypoint_askai.py b/askai/entrypoint_askai.py index 5be3fe7..90bbf3f 100644 --- a/askai/entrypoint_askai.py +++ b/askai/entrypoint_askai.py @@ -1,13 +1,16 @@ import click import openai -from .constants import OPENAI_NUM_ANSWERS_MIN, OPENAI_TEMPERATURE_MIN, OPENAI_TEMPERATURE_MAX, OPENAI_MAX_TOKENS_MIN, \ - OPENAI_TOP_P_MIN, OPENAI_TOP_P_MAX, OPENAI_FREQUENCY_PENALTY_MIN, OPENAI_FREQUENCY_PENALTY_MAX, \ - OPENAI_PRESENCE_PENALTY_MIN, OPENAI_PRESENCE_PENALTY_MAX -from .utils import KeyHelper, ConfigHelper, PrintHelper, AvailableModels +from .constants import (OPENAI_FREQUENCY_PENALTY_MAX, + OPENAI_FREQUENCY_PENALTY_MIN, OPENAI_MAX_TOKENS_MIN, + OPENAI_NUM_ANSWERS_MIN, OPENAI_PRESENCE_PENALTY_MAX, + OPENAI_PRESENCE_PENALTY_MIN, OPENAI_TEMPERATURE_MAX, + OPENAI_TEMPERATURE_MIN, OPENAI_TOP_P_MAX, + OPENAI_TOP_P_MIN) from .entrypoint_config import config from .entrypoint_init import init from .entrypoint_key import key +from .utils import AvailableModels, ConfigHelper, KeyHelper, PrintHelper class DefaultCommandGroup(click.Group): @@ -22,12 +25,13 @@ class DefaultCommandGroup(click.Group): default_command: bool def command(self, *args, **kwargs): - default_command = kwargs.pop('default_command', False) + default_command = kwargs.pop("default_command", False) if default_command and not args: - kwargs['name'] = kwargs.get('name', ' ') + kwargs["name"] = kwargs.get("name", " ") decorator = super(DefaultCommandGroup, self).command(*args, **kwargs) if default_command: + def new_decorator(f): cmd = decorator(f) self.default_command = cmd.name @@ -40,13 +44,11 @@ def new_decorator(f): def resolve_command(self, ctx, args): try: # test if the command parses - return super( - DefaultCommandGroup, self).resolve_command(ctx, args) + return super(DefaultCommandGroup, self).resolve_command(ctx, args) except click.UsageError: # command did not parse, assume it is the default command args.insert(0, self.default_command) - return super( - DefaultCommandGroup, self).resolve_command(ctx, args) + return super(DefaultCommandGroup, self).resolve_command(ctx, args) def format_help(self, ctx, formatter) -> None: PrintHelper.logo() @@ -65,21 +67,60 @@ def askai() -> None: @askai.command(default_command=True) @click.argument("prompt") -@click.option("-n", "--num-answers", type=click.IntRange(min=OPENAI_NUM_ANSWERS_MIN), help="Number of alternative answers") -@click.option("-m", "--model", type=click.Choice(choices=AvailableModels.members_as_list()), help="OpenAI model to use. E.g. `text-ada-001`") -@click.option("-t", "--temperature", type=click.FloatRange(min=OPENAI_TEMPERATURE_MIN, max=OPENAI_TEMPERATURE_MAX), help="Temperature") -@click.option("--max-tokens", type=click.IntRange(min=OPENAI_MAX_TOKENS_MIN), help="Max tokens") -@click.option("--top-p", type=click.FloatRange(min=OPENAI_TOP_P_MIN, max=OPENAI_TOP_P_MAX), help="Top p") -@click.option("--frequency-penalty", type=click.FloatRange(min=OPENAI_FREQUENCY_PENALTY_MIN, max=OPENAI_FREQUENCY_PENALTY_MAX), help="Frequency penalty") -@click.option("--presence-penalty", type=click.FloatRange(min=OPENAI_PRESENCE_PENALTY_MIN, max=OPENAI_PRESENCE_PENALTY_MAX), help="Presence penalty") -def ask(prompt: str, - num_answers: int, - model: str, - temperature: float, - max_tokens: int, - top_p: float, - frequency_penalty: float, - presence_penalty: float) -> None: +@click.option( + "-n", + "--num-answers", + type=click.IntRange(min=OPENAI_NUM_ANSWERS_MIN), + help="Number of alternative answers", +) +@click.option( + "-m", + "--model", + type=click.Choice(choices=AvailableModels.members_as_list()), + help="OpenAI model to use. E.g. `text-ada-001`", +) +@click.option( + "-t", + "--temperature", + type=click.FloatRange( + min=OPENAI_TEMPERATURE_MIN, max=OPENAI_TEMPERATURE_MAX + ), + help="Temperature", +) +@click.option( + "--max-tokens", + type=click.IntRange(min=OPENAI_MAX_TOKENS_MIN), + help="Max tokens", +) +@click.option( + "--top-p", + type=click.FloatRange(min=OPENAI_TOP_P_MIN, max=OPENAI_TOP_P_MAX), + help="Top p", +) +@click.option( + "--frequency-penalty", + type=click.FloatRange( + min=OPENAI_FREQUENCY_PENALTY_MIN, max=OPENAI_FREQUENCY_PENALTY_MAX + ), + help="Frequency penalty", +) +@click.option( + "--presence-penalty", + type=click.FloatRange( + min=OPENAI_PRESENCE_PENALTY_MIN, max=OPENAI_PRESENCE_PENALTY_MAX + ), + help="Presence penalty", +) +def ask( + prompt: str, + num_answers: int, + model: str, + temperature: float, + max_tokens: int, + top_p: float, + frequency_penalty: float, + presence_penalty: float, +) -> None: openai.api_key = KeyHelper.from_file() _config = ConfigHelper.from_file() @@ -90,8 +131,12 @@ def ask(prompt: str, max_tokens=max_tokens if max_tokens else _config.max_tokens, n=num_answers if num_answers else _config.num_answers, top_p=top_p if top_p else _config.top_p, - frequency_penalty=frequency_penalty if frequency_penalty else _config.frequency_penalty, - presence_penalty=presence_penalty if presence_penalty else _config.presence_penalty + frequency_penalty=frequency_penalty + if frequency_penalty + else _config.frequency_penalty, + presence_penalty=presence_penalty + if presence_penalty + else _config.presence_penalty, ) PrintHelper.print_response(response=response) diff --git a/askai/entrypoint_config.py b/askai/entrypoint_config.py index 049e4e7..a09175c 100644 --- a/askai/entrypoint_config.py +++ b/askai/entrypoint_config.py @@ -11,7 +11,9 @@ def config() -> None: @config.command(help="Reset the config to default") def reset() -> None: - user_verification = input("Do you want to reset your config to the default values? [y/Y]? ") + user_verification = input( + "Do you want to reset your config to the default values? [y/Y]? " + ) if user_verification.lower() in ["y", "yes"]: ConfigHelper().reset() @@ -38,7 +40,10 @@ def update_all() -> None: PrintHelper.model() config_helper.input_model() - PrintHelper.step(step=2, description="SET NUMBER OF ALTERNATIVE ANSWERS GENERATED PER QUESTION") + PrintHelper.step( + step=2, + description="SET NUMBER OF ALTERNATIVE ANSWERS GENERATED PER QUESTION", + ) PrintHelper.num_answers() config_helper.input_num_answer() @@ -73,7 +78,9 @@ def model() -> None: config_helper.update() -@update.command(help="Update number of altenative answers generated per question") +@update.command( + help="Update number of altenative answers generated per question" +) def num_answers() -> None: PrintHelper.num_answers() config_helper = ConfigHelper.from_file() diff --git a/askai/entrypoint_init.py b/askai/entrypoint_init.py index d23d3bc..ff9f137 100644 --- a/askai/entrypoint_init.py +++ b/askai/entrypoint_init.py @@ -1,6 +1,6 @@ import click -from .utils import KeyHelper, ConfigHelper, PrintHelper +from .utils import ConfigHelper, KeyHelper, PrintHelper @click.command() diff --git a/askai/entrypoint_key.py b/askai/entrypoint_key.py index 3658d34..15dd81c 100644 --- a/askai/entrypoint_key.py +++ b/askai/entrypoint_key.py @@ -1,7 +1,7 @@ import click -from .utils import KeyHelper, PrintHelper from .constants import API_KEY_PATH +from .utils import KeyHelper, PrintHelper @click.group() @@ -27,7 +27,9 @@ def remove() -> None: if not API_KEY_PATH.is_file(): PrintHelper.no_key() else: - user_verification = input("Do you want to remove your API key? [y/Y]? ") + user_verification = input( + "Do you want to remove your API key? [y/Y]? " + ) if user_verification.lower() in ["y", "yes"]: KeyHelper().remove() else: diff --git a/askai/utils.py b/askai/utils.py index ec3d6bb..5e0b60a 100644 --- a/askai/utils.py +++ b/askai/utils.py @@ -1,27 +1,25 @@ +from dataclasses import asdict, dataclass +from enum import Enum, auto +from getpass import getpass from pathlib import Path +from typing import Callable -import yaml import click import openai -from getpass import getpass -from enum import Enum, auto -from typing import Callable -from dataclasses import dataclass, asdict +import yaml from openai.error import AuthenticationError from openai.openai_object import OpenAIObject -from .constants import ( - CONFIG_PATH, - API_KEY_PATH, - DEFAULT_MODEL, - DEFAULT_NUM_ANSWERS, - DEFAULT_MAX_TOKENS, - DEFAULT_TEMPERATURE, - DEFAULT_TOP_P, - DEFAULT_FREQUENCY_PENALTY, - DEFAULT_PRESENCE_PENALTY, MAX_INPUT_TRIES, OPENAI_NUM_ANSWERS_MIN, OPENAI_MAX_TOKENS_MIN, OPENAI_TEMPERATURE_MIN, - OPENAI_TEMPERATURE_MAX, OPENAI_TOP_P_MIN, OPENAI_TOP_P_MAX, OPENAI_FREQUENCY_PENALTY_MIN, - OPENAI_FREQUENCY_PENALTY_MAX, OPENAI_PRESENCE_PENALTY_MIN, OPENAI_PRESENCE_PENALTY_MAX -) + +from .constants import (API_KEY_PATH, CONFIG_PATH, DEFAULT_FREQUENCY_PENALTY, + DEFAULT_MAX_TOKENS, DEFAULT_MODEL, DEFAULT_NUM_ANSWERS, + DEFAULT_PRESENCE_PENALTY, DEFAULT_TEMPERATURE, + DEFAULT_TOP_P, MAX_INPUT_TRIES, + OPENAI_FREQUENCY_PENALTY_MAX, + OPENAI_FREQUENCY_PENALTY_MIN, OPENAI_MAX_TOKENS_MIN, + OPENAI_NUM_ANSWERS_MIN, OPENAI_PRESENCE_PENALTY_MAX, + OPENAI_PRESENCE_PENALTY_MIN, OPENAI_TEMPERATURE_MAX, + OPENAI_TEMPERATURE_MIN, OPENAI_TOP_P_MAX, + OPENAI_TOP_P_MIN) class AvailableModels(Enum): @@ -49,14 +47,19 @@ class ConfigHelper: presence_penalty: float = DEFAULT_PRESENCE_PENALTY @classmethod - def from_file(cls, config_path: Path = CONFIG_PATH) -> 'ConfigHelper': + def from_file(cls, config_path: Path = CONFIG_PATH) -> "ConfigHelper": if config_path.is_file(): with open(config_path, "r") as f: config = yaml.safe_load(f) return cls(**config) else: - click.echo(click.style("No config file found, can't initialize config. " - "Run 'askai config reset' to create a default config.", fg="red")) + click.echo( + click.style( + "No config file found, can't initialize config. " + "Run 'askai config reset' to create a default config.", + fg="red", + ) + ) exit() def input_model(self, max_input_tries: int = MAX_INPUT_TRIES) -> None: @@ -65,7 +68,9 @@ def input_model(self, max_input_tries: int = MAX_INPUT_TRIES) -> None: while not _is_int(model) or int(model) not in range(1, 5): if num_of_tries >= max_input_tries: - click.echo(click.style("Too many invalid tries. Aborted!", fg="red")) + click.echo( + click.style("Too many invalid tries. Aborted!", fg="red") + ) exit(1) click.echo(click.style("Choose value between 1 and 4.", fg="red")) @@ -76,68 +81,80 @@ def input_model(self, max_input_tries: int = MAX_INPUT_TRIES) -> None: click.echo(click.style(f"Model chosen: {self.model}", fg="green")) click.echo() - def input_num_answer(self, - default_value: int = DEFAULT_NUM_ANSWERS, - min_value: int = OPENAI_NUM_ANSWERS_MIN, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_num_answer( + self, + default_value: int = DEFAULT_NUM_ANSWERS, + min_value: int = OPENAI_NUM_ANSWERS_MIN, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.num_answers = self._input_integer( default_value=default_value, predicate=lambda x: x >= min_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_max_token(self, - default_value: int = DEFAULT_MAX_TOKENS, - min_value: int = OPENAI_MAX_TOKENS_MIN, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_max_token( + self, + default_value: int = DEFAULT_MAX_TOKENS, + min_value: int = OPENAI_MAX_TOKENS_MIN, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.max_tokens = self._input_integer( default_value=default_value, predicate=lambda x: x >= min_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_temperature(self, - default_value: float = DEFAULT_TEMPERATURE, - min_value: float = OPENAI_TEMPERATURE_MIN, - max_value: float = OPENAI_TEMPERATURE_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_temperature( + self, + default_value: float = DEFAULT_TEMPERATURE, + min_value: float = OPENAI_TEMPERATURE_MIN, + max_value: float = OPENAI_TEMPERATURE_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.temperature = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_top_p(self, - default_value: float = DEFAULT_TOP_P, - min_value: float = OPENAI_TOP_P_MIN, - max_value: float = OPENAI_TOP_P_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_top_p( + self, + default_value: float = DEFAULT_TOP_P, + min_value: float = OPENAI_TOP_P_MIN, + max_value: float = OPENAI_TOP_P_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.top_p = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_frequency_penalty(self, - default_value: float = DEFAULT_FREQUENCY_PENALTY, - min_value: float = OPENAI_FREQUENCY_PENALTY_MIN, - max_value: float = OPENAI_FREQUENCY_PENALTY_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_frequency_penalty( + self, + default_value: float = DEFAULT_FREQUENCY_PENALTY, + min_value: float = OPENAI_FREQUENCY_PENALTY_MIN, + max_value: float = OPENAI_FREQUENCY_PENALTY_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.frequency_penalty = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_presence_penalty(self, - default_value: float = DEFAULT_PRESENCE_PENALTY, - min_value: float = OPENAI_PRESENCE_PENALTY_MIN, - max_value: float = OPENAI_PRESENCE_PENALTY_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_presence_penalty( + self, + default_value: float = DEFAULT_PRESENCE_PENALTY, + min_value: float = OPENAI_PRESENCE_PENALTY_MIN, + max_value: float = OPENAI_PRESENCE_PENALTY_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.presence_penalty = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) def as_dict(self) -> dict: @@ -156,17 +173,25 @@ def reset(config_path: Path = CONFIG_PATH) -> None: with open(config_path, "w") as f: yaml.dump(config, f) - click.echo("\nDefault config has been created with the following values:") + click.echo( + "\nDefault config has been created with the following values:" + ) for key, value in config.items(): click.echo(f" * {key}={value}") - click.echo(click.style("Successfully set config to default values\n", fg="green")) + click.echo( + click.style( + "Successfully set config to default values\n", fg="green" + ) + ) click.echo("To change the config, please see: 'askai config --help'\n") @staticmethod def show(config_path: Path = CONFIG_PATH) -> None: if not config_path.is_file(): - click.echo("No config exists. Please reset the config ('askai config reset') " - "or see 'askai config --help'.\n") + click.echo( + "No config exists. Please reset the config ('askai config reset') " + "or see 'askai config --help'.\n" + ) else: with open(config_path, "r") as f: try: @@ -174,17 +199,25 @@ def show(config_path: Path = CONFIG_PATH) -> None: for key, value in config.items(): click.echo(f"{key}: {value}") except yaml.YAMLError: - click.echo("Something is wrong with the config. Please reset the config: 'askai config reset'") + click.echo( + "Something is wrong with the config. Please reset the config: 'askai config reset'" + ) @staticmethod - def _input_integer(default_value: int, - predicate: Callable[[int], bool] = lambda x: True, - max_input_tries: int = MAX_INPUT_TRIES) -> int: + def _input_integer( + default_value: int, + predicate: Callable[[int], bool] = lambda x: True, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> int: for _ in range(max_input_tries): - input_value = input(f"Choose (press enter for default = {default_value}): ") + input_value = input( + f"Choose (press enter for default = {default_value}): " + ) if input_value == "": - click.echo(click.style(f"Value chosen: {default_value}", fg="green")) + click.echo( + click.style(f"Value chosen: {default_value}", fg="green") + ) click.echo() return default_value @@ -192,7 +225,11 @@ def _input_integer(default_value: int, click.echo(click.style("Input is not an integer.\n", fg="red")) continue elif not predicate(int(input_value)): - click.echo(click.style("Input is not within allowed range.\n", fg="red")) + click.echo( + click.style( + "Input is not within allowed range.\n", fg="red" + ) + ) continue click.echo(click.style(f"Value chosen: {input_value}", fg="green")) @@ -203,14 +240,20 @@ def _input_integer(default_value: int, exit(1) @staticmethod - def _input_float(default_value: float, - predicate: Callable[[float], bool] = lambda x: True, - max_input_tries: int = MAX_INPUT_TRIES) -> float: + def _input_float( + default_value: float, + predicate: Callable[[float], bool] = lambda x: True, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> float: for _ in range(max_input_tries): - input_value = input(f"Choose (press enter for default = {default_value}): ") + input_value = input( + f"Choose (press enter for default = {default_value}): " + ) if input_value == "": - click.echo(click.style(f"Value chosen: {default_value}", fg="green")) + click.echo( + click.style(f"Value chosen: {default_value}", fg="green") + ) click.echo() return default_value @@ -218,7 +261,11 @@ def _input_float(default_value: float, click.echo(click.style("Input is not a float.\n", fg="red")) continue elif not predicate(float(input_value)): - click.echo(click.style("Input is not within allowed range.\n", fg="red")) + click.echo( + click.style( + "Input is not within allowed range.\n", fg="red" + ) + ) continue click.echo(click.style(f"Value chosen: {input_value}", fg="green")) @@ -239,7 +286,9 @@ def input(self) -> None: while not self._is_valid_api_key(key): if num_tries >= MAX_INPUT_TRIES: - click.echo(click.style("Too many invalid tries. Aborted!", fg="red")) + click.echo( + click.style("Too many invalid tries. Aborted!", fg="red") + ) exit(1) click.echo(click.style("The API key is not valid.", fg="red")) key = getpass("Enter API Key: ") @@ -250,7 +299,11 @@ def input(self) -> None: def save(self) -> None: API_KEY_PATH.parent.mkdir(parents=True, exist_ok=True) API_KEY_PATH.write_text(self.api_key) - click.echo(click.style("Your API key has been successfully added!", fg="green")) + click.echo( + click.style( + "Your API key has been successfully added!", fg="green" + ) + ) @staticmethod def remove() -> None: @@ -264,8 +317,13 @@ def from_file(cls) -> str: api_key = f.read().strip() return api_key else: - click.echo(click.style("No API-key found, can't answer question. " - "Please add a key ('askai key add') or initialize ('askai init').", fg="red")) + click.echo( + click.style( + "No API-key found, can't answer question. " + "Please add a key ('askai key add') or initialize ('askai init').", + fg="red", + ) + ) exit() @staticmethod @@ -280,109 +338,124 @@ def _is_valid_api_key(key: str) -> bool: class PrintHelper: - @staticmethod def logo() -> None: - click.echo("\n" - "██████████ ██████████ ███ ███ ██████████ ███\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ███ ███ ███▐███ ███ ███ ▄█\n" - "███ ███ ███ █████▀ ███ ███ ███\n" - "██████████ ██████████ █████▄ ██████████ ███▌\n" - "███ ███ ███ ███▐███ ███ ███ ███▌\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ██████████ ███ ███ ███ ███ █▀\n" - "\n" - " ~~~~~~~ Your simple terminal helper ~~~~~~~\n") + click.echo( + "\n" + "██████████ ██████████ ███ ███ ██████████ ███\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ███ ███ ███▐███ ███ ███ ▄█\n" + "███ ███ ███ █████▀ ███ ███ ███\n" + "██████████ ██████████ █████▄ ██████████ ███▌\n" + "███ ███ ███ ███▐███ ███ ███ ███▌\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ██████████ ███ ███ ███ ███ █▀\n" + "\n" + " ~~~~~~~ Your simple terminal helper ~~~~~~~\n" + ) @staticmethod def help_what_is_askai() -> None: - click.echo("What is askai?\n" - " askai is a simple CLI integration with the worlds most powerful \n" - " and capable AI-model, OpenAI GPT3. This gives you the ability to \n" - " interact with these models straight from your terminal.\n" - "\n" - "What can it be used for?\n" - " askai enables you to ask any free-text question you want.\n" - "\n" - " For example:\n" - "\n" - " > askai \"What is the curl command to download html from a url?\"\n" - " > askai \"How do you remove '\\n' from the beginning of a string using Python?\"\n" - " > askai \"List the top 3 most common Python packages used to parse json-files\"" - "\n") + click.echo( + "What is askai?\n" + " askai is a simple CLI integration with the worlds most powerful \n" + " and capable AI-model, OpenAI GPT3. This gives you the ability to \n" + " interact with these models straight from your terminal.\n" + "\n" + "What can it be used for?\n" + " askai enables you to ask any free-text question you want.\n" + "\n" + " For example:\n" + "\n" + ' > askai "What is the curl command to download html from a url?"\n' + " > askai \"How do you remove '\\n' from the beginning of a string using Python?\"\n" + ' > askai "List the top 3 most common Python packages used to parse json-files"' + "\n" + ) @staticmethod def help_does_it_cost() -> None: - click.echo("Does it cost anything?\n" - " Yes (after the free OpenAI quota is used).\n" - "\n" - " askai is using your OpenAI API-key to generate the answers to your questions.\n" - " When creating a new account at OpenAI, you will get $18 of requests for free.\n" - " After you've consumed the free quota, you need to add payment info to your \n" - " OpenAI account to continue to use askai.\n") - + click.echo( + "Does it cost anything?\n" + " Yes (after the free OpenAI quota is used).\n" + "\n" + " askai is using your OpenAI API-key to generate the answers to your questions.\n" + " When creating a new account at OpenAI, you will get $18 of requests for free.\n" + " After you've consumed the free quota, you need to add payment info to your \n" + " OpenAI account to continue to use askai.\n" + ) + @staticmethod def help_requirements() -> None: - click.echo("Requirements:\n" - " * Create an OpenAI account and generate an API-key\n" - " * Run 'askai init' to add you API key and setup the default config.\n") + click.echo( + "Requirements:\n" + " * Create an OpenAI account and generate an API-key\n" + " * Run 'askai init' to add you API key and setup the default config.\n" + ) @staticmethod def help_main_command_options() -> None: - click.echo("Override config:\n" - " askai \"\"