Skip to content

Add reconfigure feature when session_key is not usable #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 0 additions & 94 deletions aishell/cli.py

This file was deleted.

2 changes: 2 additions & 0 deletions aishell/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .aishell import aishell_command as _ # noqa
from .cli_app import cli_app as cli_app
72 changes: 72 additions & 0 deletions aishell/cli/aishell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import os
import time
from typing import Optional

import rich
import typer
from rich.console import Console

from aishell.models import AiShellConfigModel
from aishell.models.language_model import LanguageModel
from aishell.query_clients import GPT3Client, OfficialChatGPTClient, ReverseEngineeredChatGPTClient
from aishell.utils import AiShellConfigManager

from .cli_app import cli_app
from .config_aishell import config_aishell


@cli_app.command()
def aishell_command(question: str, language_model: Optional[LanguageModel] = None):
config_manager = _get_config_manager()
config_manager.config_model.language_model = language_model or config_manager.config_model.language_model

query_client = _get_query_client(
language_model=config_manager.config_model.language_model,
config_model=config_manager.config_model,
)

console = Console()

try:
with console.status(f'''
[green] AiShell is thinking of `{question}` ...[/green]

[dim]AiShell is not responsible for any damage caused by the command executed by the user.[/dim]'''[1:]):
start_time = time.time()
response = query_client.query(question)
end_time = time.time()

execution_time = end_time - start_time
console.print(f'AiShell: {response}\n\n[dim]Took {execution_time:.2f} seconds to think the command.[/dim]')

will_execute = typer.confirm('Execute this command?')
if will_execute:
os.system(response)
except KeyError:
rich.print('It looks like the `session_token` is expired. Please reconfigure AiShell.')
typer.confirm('Reconfigure AiShell?', abort=True)
config_aishell()
aishell_command(question=question, language_model=language_model)
typer.Exit()


def _get_config_manager():
is_config_file_available = AiShellConfigManager.is_config_file_available(AiShellConfigManager.DEFAULT_CONFIG_PATH)
if is_config_file_available:
return AiShellConfigManager(load_config=True)
else:
return config_aishell()


def _get_query_client(language_model: LanguageModel, config_model: AiShellConfigModel):
if language_model == LanguageModel.REVERSE_ENGINEERED_CHATGPT:
return ReverseEngineeredChatGPTClient(config=config_model.chatgpt_config)

if not config_model.openai_api_key:
raise RuntimeError('OpenAI API key is not provided. Please provide it in the config file.')

if language_model == LanguageModel.GPT3:
return GPT3Client(openai_api_key=config_model.openai_api_key)
if language_model == LanguageModel.OFFICIAL_CHATGPT:
return OfficialChatGPTClient(openai_api_key=config_model.openai_api_key)
raise NotImplementedError(f'Language model {language_model} is not implemented yet.')
3 changes: 3 additions & 0 deletions aishell/cli/cli_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from typer import Typer

cli_app = Typer()
67 changes: 67 additions & 0 deletions aishell/cli/config_aishell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import sys

import rich
import typer
from yt_dlp.cookies import SUPPORTED_BROWSERS

from aishell.adapters.openai_cookie_adapter import OpenAICookieAdapter
from aishell.models import RevChatGPTChatbotConfigModel
from aishell.models.aishell_config_model import AiShellConfigModel
from aishell.utils import AiShellConfigManager


def config_aishell():
rich.print('''
Hi! 🙌 I am [bold blue]AiShell[/bold blue], [yellow]your powerful terminal assistant[/yellow] 🔥
I am here to assist you with configuring AiShell. 💪

Please make sure that you have logged into chat.openai.com on your browser before we continue. 🗝️

'''[1:])
typer.confirm('Are you ready to proceed? 🚀', abort=True)

rich.print(f'''
Which browser did you use to log in to chat.openai.com?

We support the following browsers: [{SUPPORTED_BROWSERS}]'''[1:])
browser_name = typer.prompt('Please enter your choice here: ')
if browser_name not in SUPPORTED_BROWSERS:
rich.print(f'Browser {browser_name} is not supported. Supported browsers are: {SUPPORTED_BROWSERS}')
sys.exit(1)

adapter = OpenAICookieAdapter(browser_name)
session_token = adapter.get_openai_session_token()
if not session_token:
rich.print('Failed to get session token. 😓 Can you check if you are logged in to https://chat.openai.com?')
sys.exit(1)

config_manager = save_config(session_token)

rich.print(f'''
[green bold]Excellent![/green bold] You are now ready to use [bold blue]AiShell[/bold blue] 🚀

Enjoy your AI powered terminal assistant! 🎉

[dim]To check your settings file, it's at: {config_manager.config_path}[/dim]

'''[1:])
return config_manager


