3131import database
3232import openai_utils
3333
34+ import base64
3435
3536# setup
3637db = database .Database ()
@@ -177,6 +178,168 @@ async def retry_handle(update: Update, context: CallbackContext):
177178
178179 await message_handle (update , context , message = last_dialog_message ["user" ], use_new_dialog_timeout = False )
179180
181+ async def _vision_message_handle_fn (
182+ update : Update , context : CallbackContext , use_new_dialog_timeout : bool = True
183+ ):
184+ logger .info ('_vision_message_handle_fn' )
185+ user_id = update .message .from_user .id
186+ current_model = db .get_user_attribute (user_id , "current_model" )
187+
188+ if current_model != "gpt-4-vision-preview" :
189+ await update .message .reply_text (
190+ "🥲 Images processing is only available for <b>gpt-4-vision-preview</b> model. Please change your settings in /settings" ,
191+ parse_mode = ParseMode .HTML ,
192+ )
193+ return
194+
195+ chat_mode = db .get_user_attribute (user_id , "current_chat_mode" )
196+
197+ # new dialog timeout
198+ if use_new_dialog_timeout :
199+ if (datetime .now () - db .get_user_attribute (user_id , "last_interaction" )).seconds > config .new_dialog_timeout and len (db .get_dialog_messages (user_id )) > 0 :
200+ db .start_new_dialog (user_id )
201+ await update .message .reply_text (f"Starting new dialog due to timeout (<b>{ config .chat_modes [chat_mode ]['name' ]} </b> mode) ✅" , parse_mode = ParseMode .HTML )
202+ db .set_user_attribute (user_id , "last_interaction" , datetime .now ())
203+
204+ buf = None
205+ if update .message .effective_attachment :
206+ photo = update .message .effective_attachment [- 1 ]
207+ photo_file = await context .bot .get_file (photo .file_id )
208+
209+ # store file in memory, not on disk
210+ buf = io .BytesIO ()
211+ await photo_file .download_to_memory (buf )
212+ buf .name = "image.jpg" # file extension is required
213+ buf .seek (0 ) # move cursor to the beginning of the buffer
214+
215+ # in case of CancelledError
216+ n_input_tokens , n_output_tokens = 0 , 0
217+
218+ try :
219+ # send placeholder message to user
220+ placeholder_message = await update .message .reply_text ("..." )
221+ message = update .message .caption or update .message .text
222+
223+ # send typing action
224+ await update .message .chat .send_action (action = "typing" )
225+
226+ if message is None or len (message ) == 0 :
227+ await update .message .reply_text (
228+ "🥲 You sent <b>empty message</b>. Please, try again!" ,
229+ parse_mode = ParseMode .HTML ,
230+ )
231+ return
232+
233+ dialog_messages = db .get_dialog_messages (user_id , dialog_id = None )
234+ parse_mode = {"html" : ParseMode .HTML , "markdown" : ParseMode .MARKDOWN }[
235+ config .chat_modes [chat_mode ]["parse_mode" ]
236+ ]
237+
238+ chatgpt_instance = openai_utils .ChatGPT (model = current_model )
239+ if config .enable_message_streaming :
240+ gen = chatgpt_instance .send_vision_message_stream (
241+ message ,
242+ dialog_messages = dialog_messages ,
243+ image_buffer = buf ,
244+ chat_mode = chat_mode ,
245+ )
246+ else :
247+ (
248+ answer ,
249+ (n_input_tokens , n_output_tokens ),
250+ n_first_dialog_messages_removed ,
251+ ) = await chatgpt_instance .send_vision_message (
252+ message ,
253+ dialog_messages = dialog_messages ,
254+ image_buffer = buf ,
255+ chat_mode = chat_mode ,
256+ )
257+
258+ async def fake_gen ():
259+ yield "finished" , answer , (
260+ n_input_tokens ,
261+ n_output_tokens ,
262+ ), n_first_dialog_messages_removed
263+
264+ gen = fake_gen ()
265+
266+ prev_answer = ""
267+ async for gen_item in gen :
268+ (
269+ status ,
270+ answer ,
271+ (n_input_tokens , n_output_tokens ),
272+ n_first_dialog_messages_removed ,
273+ ) = gen_item
274+ answer = current_model + " " + answer
275+ answer = answer [:4096 ] # telegram message limit
276+
277+ # update only when 100 new symbols are ready
278+ if abs (len (answer ) - len (prev_answer )) < 100 and status != "finished" :
279+ continue
280+
281+ try :
282+ await context .bot .edit_message_text (
283+ answer ,
284+ chat_id = placeholder_message .chat_id ,
285+ message_id = placeholder_message .message_id ,
286+ parse_mode = parse_mode ,
287+ )
288+ except telegram .error .BadRequest as e :
289+ if str (e ).startswith ("Message is not modified" ):
290+ continue
291+ else :
292+ await context .bot .edit_message_text (
293+ answer ,
294+ chat_id = placeholder_message .chat_id ,
295+ message_id = placeholder_message .message_id ,
296+ )
297+
298+ await asyncio .sleep (0.01 ) # wait a bit to avoid flooding
299+
300+ prev_answer = answer
301+
302+ # update user data
303+ if buf is not None :
304+ base_image = base64 .b64encode (buf .getvalue ()).decode ("utf-8" )
305+ new_dialog_message = {"user" : [
306+ {
307+ "type" : "text" ,
308+ "text" : message ,
309+ },
310+ {
311+ "type" : "image" ,
312+ "image" : base_image ,
313+ }
314+ ]
315+ , "bot" : answer , "date" : datetime .now ()}
316+ else :
317+ new_dialog_message = {"user" : [{"type" : "text" , "text" : message }], "bot" : answer , "date" : datetime .now ()}
318+
319+ db .set_dialog_messages (
320+ user_id ,
321+ db .get_dialog_messages (user_id , dialog_id = None ) + [new_dialog_message ],
322+ dialog_id = None
323+ )
324+
325+ db .update_n_used_tokens (user_id , current_model , n_input_tokens , n_output_tokens )
326+
327+ except asyncio .CancelledError :
328+ # note: intermediate token updates only work when enable_message_streaming=True (config.yml)
329+ db .update_n_used_tokens (user_id , current_model , n_input_tokens , n_output_tokens )
330+ raise
331+
332+ except Exception as e :
333+ error_text = f"Something went wrong during completion. Reason: { e } "
334+ logger .error (error_text )
335+ await update .message .reply_text (error_text )
336+ return
337+
338+ async def unsupport_message_handle (update : Update , context : CallbackContext , message = None ):
339+ error_text = f"I don't know how to read files or videos. Send the picture in normal mode (Quick Mode)."
340+ logger .error (error_text )
341+ await update .message .reply_text (error_text )
342+ return
180343
181344async def message_handle (update : Update , context : CallbackContext , message = None , use_new_dialog_timeout = True ):
182345 # check if bot was mentioned (for group chats)
@@ -204,6 +367,8 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
204367 await generate_image_handle (update , context , message = message )
205368 return
206369
370+ current_model = db .get_user_attribute (user_id , "current_model" )
371+
207372 async def message_handle_fn ():
208373 # new dialog timeout
209374 if use_new_dialog_timeout :
@@ -214,7 +379,6 @@ async def message_handle_fn():
214379
215380 # in case of CancelledError
216381 n_input_tokens , n_output_tokens = 0 , 0
217- current_model = db .get_user_attribute (user_id , "current_model" )
218382
219383 try :
220384 # send placeholder message to user
@@ -249,11 +413,12 @@ async def fake_gen():
249413 gen = fake_gen ()
250414
251415 prev_answer = ""
416+
252417 async for gen_item in gen :
253418 status , answer , (n_input_tokens , n_output_tokens ), n_first_dialog_messages_removed = gen_item
254-
419+ answer = current_model + " " + answer
255420 answer = answer [:4096 ] # telegram message limit
256-
421+
257422 # update only when 100 new symbols are ready
258423 if abs (len (answer ) - len (prev_answer )) < 100 and status != "finished" :
259424 continue
@@ -267,11 +432,12 @@ async def fake_gen():
267432 await context .bot .edit_message_text (answer , chat_id = placeholder_message .chat_id , message_id = placeholder_message .message_id )
268433
269434 await asyncio .sleep (0.01 ) # wait a bit to avoid flooding
270-
435+
271436 prev_answer = answer
272-
437+
273438 # update user data
274439 new_dialog_message = {"user" : _message , "bot" : answer , "date" : datetime .now ()}
440+
275441 db .set_dialog_messages (
276442 user_id ,
277443 db .get_dialog_messages (user_id , dialog_id = None ) + [new_dialog_message ],
@@ -300,7 +466,19 @@ async def fake_gen():
300466 await update .message .reply_text (text , parse_mode = ParseMode .HTML )
301467
302468 async with user_semaphores [user_id ]:
303- task = asyncio .create_task (message_handle_fn ())
469+ if current_model == "gpt-4-vision-preview" or update .message .photo is not None and len (update .message .photo ) > 0 :
470+ logger .error ('gpt-4-vision-preview' )
471+ if current_model != "gpt-4-vision-preview" :
472+ current_model = "gpt-4-vision-preview"
473+ db .set_user_attribute (user_id , "current_model" , "gpt-4-vision-preview" )
474+ task = asyncio .create_task (
475+ _vision_message_handle_fn (update , context , use_new_dialog_timeout = use_new_dialog_timeout )
476+ )
477+ else :
478+ task = asyncio .create_task (
479+ message_handle_fn ()
480+ )
481+
304482 user_tasks [user_id ] = task
305483
306484 try :
@@ -392,6 +570,7 @@ async def new_dialog_handle(update: Update, context: CallbackContext):
392570
393571 user_id = update .message .from_user .id
394572 db .set_user_attribute (user_id , "last_interaction" , datetime .now ())
573+ db .set_user_attribute (user_id , "current_model" , "gpt-3.5-turbo" )
395574
396575 db .start_new_dialog (user_id )
397576 await update .message .reply_text ("Starting new dialog ✅" )
@@ -672,6 +851,9 @@ def run_bot() -> None:
672851 application .add_handler (CommandHandler ("help_group_chat" , help_group_chat_handle , filters = user_filter ))
673852
674853 application .add_handler (MessageHandler (filters .TEXT & ~ filters .COMMAND & user_filter , message_handle ))
854+ application .add_handler (MessageHandler (filters .PHOTO & ~ filters .COMMAND & user_filter , message_handle ))
855+ application .add_handler (MessageHandler (filters .VIDEO & ~ filters .COMMAND & user_filter , unsupport_message_handle ))
856+ application .add_handler (MessageHandler (filters .Document .ALL & ~ filters .COMMAND & user_filter , unsupport_message_handle ))
675857 application .add_handler (CommandHandler ("retry" , retry_handle , filters = user_filter ))
676858 application .add_handler (CommandHandler ("new" , new_dialog_handle , filters = user_filter ))
677859 application .add_handler (CommandHandler ("cancel" , cancel_handle , filters = user_filter ))
@@ -694,4 +876,4 @@ def run_bot() -> None:
694876
695877
696878if __name__ == "__main__" :
697- run_bot ()
879+ run_bot ()
0 commit comments