Skip to content

Commit cbc533d

Browse files
authored
Merge pull request #31 from code-yeongyu/feature/configs
2 parents 5dd2fa8 + eb344a9 commit cbc533d

9 files changed

+157
-61
lines changed

README.md

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,42 @@ A simple Python code that connects to OpenAI's ChatGPT and executes the returned
1616
- Automatically executes the command from the response of ChatGPT
1717
- Good for complex tasks like handling Git and extracting tar files
1818
- No need to search StackOverflow for commands, AiShell has got you covered
19+
- **No need to set up annoying retrieving of tokens or API keys with ChatGPT, as AiShell does it for you. INSTALL IT. EXECUTE IT. DONE.**
20+
21+
## Prerequisites 📚
22+
23+
- Python 3.9+
24+
- ChatGPT Account (or OpenAI Account)
1925

2026
## Installation 🔧
2127

2228
```sh
2329
pip install aishell
2430
```
2531

26-
## Usage 📝
32+
## Getting Started 🚀
33+
34+
Let's just start by printing "Hello World" using AiShell.
2735

2836
```sh
29-
aishell --help
37+
aishell 'print Hello World'
3038
```
3139

32-
## Prerequisites 📚
33-
34-
- Python 3
35-
- OpenAI API Key or ChatGPT Account
36-
37-
## Getting Started 🚀
38-
39-
### For those who want to use reverse-engineered `ChatGPT`
40-
41-
- Permanent Login Method
42-
1. Login on <https://chat.openai.com/>
43-
1. Get your 'accessToken` from <https://chat.openai.com/api/auth/session>
44-
1. Set the API key as an environment variable `CHATGPT_ACCESS_TOKEN`
45-
- Temporary Login Method
46-
1. Just run `aishell <query>`
47-
1. Browser opens up. Login there.
48-
1. Tell AiShell which browser you use.
49-
1. Enjoy AiShell
40+
## Advanced Settings 🛠
5041

5142
### For those who want to use `Official ChatGPT(GPT3.5-turbo)` or `GPT-3`
5243

5344
1. Create account on OpenAI
5445
1. Go to <https://platform.openai.com/account/api-keys>, Copy API key
55-
1. Set the API key as an environment variable `OPENAI_API_KEY`
56-
1. Enjoy AiShell
46+
1. Modify or create `~/.aishell/config.json` file like following
47+
48+
```sh
49+
{
50+
...
51+
"language_model": <language model of your preference>, //"official_chatgpt" or "gpt3"
52+
"openai_api_key": <your openai api key>
53+
}
54+
```
5755

5856
## Contributions 💬
5957

aishell/cli.py

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,77 @@
11
import os
22
import sys
3-
import webbrowser
43

5-
import pyperclip
64
import rich
75
import typer
86
from rich.console import Console
97
from yt_dlp.cookies import SUPPORTED_BROWSERS
108

119
from aishell.adapters.openai_cookie_adapter import OpenAICookieAdapter
12-
from aishell.exceptions import UnauthorizedAccessError
10+
from aishell.models import RevChatGPTChatbotConfigModel
11+
from aishell.models.aishell_config_model import AiShellConfigModel
1312
from aishell.models.language_model import LanguageModel
1413
from aishell.query_clients import GPT3Client, OfficialChatGPTClient, QueryClient, ReverseEngineeredChatGPTClient
14+
from aishell.utils import AiShellConfigManager
1515

1616
cli_app = typer.Typer()
1717

1818