def save_config(session_token: str):
is_config_file_available = AiShellConfigManager.is_config_file_available(AiShellConfigManager.DEFAULT_CONFIG_PATH)
if is_config_file_available:
config_manager = AiShellConfigManager(load_config=True)
is_chatgpt_config_available = config_manager.config_model.chatgpt_config is not None
if is_chatgpt_config_available:
assert config_manager.config_model.chatgpt_config # for type hinting
config_manager.config_model.chatgpt_config.session_token = session_token
else:
config_manager.config_model.chatgpt_config = RevChatGPTChatbotConfigModel(session_token=session_token)
else:
chatgpt_config = RevChatGPTChatbotConfigModel(session_token=session_token)
aishell_config = AiShellConfigModel(chatgpt_config=chatgpt_config)
config_manager = AiShellConfigManager(config_model=aishell_config)

config_manager.save_config()
return config_manager
Empty file added aishell/cli/test/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions aishell/cli/test/test_config_aishell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from unittest.mock import MagicMock, patch

from aishell.cli.config_aishell import save_config
from aishell.models import AiShellConfigModel, LanguageModel, RevChatGPTChatbotConfigModel
from aishell.utils import AiShellConfigManager


class TestSaveConfig:

@patch('aishell.cli.config_aishell.AiShellConfigManager', spec=AiShellConfigManager)
def test_success_when_config_file_not_available(
self,
mocked_config_manager_class: MagicMock,
):
'''config file 이 없는 경우 테스트 성공해야 한다'''
# given
mocked_config_manager_class.is_config_file_available.return_value = False

# when
save_config('valid_session_token')

# then
mocked_config_manager_class.return_value.save_config.assert_called_once()

@patch('aishell.cli.config_aishell.AiShellConfigManager', spec=AiShellConfigManager)
def test_success_when_config_file_available_with_chatgpt_config(
self,
mocked_config_manager_class: MagicMock,
):
'''config file 이 있고, chatgpt_config 가 있는 경우 테스트 성공해야 한다.'''
# given
mocked_config_manager_class.is_config_file_available.return_value = True
mocked_config_manager_instance = mocked_config_manager_class.return_value
mocked_config_manager_instance.config_model =\
AiShellConfigModel(chatgpt_config=RevChatGPTChatbotConfigModel(session_token='invalid_session_token'))

# when
save_config('valid_session_token')

# then
mocked_config_manager_class.return_value.save_config.assert_called_once()

@patch('aishell.cli.config_aishell.AiShellConfigManager', spec=AiShellConfigManager)
def test_success_when_config_file_available_without_chatgpt_config(
self,
mocked_config_manager_class: MagicMock,
):
'''config file 이 있고, chatgpt_config 가 없는 경우 테스트 성공해야 한다.'''
# given
mocked_config_manager_class.is_config_file_available.return_value = True
mocked_config_manager_instance = mocked_config_manager_class.return_value
mocked_config_manager_instance.config_model =\
AiShellConfigModel(language_model=LanguageModel.GPT3, openai_api_key='valid_openai_api_key')

# when
save_config('valid_session_token')

# then
mocked_config_manager_class.return_value.save_config.assert_called_once()

# when config file available - with chatgpt_config
# when config file available - without chatgpt_config
# when config file not available
3 changes: 0 additions & 3 deletions aishell/models/revchatgpt_chatbot_config_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,3 @@ def check_at_least_one_account_info(cls, values: dict[str, Optional[str]]):
raise ValueError('No information for authentication provided.')

return values

class Config:
frozen = True
15 changes: 11 additions & 4 deletions aishell/query_clients/gpt3_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

class GPT3Client(QueryClient):

def __init__(
self,
openai_api_key: str,
):
openai.api_key = openai_api_key

def query(self, prompt: str) -> str:
prompt = self._construct_prompt(prompt)
completion: Final[OpenAIResponseModel] = cast(
Expand All @@ -29,8 +35,9 @@ def query(self, prompt: str) -> str:
return make_executable_command(response_text)

def _construct_prompt(self, text: str) -> str:
return f'''User: You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.
return f'''
User: You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.

You: '''
You: '''[1:]
10 changes: 5 additions & 5 deletions aishell/query_clients/official_chatgpt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ class OfficialChatGPTClient(QueryClient):

def __init__(
self,
openai_api_key: Optional[str] = None,
openai_api_key: str,
):
super().__init__()
OPENAI_API_KEY: Optional[str] = os.environ.get('OPENAI_API_KEY', openai_api_key)
if OPENAI_API_KEY is None:
raise UnauthorizedAccessError('OPENAI_API_KEY should not be none')
Expand All @@ -31,6 +30,7 @@ def query(self, prompt: str) -> str:
return executable_command

def _construct_prompt(self, text: str) -> str:
return f'''You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.'''
return f'''
You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.'''[1:]
7 changes: 4 additions & 3 deletions aishell/query_clients/reverse_engineered_chatgpt_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def query(self, prompt: str) -> str:
return response_text

def _construct_prompt(self, text: str) -> str:
return f'''You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.'''
return f'''
You are now a translater from human language to {os.uname()[0]} shell command.
No explanation required, respond with only the raw shell command.
What should I type to shell for: {text}, in one line.'''[1:]