Spaces:
Runtime error
Runtime error
""" | |
Standard ChatGPT | |
""" | |
import json | |
import logging | |
import uuid | |
from os import environ | |
from os import getenv | |
from os.path import exists | |
from random import choice | |
import requests | |
from OpenAIAuth.OpenAIAuth import OpenAIAuth | |
# Disable all logging | |
logging.basicConfig(level=logging.ERROR) | |
BASE_URL = environ.get("CHATGPT_BASE_URL") or choice( | |
["https://chatgpt-proxy.fly.dev/", "https://chatgpt-proxy2.fly.dev/"] | |
) | |
class Error(Exception): | |
"""Base class for exceptions in this module.""" | |
source: str | |
message: str | |
code: int | |
class Chatbot: | |
""" | |
Chatbot class for ChatGPT | |
""" | |
def __init__( | |
self, | |
config, | |
conversation_id=None, | |
parent_id=None, | |
) -> None: | |
self.config = config | |
self.session = requests.Session() | |
if "proxy" in config: | |
if isinstance(config["proxy"], str) is False: | |
raise Exception("Proxy must be a string!") | |
proxies = { | |
"http": config["proxy"], | |
"https": config["proxy"], | |
} | |
self.session.proxies.update(proxies) | |
if "verbose" in config: | |
if type(config["verbose"]) != bool: | |
raise Exception("Verbose must be a boolean!") | |
self.verbose = config["verbose"] | |
else: | |
self.verbose = False | |
self.conversation_id = conversation_id | |
self.parent_id = parent_id | |
self.conversation_mapping = {} | |
self.conversation_id_prev_queue = [] | |
self.parent_id_prev_queue = [] | |
if "email" in config and "password" in config: | |
pass | |
elif "session_token" in config: | |
pass | |
elif "access_token" in config: | |
self.__refresh_headers(config["access_token"]) | |
else: | |
raise Exception("No login details provided!") | |
if "access_token" not in config: | |
self.__login() | |
def __refresh_headers(self, access_token): | |
self.session.headers.clear() | |
self.session.headers.update( | |
{ | |
"Accept": "text/event-stream", | |
"Authorization": f"Bearer {access_token}", | |
"Content-Type": "application/json", | |
"X-Openai-Assistant-App-Id": "", | |
"Connection": "close", | |
"Accept-Language": "en-US,en;q=0.9", | |
"Referer": "https://chat.openai.com/chat", | |
}, | |
) | |
def __login(self): | |
if ( | |
"email" not in self.config or "password" not in self.config | |
) and "session_token" not in self.config: | |
raise Exception("No login details provided!") | |
auth = OpenAIAuth( | |
email_address=self.config.get("email"), | |
password=self.config.get("password"), | |
proxy=self.config.get("proxy"), | |
) | |
if self.config.get("session_token"): | |
auth.session_token = self.config["session_token"] | |
auth.get_access_token() | |
if auth.access_token is None: | |
del self.config["session_token"] | |
self.__login() | |
return | |
else: | |
auth.begin() | |
self.config["session_token"] = auth.session_token | |
auth.get_access_token() | |
self.__refresh_headers(auth.access_token) | |
def ask( | |
self, | |
prompt, | |
conversation_id=None, | |
parent_id=None, | |
# gen_title=True, | |
): | |
""" | |
Ask a question to the chatbot | |
:param prompt: String | |
:param conversation_id: UUID | |
:param parent_id: UUID | |
:param gen_title: Boolean | |
""" | |
if conversation_id is not None and parent_id is None: | |
self.__map_conversations() | |
if conversation_id is None: | |
conversation_id = self.conversation_id | |
if parent_id is None: | |
parent_id = ( | |
self.parent_id | |
if conversation_id == self.conversation_id | |
else self.conversation_mapping[conversation_id] | |
) | |
# new_conv = conversation_id is None | |
data = { | |
"action": "next", | |
"messages": [ | |
{ | |
"id": str(uuid.uuid4()), | |
"role": "user", | |
"content": {"content_type": "text", "parts": [prompt]}, | |
}, | |
], | |
"conversation_id": conversation_id, | |
"parent_message_id": parent_id or str(uuid.uuid4()), | |
"model": "text-davinci-002-render-sha", | |
} | |
# new_conv = data["conversation_id"] is None | |
self.conversation_id_prev_queue.append( | |
data["conversation_id"], | |
) # for rollback | |
self.parent_id_prev_queue.append(data["parent_message_id"]) | |
response = self.session.post( | |
url=BASE_URL + "api/conversation", | |
data=json.dumps(data), | |
timeout=360, | |
stream=True, | |
) | |
self.__check_response(response) | |
for line in response.iter_lines(): | |
line = str(line)[2:-1] | |
if line == "" or line is None: | |
continue | |
if "data: " in line: | |
line = line[6:] | |
if line == "[DONE]": | |
break | |
# Replace accidentally escaped double quotes | |
line = line.replace('\\"', '"') | |
line = line.replace("\\'", "'") | |
line = line.replace("\\\\", "\\") | |
# Try parse JSON | |
try: | |
line = json.loads(line) | |
except json.decoder.JSONDecodeError: | |
continue | |
if not self.__check_fields(line): | |
print("Field missing") | |
print(line) | |
continue | |
message = line["message"]["content"]["parts"][0] | |
conversation_id = line["conversation_id"] | |
parent_id = line["message"]["id"] | |
yield { | |
"message": message, | |
"conversation_id": conversation_id, | |
"parent_id": parent_id, | |
} | |
if parent_id is not None: | |
self.parent_id = parent_id | |
if conversation_id is not None: | |
self.conversation_id = conversation_id | |
def __check_fields(self, data: dict) -> bool: | |
try: | |
data["message"]["content"] | |
except TypeError: | |
return False | |
except KeyError: | |
return False | |
return True | |
def __check_response(self, response): | |
if response.status_code != 200: | |
print(response.text) | |
error = Error() | |
error.source = "OpenAI" | |
error.code = response.status_code | |
error.message = response.text | |
raise error | |
def get_conversations(self, offset=0, limit=20): | |
""" | |
Get conversations | |
:param offset: Integer | |
:param limit: Integer | |
""" | |
url = BASE_URL + f"api/conversations?offset={offset}&limit={limit}" | |
response = self.session.get(url) | |
self.__check_response(response) | |
data = json.loads(response.text) | |
return data["items"] | |
def get_msg_history(self, convo_id): | |
""" | |
Get message history | |
:param id: UUID of conversation | |
""" | |
url = BASE_URL + f"api/conversation/{convo_id}" | |
response = self.session.get(url) | |
self.__check_response(response) | |
data = json.loads(response.text) | |
return data | |
# def __gen_title(self, convo_id, message_id): | |
# """ | |
# Generate title for conversation | |
# """ | |
# url = BASE_URL + f"api/conversation/gen_title/{convo_id}" | |
# response = self.session.post( | |
# url, | |
# data=json.dumps( | |
# {"message_id": message_id, "model": "text-davinci-002-render"}, | |
# ), | |
# ) | |
# self.__check_response(response) | |
def change_title(self, convo_id, title): | |
""" | |
Change title of conversation | |
:param id: UUID of conversation | |
:param title: String | |
""" | |
url = BASE_URL + f"api/conversation/{convo_id}" | |
response = self.session.patch(url, data=f'{{"title": "{title}"}}') | |
self.__check_response(response) | |
def delete_conversation(self, convo_id): | |
""" | |
Delete conversation | |
:param id: UUID of conversation | |
""" | |
url = BASE_URL + f"api/conversation/{convo_id}" | |
response = self.session.patch(url, data='{"is_visible": false}') | |
self.__check_response(response) | |
def clear_conversations(self): | |
""" | |
Delete all conversations | |
""" | |
url = BASE_URL + "api/conversations" | |
response = self.session.patch(url, data='{"is_visible": false}') | |
self.__check_response(response) | |
def __map_conversations(self): | |
conversations = self.get_conversations() | |
histories = [self.get_msg_history(x["id"]) for x in conversations] | |
for x, y in zip(conversations, histories): | |
self.conversation_mapping[x["id"]] = y["current_node"] | |
def reset_chat(self) -> None: | |
""" | |
Reset the conversation ID and parent ID. | |
:return: None | |
""" | |
self.conversation_id = None | |
self.parent_id = str(uuid.uuid4()) | |
def rollback_conversation(self, num=1) -> None: | |
""" | |
Rollback the conversation. | |
:param num: The number of messages to rollback | |
:return: None | |
""" | |
for _ in range(num): | |
self.conversation_id = self.conversation_id_prev_queue.pop() | |
self.parent_id = self.parent_id_prev_queue.pop() | |
def get_input(prompt): | |
""" | |
Multiline input function. | |
""" | |
# Display the prompt | |
print(prompt, end="") | |
# Initialize an empty list to store the input lines | |
lines = [] | |
# Read lines of input until the user enters an empty line | |
while True: | |
line = input() | |
if line == "": | |
break | |
lines.append(line) | |
# Join the lines, separated by newlines, and store the result | |
user_input = "\n".join(lines) | |
# Return the input | |
return user_input | |
def configure(): | |
""" | |
Looks for a config file in the following locations: | |
""" | |
config_files = ["config.json"] | |
xdg_config_home = getenv("XDG_CONFIG_HOME") | |
if xdg_config_home: | |
config_files.append(f"{xdg_config_home}/revChatGPT/config.json") | |
user_home = getenv("HOME") | |
if user_home: | |
config_files.append(f"{user_home}/.config/revChatGPT/config.json") | |
config_file = next((f for f in config_files if exists(f)), None) | |
if config_file: | |
with open(config_file, encoding="utf-8") as f: | |
config = json.load(f) | |
else: | |
print("No config file found.") | |
raise Exception("No config file found.") | |
return config | |
def main(config): | |
""" | |
Main function for the chatGPT program. | |
""" | |
print("Logging in...") | |
chatbot = Chatbot(config) | |
while True: | |
prompt = get_input("\nYou:\n") | |
if prompt.startswith("!"): | |
if prompt == "!help": | |
print( | |
""" | |
!help - Show this message | |
!reset - Forget the current conversation | |
!config - Show the current configuration | |
!rollback x - Rollback the conversation (x being the number of messages to rollback) | |
!exit - Exit this program | |
""", | |
) | |
continue | |
elif prompt == "!reset": | |
chatbot.reset_chat() | |
print("Chat session successfully reset.") | |
continue | |
elif prompt == "!config": | |
print(json.dumps(chatbot.config, indent=4)) | |
continue | |
elif prompt.startswith("!rollback"): | |
# Default to 1 rollback if no number is specified | |
try: | |
rollback = int(prompt.split(" ")[1]) | |
except IndexError: | |
rollback = 1 | |
chatbot.rollback_conversation(rollback) | |
print(f"Rolled back {rollback} messages.") | |
continue | |
elif prompt.startswith("!setconversation"): | |
try: | |
chatbot.config["conversation"] = prompt.split(" ")[1] | |
print("Conversation has been changed") | |
except IndexError: | |
print("Please include conversation UUID in command") | |
continue | |
elif prompt == "!exit": | |
break | |
print("Chatbot: ") | |
prev_text = "" | |
for data in chatbot.ask( | |
prompt, | |
conversation_id=chatbot.config.get("conversation"), | |
parent_id=chatbot.config.get("parent_id"), | |
): | |
message = data["message"][len(prev_text) :] | |
print(message, end="", flush=True) | |
prev_text = data["message"] | |
print() | |
# print(message["message"]) | |
if __name__ == "__main__": | |
print( | |
""" | |
ChatGPT - A command-line interface to OpenAI's ChatGPT (https://chat.openai.com/chat) | |
Repo: github.com/acheong08/ChatGPT | |
""", | |
) | |
print("Type '!help' to show a full list of commands") | |
print("Press enter twice to submit your question.\n") | |
main(configure()) | |