19-
def _open_chatgpt_browser():
20-
CHATGPT_LOGIN_URL = 'https://chat.openai.com/auth/login?next=/chat'
21-
webbrowser.open(CHATGPT_LOGIN_URL)
19+
def config_aishell():
20+
rich.print('''Hi! 🙌 I am [bold blue]AiShell[/bold blue], [yellow]your powerful terminal assistant[/yellow] 🔥
21+
I am here to assist you with configuring AiShell. 💪
2222
23+
Please make sure that you have logged into chat.openai.com on your browser before we continue. 🗝️
2324
24-
def _ask_user_copy_session_token_to_clipboard(session_token: str) -> None:
25-
copy_session_token = typer.confirm('Do you want to copy the session token to your clipboard?')
26-
if copy_session_token:
27-
pyperclip.copy(session_token)
28-
rich.print(
29-
'Session token copied to clipboard. [bold]`export CHATGPT_SESSION_TOKEN=<session_token>`[/bold] to set it.'
30-
)
25+
''')
26+
typer.confirm('Are you ready to proceed? 🚀', abort=True)
27+
28+
rich.print(f'''Which browser did you use to log in to chat.openai.com?
29+
30+
We support the following browsers: [{SUPPORTED_BROWSERS}]''')
31+
browser_name = typer.prompt('Please enter your choice here: ')
32+
if browser_name not in SUPPORTED_BROWSERS:
33+
rich.print(f'Browser {browser_name} is not supported. Supported browsers are: {SUPPORTED_BROWSERS}')
34+
sys.exit(1)
35+
36+
adapter = OpenAICookieAdapter(browser_name)
37+
session_token = adapter.get_openai_session_token()
38+
if not session_token:
39+
rich.print('Failed to get session token. 😓 Can you check if you are logged in to https://chat.openai.com?')
40+
sys.exit(1)
41+
42+
is_paid = typer.confirm("It's my last question! 🤩 Are you a PLUS user?")
43+
44+
chatgpt_config = RevChatGPTChatbotConfigModel(session_token=session_token, paid=is_paid)
45+
aishell_config = AiShellConfigModel(chatgpt_config=chatgpt_config)
46+
config_manager = AiShellConfigManager(config_model=aishell_config)
47+
config_manager.save_config()
48+
49+
rich.print(f'''[green bold]Excellent![/green bold] You are now ready to use [bold blue]AiShell[/bold blue] 🚀
50+
51+
Enjoy your AI powered terminal assistant! 🎉
52+
53+
[dim]To check your settings file, it's at: {config_manager.config_path}[/dim]
54+
55+
''')
56+
return config_manager
3157

3258

3359
@cli_app.command()
3460
def ask(question: str, language_model: LanguageModel = LanguageModel.REVERSE_ENGINEERED_CHATGPT):
61+
is_config_file_available = AiShellConfigManager.is_config_file_available(AiShellConfigManager.DEFAULT_CONFIG_PATH)
62+
config_manager: AiShellConfigManager
63+
if is_config_file_available:
64+
config_manager = AiShellConfigManager(load_config=True)
65+
else:
66+
config_manager = config_aishell()
67+
3568
query_client: QueryClient
36-
if language_model == LanguageModel.GPT3:
69+
if language_model == LanguageModel.REVERSE_ENGINEERED_CHATGPT:
70+
query_client = ReverseEngineeredChatGPTClient(config=config_manager.config_model.chatgpt_config)
71+
elif language_model == LanguageModel.GPT3:
3772
query_client = GPT3Client()
3873
elif language_model == LanguageModel.OFFICIAL_CHATGPT:
3974
query_client = OfficialChatGPTClient()
40-
elif language_model == LanguageModel.REVERSE_ENGINEERED_CHATGPT:
41-
try:
42-
query_client = ReverseEngineeredChatGPTClient()
43-
except UnauthorizedAccessError:
44-
print('You are not logged in to OpenAI, attempting to log you in...')
45-
_open_chatgpt_browser()
46-
BROWSER_NAME = typer.prompt(f'Which browser did you use to log in? [{SUPPORTED_BROWSERS}]')
47-
adapter = OpenAICookieAdapter(BROWSER_NAME)
48-
session_token = adapter.get_openai_session_token()
49-
if session_token is not None:
50-
os.environ['CHATGPT_SESSION_TOKEN'] = session_token
51-
_ask_user_copy_session_token_to_clipboard(session_token)
52-
ask(question, language_model)
53-
else:
54-
print('Failed to log in.')
55-
sys.exit()
5675

5776
query_client.query(question)
5877

aishell/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .aishell_config_model import AiShellConfigModel as AiShellConfigModel
12
from .language_model import LanguageModel as LanguageModel
23
from .open_ai_response_model import OpenAIResponseModel as OpenAIResponseModel
34
from .revchatgpt_chatbot_config_model import RevChatGPTChatbotConfigModel as RevChatGPTChatbotConfigModel
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Optional
2+
3+
from pydantic import BaseModel, root_validator
4+
5+
from .language_model import LanguageModel
6+
from .revchatgpt_chatbot_config_model import RevChatGPTChatbotConfigModel
7+
8+
9+
class AiShellConfigModel(BaseModel):
10+
language_model: LanguageModel = LanguageModel.REVERSE_ENGINEERED_CHATGPT
11+
chatgpt_config: Optional[RevChatGPTChatbotConfigModel] = None
12+
openai_api_key: Optional[str] = None
13+
14+
@root_validator
15+
def check_required_info_provided(cls, values: dict[str, Optional[str]]):
16+
OPENAI_API_KEY_REQUIRED_MODELS = (LanguageModel.GPT3, LanguageModel.OFFICIAL_CHATGPT)
17+
18+
language_model = values.get('language_model')
19+
if language_model in OPENAI_API_KEY_REQUIRED_MODELS:
20+
if not values.get('openai_api_key'):
21+
raise ValueError('openai_api_key should not be none')
22+
elif language_model == LanguageModel.REVERSE_ENGINEERED_CHATGPT:
23+
if not values.get('chatgpt_config'):
24+
raise ValueError('chatgpt_config should not be none')
25+
26+
return values

