JeffJing commited on
Commit
a46994b
·
1 Parent(s): 0ee4b17

Upload 2 files

Browse files
Files changed (2) hide show
  1. V1.py +420 -0
  2. __init__.py +0 -0
V1.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standard ChatGPT
3
+ """
4
+ import json
5
+ import logging
6
+ import uuid
7
+ from os import environ
8
+ from os import getenv
9
+ from os.path import exists
10
+ from random import choice
11
+
12
+ import requests
13
+ from OpenAIAuth.OpenAIAuth import OpenAIAuth
14
+
15
+ # Disable all logging
16
+ logging.basicConfig(level=logging.ERROR)
17
+
18
+ BASE_URL = environ.get("CHATGPT_BASE_URL") or choice(
19
+ ["https://chatgpt-proxy.fly.dev/", "https://chatgpt-proxy2.fly.dev/"]
20
+ )
21
+
22
+
23
+ class Error(Exception):
24
+ """Base class for exceptions in this module."""
25
+
26
+ source: str
27
+ message: str
28
+ code: int
29
+
30
+
31
+ class Chatbot:
32
+ """
33
+ Chatbot class for ChatGPT
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ config,
39
+ conversation_id=None,
40
+ parent_id=None,
41
+ ) -> None:
42
+ self.config = config
43
+ self.session = requests.Session()
44
+ if "proxy" in config:
45
+ if isinstance(config["proxy"], str) is False:
46
+ raise Exception("Proxy must be a string!")
47
+ proxies = {
48
+ "http": config["proxy"],
49
+ "https": config["proxy"],
50
+ }
51
+ self.session.proxies.update(proxies)
52
+ if "verbose" in config:
53
+ if type(config["verbose"]) != bool:
54
+ raise Exception("Verbose must be a boolean!")
55
+ self.verbose = config["verbose"]
56
+ else:
57
+ self.verbose = False
58
+ self.conversation_id = conversation_id
59
+ self.parent_id = parent_id
60
+ self.conversation_mapping = {}
61
+ self.conversation_id_prev_queue = []
62
+ self.parent_id_prev_queue = []
63
+ if "email" in config and "password" in config:
64
+ pass
65
+ elif "session_token" in config:
66
+ pass
67
+ elif "access_token" in config:
68
+ self.__refresh_headers(config["access_token"])
69
+ else:
70
+ raise Exception("No login details provided!")
71
+ if "access_token" not in config:
72
+ self.__login()
73
+
74
+ def __refresh_headers(self, access_token):
75
+ self.session.headers.clear()
76
+ self.session.headers.update(
77
+ {
78
+ "Accept": "text/event-stream",
79
+ "Authorization": f"Bearer {access_token}",
80
+ "Content-Type": "application/json",
81
+ "X-Openai-Assistant-App-Id": "",
82
+ "Connection": "close",
83
+ "Accept-Language": "en-US,en;q=0.9",
84
+ "Referer": "https://chat.openai.com/chat",
85
+ },
86
+ )
87
+
88
+ def __login(self):
89
+ if (
90
+ "email" not in self.config or "password" not in self.config
91
+ ) and "session_token" not in self.config:
92
+ raise Exception("No login details provided!")
93
+ auth = OpenAIAuth(
94
+ email_address=self.config.get("email"),
95
+ password=self.config.get("password"),
96
+ proxy=self.config.get("proxy"),
97
+ )
98
+ if self.config.get("session_token"):
99
+ auth.session_token = self.config["session_token"]
100
+ auth.get_access_token()
101
+ if auth.access_token is None:
102
+ del self.config["session_token"]
103
+ self.__login()
104
+ return
105
+ else:
106
+ auth.begin()
107
+ self.config["session_token"] = auth.session_token
108
+ auth.get_access_token()
109
+
110
+ self.__refresh_headers(auth.access_token)
111
+
112
+ def ask(
113
+ self,
114
+ prompt,
115
+ conversation_id=None,
116
+ parent_id=None,
117
+ # gen_title=True,
118
+ ):
119
+ """
120
+ Ask a question to the chatbot
121
+ :param prompt: String
122
+ :param conversation_id: UUID
123
+ :param parent_id: UUID
124
+ :param gen_title: Boolean
125
+ """
126
+ if conversation_id is not None and parent_id is None:
127
+ self.__map_conversations()
128
+ if conversation_id is None:
129
+ conversation_id = self.conversation_id
130
+ if parent_id is None:
131
+ parent_id = (
132
+ self.parent_id
133
+ if conversation_id == self.conversation_id
134
+ else self.conversation_mapping[conversation_id]
135
+ )
136
+ # new_conv = conversation_id is None
137
+ data = {
138
+ "action": "next",
139
+ "messages": [
140
+ {
141
+ "id": str(uuid.uuid4()),
142
+ "role": "user",
143
+ "content": {"content_type": "text", "parts": [prompt]},
144
+ },
145
+ ],
146
+ "conversation_id": conversation_id,
147
+ "parent_message_id": parent_id or str(uuid.uuid4()),
148
+ "model": "text-davinci-002-render-sha"
149
+ if not self.config.get("paid")
150
+ else "text-davinci-002-render-paid",
151
+ }
152
+ # new_conv = data["conversation_id"] is None
153
+ self.conversation_id_prev_queue.append(
154
+ data["conversation_id"],
155
+ ) # for rollback
156
+ self.parent_id_prev_queue.append(data["parent_message_id"])
157
+ response = self.session.post(
158
+ url=BASE_URL + "api/conversation",
159
+ data=json.dumps(data),
160
+ timeout=360,
161
+ stream=True,
162
+ )
163
+ self.__check_response(response)
164
+ for line in response.iter_lines():
165
+ line = str(line)[2:-1]
166
+ if line == "" or line is None:
167
+ continue
168
+ if "data: " in line:
169
+ line = line[6:]
170
+ if line == "[DONE]":
171
+ break
172
+
173
+ # Replace accidentally escaped double quotes
174
+ line = line.replace('\\"', '"')
175
+ line = line.replace("\\'", "'")
176
+ line = line.replace("\\\\", "\\")
177
+ # Try parse JSON
178
+ try:
179
+ line = json.loads(line)
180
+ except json.decoder.JSONDecodeError:
181
+ continue
182
+ if not self.__check_fields(line):
183
+ print("Field missing")
184
+ print(line)
185
+ continue
186
+ message = line["message"]["content"]["parts"][0]
187
+ conversation_id = line["conversation_id"]
188
+ parent_id = line["message"]["id"]
189
+ yield {
190
+ "message": message,
191
+ "conversation_id": conversation_id,
192
+ "parent_id": parent_id,
193
+ }
194
+ if parent_id is not None:
195
+ self.parent_id = parent_id
196
+ if conversation_id is not None:
197
+ self.conversation_id = conversation_id
198
+
199
+ def __check_fields(self, data: dict) -> bool:
200
+ try:
201
+ data["message"]["content"]
202
+ except TypeError:
203
+ return False
204
+ except KeyError:
205
+ return False
206
+ return True
207
+
208
+ def __check_response(self, response):
209
+ if response.status_code != 200:
210
+ print(response.text)
211
+ error = Error()
212
+ error.source = "OpenAI"
213
+ error.code = response.status_code
214
+ error.message = response.text
215
+ raise error
216
+
217
+ def get_conversations(self, offset=0, limit=20):
218
+ """
219
+ Get conversations
220
+ :param offset: Integer
221
+ :param limit: Integer
222
+ """
223
+ url = BASE_URL + f"api/conversations?offset={offset}&limit={limit}"
224
+ response = self.session.get(url)
225
+ self.__check_response(response)
226
+ data = json.loads(response.text)
227
+ return data["items"]
228
+
229
+ def get_msg_history(self, convo_id):
230
+ """
231
+ Get message history
232
+ :param id: UUID of conversation
233
+ """
234
+ url = BASE_URL + f"api/conversation/{convo_id}"
235
+ response = self.session.get(url)
236
+ self.__check_response(response)
237
+ data = json.loads(response.text)
238
+ return data
239
+
240
+ # def __gen_title(self, convo_id, message_id):
241
+ # """
242
+ # Generate title for conversation
243
+ # """
244
+ # url = BASE_URL + f"api/conversation/gen_title/{convo_id}"
245
+ # response = self.session.post(
246
+ # url,
247
+ # data=json.dumps(
248
+ # {"message_id": message_id, "model": "text-davinci-002-render"},
249
+ # ),
250
+ # )
251
+ # self.__check_response(response)
252
+
253
+ def change_title(self, convo_id, title):
254
+ """
255
+ Change title of conversation
256
+ :param id: UUID of conversation
257
+ :param title: String
258
+ """
259
+ url = BASE_URL + f"api/conversation/{convo_id}"
260
+ response = self.session.patch(url, data=f'{{"title": "{title}"}}')
261
+ self.__check_response(response)
262
+
263
+ def delete_conversation(self, convo_id):
264
+ """
265
+ Delete conversation
266
+ :param id: UUID of conversation
267
+ """
268
+ url = BASE_URL + f"api/conversation/{convo_id}"
269
+ response = self.session.patch(url, data='{"is_visible": false}')
270
+ self.__check_response(response)
271
+
272
+ def clear_conversations(self):
273
+ """
274
+ Delete all conversations
275
+ """
276
+ url = BASE_URL + "api/conversations"
277
+ response = self.session.patch(url, data='{"is_visible": false}')
278
+ self.__check_response(response)
279
+
280
+ def __map_conversations(self):
281
+ conversations = self.get_conversations()
282
+ histories = [self.get_msg_history(x["id"]) for x in conversations]
283
+ for x, y in zip(conversations, histories):
284
+ self.conversation_mapping[x["id"]] = y["current_node"]
285
+
286
+ def reset_chat(self) -> None:
287
+ """
288
+ Reset the conversation ID and parent ID.
289
+
290
+ :return: None
291
+ """
292
+ self.conversation_id = None
293
+ self.parent_id = str(uuid.uuid4())
294
+
295
+ def rollback_conversation(self, num=1) -> None:
296
+ """
297
+ Rollback the conversation.
298
+ :param num: The number of messages to rollback
299
+ :return: None
300
+ """
301
+ for _ in range(num):
302
+ self.conversation_id = self.conversation_id_prev_queue.pop()
303
+ self.parent_id = self.parent_id_prev_queue.pop()
304
+
305
+
306
+ def get_input(prompt):
307
+ """
308
+ Multiline input function.
309
+ """
310
+ # Display the prompt
311
+ print(prompt, end="")
312
+
313
+ # Initialize an empty list to store the input lines
314
+ lines = []
315
+
316
+ # Read lines of input until the user enters an empty line
317
+ while True:
318
+ line = input()
319
+ if line == "":
320
+ break
321
+ lines.append(line)
322
+
323
+ # Join the lines, separated by newlines, and store the result
324
+ user_input = "\n".join(lines)
325
+
326
+ # Return the input
327
+ return user_input
328
+
329
+
330
+ def configure():
331
+ """
332
+ Looks for a config file in the following locations:
333
+ """
334
+ config_files = ["config.json"]
335
+ xdg_config_home = getenv("XDG_CONFIG_HOME")
336
+ if xdg_config_home:
337
+ config_files.append(f"{xdg_config_home}/revChatGPT/config.json")
338
+ user_home = getenv("HOME")
339
+ if user_home:
340
+ config_files.append(f"{user_home}/.config/revChatGPT/config.json")
341
+
342
+ config_file = next((f for f in config_files if exists(f)), None)
343
+ if config_file:
344
+ with open(config_file, encoding="utf-8") as f:
345
+ config = json.load(f)
346
+ else:
347
+ print("No config file found.")
348
+ raise Exception("No config file found.")
349
+ return config
350
+
351
+
352
+ def main(config):
353
+ """
354
+ Main function for the chatGPT program.
355
+ """
356
+ print("Logging in...")
357
+ chatbot = Chatbot(config)
358
+ while True:
359
+ prompt = get_input("\nYou:\n")
360
+ if prompt.startswith("!"):
361
+ if prompt == "!help":
362
+ print(
363
+ """
364
+ !help - Show this message
365
+ !reset - Forget the current conversation
366
+ !config - Show the current configuration
367
+ !rollback x - Rollback the conversation (x being the number of messages to rollback)
368
+ !exit - Exit this program
369
+ """,
370
+ )
371
+ continue
372
+ elif prompt == "!reset":
373
+ chatbot.reset_chat()
374
+ print("Chat session successfully reset.")
375
+ continue
376
+ elif prompt == "!config":
377
+ print(json.dumps(chatbot.config, indent=4))
378
+ continue
379
+ elif prompt.startswith("!rollback"):
380
+ # Default to 1 rollback if no number is specified
381
+ try:
382
+ rollback = int(prompt.split(" ")[1])
383
+ except IndexError:
384
+ rollback = 1
385
+ chatbot.rollback_conversation(rollback)
386
+ print(f"Rolled back {rollback} messages.")
387
+ continue
388
+ elif prompt.startswith("!setconversation"):
389
+ try:
390
+ chatbot.config["conversation"] = prompt.split(" ")[1]
391
+ print("Conversation has been changed")
392
+ except IndexError:
393
+ print("Please include conversation UUID in command")
394
+ continue
395
+ elif prompt == "!exit":
396
+ break
397
+ print("Chatbot: ")
398
+ prev_text = ""
399
+ for data in chatbot.ask(
400
+ prompt,
401
+ conversation_id=chatbot.config.get("conversation"),
402
+ parent_id=chatbot.config.get("parent_id"),
403
+ ):
404
+ message = data["message"][len(prev_text) :]
405
+ print(message, end="", flush=True)
406
+ prev_text = data["message"]
407
+ print()
408
+ # print(message["message"])
409
+
410
+
411
+ if __name__ == "__main__":
412
+ print(
413
+ """
414
+ ChatGPT - A command-line interface to OpenAI's ChatGPT (https://chat.openai.com/chat)
415
+ Repo: github.com/acheong08/ChatGPT
416
+ """,
417
+ )
418
+ print("Type '!help' to show a full list of commands")
419
+ print("Press enter twice to submit your question.\n")
420
+ main(configure())
__init__.py ADDED
File without changes