Skip to content

Commit 4fef5c9

Browse files
authored
Merge pull request #17 from F33RNI/next
Next
2 parents 43b368c + 0fe10a3 commit 4fef5c9

10 files changed

+228
-94
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/*.zip
33
/test.py
44
/logs/
5+
/chats.json

Diff for: AIHandler.py

+96-30
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,23 @@
1818
import queue
1919
import threading
2020
import time
21+
import uuid
2122

2223
import openai
2324

2425
import Authenticator
2526
import RequestResponseContainer
27+
from JSONReaderWriter import load_json, save_json
2628

2729
EMPTY_RESPONSE_ERROR_MESSAGE = 'Empty response or unhandled error!'
2830
ERROR_CHATGPT_DISABLED = 'ChatGPT module is disabled in settings.json'
2931
ERROR_DALLE_DISABLED = 'DALL-E module is disabled in settings.json'
3032

3133

3234
class AIHandler:
33-
def __init__(self, settings, authenticator):
35+
def __init__(self, settings, chats_file, authenticator):
3436
self.settings = settings
37+
self.chats_file = chats_file
3538
self.authenticator = authenticator
3639

3740
# Loop running flag
@@ -46,10 +49,6 @@ def __init__(self, settings, authenticator):
4649
# Requests queue
4750
self.requests_queue = None
4851

49-
# Conversation id and parent id to continue dialog
50-
self.conversation_id = None
51-
self.parent_id = None
52-
5352
# Check settings
5453
if self.settings is not None:
5554
# Initialize queue
@@ -68,6 +67,63 @@ def thread_start(self):
6867
thread.start()
6968
logging.info('AIHandler thread: ' + thread.name)
7069

70+
def get_chat(self, chat_id: int):
71+
"""
72+
Retrieves conversation_id and parent_id for given chat_id or None if not exists
73+
:param chat_id:
74+
:return: (conversation_id, parent_id)
75+
"""
76+
logging.info('Loading conversation_id for chat_id ' + str(chat_id))
77+
chats = load_json(self.chats_file)
78+
if chats is not None and str(chat_id) in chats:
79+
chat = chats[str(chat_id)]
80+
conversation_id = None
81+
parent_id = None
82+
if 'conversation_id' in chat:
83+
conversation_id = chat['conversation_id']
84+
if 'parent_id' in chat:
85+
parent_id = chat['parent_id']
86+
87+
return conversation_id, parent_id
88+
else:
89+
return None, None
90+
91+
def set_chat(self, chat_id: int, conversation_id=None, parent_id=None):
92+
"""
93+
Saves conversation ID and parent ID or Nones to remove it
94+
:param chat_id:
95+
:param conversation_id:
96+
:param parent_id:
97+
:return:
98+
"""
99+
logging.info('Saving conversation_id ' + str(conversation_id) + ' and parent_id '
100+
+ str(parent_id) + ' for chat_id ' + str(chat_id))
101+
chats = load_json(self.chats_file)
102+
if chats is not None:
103+
if str(chat_id) in chats:
104+
# Save or delete conversation_id
105+
if conversation_id is not None and len(conversation_id) > 0:
106+
chats[str(chat_id)]['conversation_id'] = conversation_id
107+
elif 'conversation_id' in chats[str(chat_id)]:
108+
del chats[str(chat_id)]['conversation_id']
109+
110+
# Save or delete parent_id
111+
if parent_id is not None and len(parent_id) > 0:
112+
chats[str(chat_id)]['parent_id'] = parent_id
113+
elif 'parent_id' in chats[str(chat_id)]:
114+
del chats[str(chat_id)]['parent_id']
115+
116+
# New chat
117+
else:
118+
chats[str(chat_id)] = {}
119+
if conversation_id is not None and len(conversation_id) > 0:
120+
chats[str(chat_id)]['conversation_id'] = conversation_id
121+
if parent_id is not None and len(parent_id) > 0:
122+
chats[str(chat_id)]['parent_id'] = parent_id
123+
else:
124+
chats = {}
125+
save_json(self.chats_file, chats)
126+
71127
def gpt_loop(self):
72128
"""
73129
Background loop for handling requests
@@ -99,25 +155,33 @@ def gpt_loop(self):
99155

100156
# API type 0
101157
if self.authenticator.api_type == 0:
102-
# Initialize conversation id
103-
if len(str(self.settings['chatgpt_api_0']['existing_conversation_id'])) > 0:
104-
self.conversation_id = str(self.settings['chatgpt_api_0']['existing_conversation_id'])
105-
logging.info('Conversation id: ' + str(self.conversation_id))
106-
else:
107-
self.conversation_id = None
158+
# Get conversation_id
159+
conversation_id, parent_id = self.get_chat(container.chat_id)
108160

109161
# Get chatbot from Authenticator class
110162
chatbot = self.authenticator.chatbot
111163

164+
# Reset chat
165+
chatbot.reset()
166+
112167
# Ask
113-
for data in chatbot.ask_stream(str(container.request), conversation_id=self.conversation_id):
168+
for data in chatbot.ask_stream(str(container.request), conversation_id=conversation_id):
114169
# Initialize response
115170
if api_response is None:
116171
api_response = ''
117172

118173
# Append response
119174
api_response += str(data)
120175

176+
# Generate and save conversation ID
177+
try:
178+
if conversation_id is None:
179+
conversation_id = str(uuid.uuid4())
180+
chatbot.save_conversation(conversation_id)
181+
self.set_chat(container.chat_id, conversation_id, parent_id)
182+
except Exception as e:
183+
logging.warning('Error saving conversation! ' + str(e))
184+
121185
# Remove tags
122186
api_response = api_response.replace('<|im_end|>', '').replace('<|im_start|>', '')
123187

@@ -140,34 +204,36 @@ def gpt_loop(self):
140204
# Lock chatbot
141205
self.authenticator.chatbot_locked = True
142206

207+
# Get conversation_id and parent_id
208+
conversation_id, parent_id = self.get_chat(container.chat_id)
209+
143210
# Log request
144-
logging.info('Asking: ' + str(container.request))
145-
146-
# Initialize conversation_id and parent_id
147-
if self.conversation_id is None:
148-
if len(str(self.settings['chatgpt_api_1']['chatgpt_dialog']['conversation_id'])) > 0:
149-
self.conversation_id \
150-
= str(self.settings['chatgpt_api_1']['chatgpt_dialog']['conversation_id'])
151-
logging.info('Initial conversation id: ' + str(self.conversation_id))
152-
if self.parent_id is None:
153-
if len(str(self.settings['chatgpt_api_1']['chatgpt_dialog']['parent_id'])) > 0:
154-
self.parent_id = str(self.settings['chatgpt_api_1']['chatgpt_dialog']['parent_id'])
155-
logging.info('Initial parent id: ' + str(self.parent_id))
211+
logging.info('Asking: ' + str(container.request)
212+
+ ', conversation_id: ' + str(conversation_id) + ', parent_id: ' + str(parent_id))
213+
214+
# Reset chat
215+
chatbot.reset_chat()
156216

157217
# Ask
158218
for data in chatbot.ask(str(container.request),
159-
conversation_id=self.conversation_id,
160-
parent_id=self.parent_id):
219+
conversation_id=conversation_id,
220+
parent_id=parent_id):
161221
# Get last response
162222
api_response = data['message']
163223

164224
# Store conversation_id
165-
if data['conversation_id'] is not None:
166-
self.conversation_id = data['conversation_id']
225+
if 'conversation_id' in data and data['conversation_id'] is not None:
226+
conversation_id = data['conversation_id']
227+
228+
# Store parent_id
229+
if 'parent_id' in data and data['parent_id'] is not None:
230+
parent_id = data['parent_id']
167231

168232
# Log conversation id and parent id
169-
logging.info('Current conversation id: ' + str(self.conversation_id)
170-
+ '\tParent id: ' + str(self.parent_id))
233+
logging.info('Current conversation_id: ' + conversation_id + ', parent_id: ' + parent_id)
234+
235+
# Save conversation id
236+
self.set_chat(container.chat_id, conversation_id, parent_id)
171237

172238
# Wrong api type
173239
else:

Diff for: Authenticator.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ def proxy_checker_loop(self):
254254
logging.warning(str(e))
255255

256256
# Wait before next try
257-
wait_seconds = int(self.settings['chatgpt_dialog']['too_many_requests_wait_time_seconds'])
257+
wait_seconds = \
258+
int(self.settings['chatgpt_api_1']['proxy']['too_many_requests_wait_time_seconds'])
258259
logging.warning('Waiting ' + str(wait_seconds) + ' seconds...')
259260
self.chatbot_too_many_requests = True
260261
time.sleep(wait_seconds)

Diff for: BotHandler.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,25 @@
2626
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
2727

2828
import RequestResponseContainer
29-
from AIHandler import AIHandler
3029
from main import TELEGRAMUS_VERSION
3130

3231
BOT_COMMAND_START = 'start'
3332
BOT_COMMAND_HELP = 'help'
3433
BOT_COMMAND_QUEUE = 'queue'
3534
BOT_COMMAND_GPT = 'gpt'
3635
BOT_COMMAND_DRAW = 'draw'
36+
BOT_COMMAND_CLEAR = 'clear'
3737
BOT_COMMAND_RESTART = 'restart'
3838

3939
# List of markdown chars to escape with \\
4040
MARKDOWN_ESCAPE = ['_', '*', '[', ']', '(', ')', '~', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
4141

4242

4343
class BotHandler:
44-
def __init__(self, settings, messages, ai_handler: AIHandler):
44+
def __init__(self, settings, messages, chats_file, ai_handler):
4545
self.settings = settings
4646
self.messages = messages
47+
self.chats_file = chats_file
4748
self.ai_handler = ai_handler
4849
self.application = None
4950
self.event_loop = None
@@ -83,6 +84,7 @@ def bot_start(self):
8384
self.application.add_handler(CommandHandler(BOT_COMMAND_QUEUE, self.bot_command_queue))
8485
self.application.add_handler(CommandHandler(BOT_COMMAND_GPT, self.bot_command_gpt))
8586
self.application.add_handler(CommandHandler(BOT_COMMAND_DRAW, self.bot_command_draw))
87+
self.application.add_handler(CommandHandler(BOT_COMMAND_CLEAR, self.bot_command_clear))
8688
self.application.add_handler(CommandHandler(BOT_COMMAND_RESTART, self.bot_command_restart))
8789
self.application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), self.bot_read_message))
8890

@@ -166,6 +168,34 @@ async def bot_read_message(self, update: Update, context: ContextTypes.DEFAULT_T
166168
except Exception as e:
167169
logging.error('Error sending message! ' + str(e))
168170

171+
async def bot_command_clear(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
172+
"""
173+
/clear command
174+
:param update:
175+
:param context:
176+
:return:
177+
"""
178+
user = update.message.from_user
179+
chat_id = update.effective_chat.id
180+
logging.info('/clear command from user ' + str(user.full_name) + ' request: ' + ' '.join(context.args))
181+
182+
# Delete conversation
183+
if self.ai_handler.authenticator.api_type == 1:
184+
conversation_id, _ = self.ai_handler.get_chat(chat_id)
185+
if conversation_id is not None and len(conversation_id) > 0:
186+
try:
187+
self.ai_handler.authenticator.chatbot.delete_conversation(conversation_id)
188+
except Exception as e:
189+
logging.warning('Error deleting conversation ' + str(conversation_id) + ' ' + str(e))
190+
191+
# Clear conversation ID and parent ID
192+
self.ai_handler.set_chat(chat_id, None, None)
193+
194+
try:
195+
await context.bot.send_message(chat_id=chat_id, text=str(self.messages['chat_reset']).replace('\\n', '\n'))
196+
except Exception as e:
197+
logging.error('Error sending message! ' + str(e))
198+
169199
async def bot_command_restart(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
170200
"""
171201
/restart command

Diff for: JSONReaderWriter.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Copyright (C) 2022 Fern Lane, GPT-telegramus
3+
Licensed under the GNU Affero General Public License, Version 3.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
https://www.gnu.org/licenses/agpl-3.0.en.html
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR
13+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
14+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
15+
OTHER DEALINGS IN THE SOFTWARE.
16+
"""
17+
import json
18+
import logging
19+
import os.path
20+
21+
22+
def load_json(file_name: str):
23+
"""
24+
Loads json from file_name
25+
:return: json if loaded or empty json if not
26+
"""
27+
try:
28+
if os.path.exists(file_name):
29+
logging.info('Loading ' + file_name + '...')
30+
messages_file = open(file_name, encoding='utf-8')
31+
json_content = json.load(messages_file)
32+
messages_file.close()
33+
if json_content is not None and len(str(json_content)) > 0:
34+
logging.info('Loaded json: ' + str(json_content))
35+
else:
36+
json_content = None
37+
logging.error('Error loading json data from file ' + file_name)
38+
else:
39+
logging.warning('No ' + file_name + ' file! Returning empty json')
40+
return {}
41+
except Exception as e:
42+
json_content = None
43+
logging.error(e, exc_info=True)
44+
45+
if json_content is None:
46+
json_content = {}
47+
48+
return json_content
49+
50+
51+
def save_json(file_name: str, content):
52+
"""
53+
Saves
54+
:param file_name: filename to save
55+
:param content: JSON dictionary
56+
:return:
57+
"""
58+
logging.info('Saving to ' + file_name + '...')
59+
file = open(file_name, 'w')
60+
json.dump(content, file, indent=4)
61+
file.close()

0 commit comments

Comments
 (0)