aishell/models/language_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from enum import auto
22

3-
from aishell.utils import StrEnum
3+
from .str_enum import StrEnum
44

55

66
class LanguageModel(StrEnum):
File renamed without changes.

aishell/query_clients/reverse_engineered_chatgpt_client.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@ def revchatgpt_config(self) -> dict[str, Union[str, bool]]:
1919

2020
def __init__(
2121
self,
22+
config: Optional[RevChatGPTChatbotConfigModel] = None,
2223
access_token: Optional[str] = None,
2324
session_token: Optional[str] = None,
2425
):
25-
CHATGPT_ACCESS_TOKEN = os.environ.get('CHATGPT_ACCESS_TOKEN', access_token)
26-
CHATGPT_SESSION_TOKEN = os.environ.get('CHATGPT_SESSION_TOKEN', session_token)
27-
if CHATGPT_ACCESS_TOKEN:
28-
self._config = RevChatGPTChatbotConfigModel(access_token=CHATGPT_ACCESS_TOKEN)
29-
elif CHATGPT_SESSION_TOKEN:
30-
self._config = RevChatGPTChatbotConfigModel(session_token=CHATGPT_SESSION_TOKEN)
26+
if config:
27+
self._config = config
3128
else:
32-
raise UnauthorizedAccessError('No access token or session token provided.')
29+
CHATGPT_ACCESS_TOKEN = os.environ.get('CHATGPT_ACCESS_TOKEN', access_token)
30+
CHATGPT_SESSION_TOKEN = os.environ.get('CHATGPT_SESSION_TOKEN', session_token)
31+
if CHATGPT_ACCESS_TOKEN:
32+
self._config = RevChatGPTChatbotConfigModel(access_token=CHATGPT_ACCESS_TOKEN)
33+
elif CHATGPT_SESSION_TOKEN:
34+
self._config = RevChatGPTChatbotConfigModel(session_token=CHATGPT_SESSION_TOKEN)
35+
else:
36+
raise UnauthorizedAccessError('No access token or session token provided.')
3337

3438
def query(self, prompt: str) -> str:
3539
prompt = self._construct_prompt(prompt)

aishell/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1+
from .aishell_config_manager import AiShellConfigManager as AiShellConfigManager
12
from .make_executable_command import make_executable_command as make_executable_command
2-
from .str_enum import StrEnum as StrEnum
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import json
2+
import os
3+
from typing import Optional
4+
5+
from aishell.models import AiShellConfigModel
6+
7+
8+
class AiShellConfigManager:
9+
DEFAULT_CONFIG_PATH = os.path.expanduser('~/.aishell_config.json')
10+
11+
def __init__(
12+
self,
13+
config_model: Optional[AiShellConfigModel] = None,
14+
config_path: Optional[str] = None,
15+
load_config: bool = False,
16+
):
17+
'''
18+
Initialize an instance of AiShellConfigManager
19+
20+
Args:
21+
config_model: \
22+
An instance of AiShellConfigManager to use as the configuration.\
23+
If None and load_config is True, loads the configuration from the configuration file.
24+
config_path: Path to the configuration file. \
25+
If None, uses the default path "~/.aishell_config.json".
26+
load_config: If True and config_model is None, loads the configuration from the configuration file.
27+
'''
28+
self.config_path = config_path or AiShellConfigManager.DEFAULT_CONFIG_PATH
29+
30+
if config_model:
31+
self.update_config(config_model)
32+
elif load_config:
33+
self.load_config()
34+
35+
@staticmethod
36+
def is_config_file_available(config_path: str) -> bool:
37+
return os.path.isfile(config_path)
38+
39+
def update_config(self, config_model: AiShellConfigModel):
40+
self.config_model = config_model
41+
42+
def load_config(self):
43+
self.config_model = AiShellConfigModel.parse_file(self.config_path)
44+
45+
def save_config(self):
46+
with open(self.config_path, 'w') as aishell_config_path:
47+
config_dict = self.config_model.dict()
48+
aishell_config_path.write(json.dumps(config_dict, indent=4, sort_keys=True))

0 commit comments

Comments
 (0)