feat: Support chatting with chatGPT web

This commit is contained in:
hibobmaster 2023-05-30 09:59:25 +08:00
parent 600c5e161a
commit c72e6d6f8e
No known key found for this signature in database
8 changed files with 633 additions and 312 deletions

View File

@ -15,4 +15,6 @@ OUTPUT_FOUR_IMAGES="true" # Optional
IMPORT_KEYS_PATH="element-keys.txt" # Optional
IMPORT_KEYS_PASSWORD="xxxxxxx" # Optional
FLOWISE_API_URL="http://localhost:3000/api/v1/prediction/xxxx" # Optional
FLOWISE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxx" # Optional
FLOWISE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxx" # Optional
PANDORA_API_ENDPOINT="http://pandora:8008" # Optional
PANDORA_API_MODEL="text-davinci-002-render-sha-mobile" # Optional

View File

@ -1,3 +0,0 @@
{
"python.analysis.typeCheckingMode": "off"
}

View File

@ -1,16 +1,18 @@
## Introduction
This is a simple Matrix bot that uses OpenAI's GPT API and Bing AI and Google Bard to generate responses to user inputs. The bot responds to six types of prompts: `!gpt`, `!chat` and `!bing` and `!pic` and `!bard` and `!lc` depending on the first word of the prompt.
This is a simple Matrix bot that uses OpenAI's GPT API and Bing AI and Google Bard to generate responses to user inputs. The bot responds to these commands: `!gpt`, `!chat` and `!bing` and `!pic` and `!bard` and `!talk`, `!goon`, `!new` and `!lc` and `!help` depending on the first word of the prompt.
![Bing](https://user-images.githubusercontent.com/32976627/231073146-3e380217-a6a2-413d-9203-ab36965b909d.png)
![image](https://user-images.githubusercontent.com/32976627/232036790-e830145c-914e-40be-b3e6-c02cba93329c.png)
![ChatGPT](https://i.imgur.com/kK4rnPf.jpeg)
## Feature
1. Support Openai ChatGPT and Bing AI and Google Bard and Langchain([Flowise](https://github.com/FlowiseAI/Flowise))
1. Support Openai ChatGPT and Bing AI and Google Bard
2. Support Bing Image Creator
3. Support E2E Encrypted Room
4. Colorful code blocks
5. Langchain([Flowise](https://github.com/FlowiseAI/Flowise))
6. ChatGPT Web ([pandora](https://github.com/pengzhile/pandora) with Session isolation support)
## Installation and Setup
@ -109,6 +111,12 @@ To interact with the bot, simply send a message to the bot in the Matrix room wi
!pic A bridal bouquet made of succulents
```
The following commands need pandora http api:
https://github.com/pengzhile/pandora/blob/master/doc/wiki_en.md#http-restful-api
- `!talk + [prompt]` Chat using chatGPT web with context conversation
- `!goon` Ask chatGPT to complete the missing part from previous conversation
- `!new` Start a new converstaion
## Bing AI and Image Generation

695
bot.py
View File

@ -4,13 +4,27 @@ import re
import sys
import traceback
from typing import Union, Optional
import uuid
import aiohttp
from nio import (AsyncClient, AsyncClientConfig, InviteMemberEvent, JoinError,
KeyVerificationCancel, KeyVerificationEvent, EncryptionError,
KeyVerificationKey, KeyVerificationMac, KeyVerificationStart,
LocalProtocolError, LoginResponse, MatrixRoom, MegolmEvent,
RoomMessageText, ToDeviceError)
from nio import (
AsyncClient,
AsyncClientConfig,
InviteMemberEvent,
JoinError,
KeyVerificationCancel,
KeyVerificationEvent,
EncryptionError,
KeyVerificationKey,
KeyVerificationMac,
KeyVerificationStart,
LocalProtocolError,
LoginResponse,
MatrixRoom,
MegolmEvent,
RoomMessageText,
ToDeviceError,
)
from nio.store.database import SqliteStore
from askgpt import askGPT
@ -22,6 +36,7 @@ from send_message import send_room_message
from v3 import Chatbot
from bard import Bardbot
from flowise import flowise_query
from pandora import Pandora
logger = getlogger()
@ -32,8 +47,8 @@ class Bot:
homeserver: str,
user_id: str,
device_id: str,
chatgpt_api_endpoint: str = os.environ.get(
"CHATGPT_API_ENDPOINT") or "https://api.openai.com/v1/chat/completions",
chatgpt_api_endpoint: str = os.environ.get("CHATGPT_API_ENDPOINT")
or "https://api.openai.com/v1/chat/completions",
api_key: Union[str, None] = None,
room_id: Union[str, None] = None,
bing_api_endpoint: Union[str, None] = None,
@ -41,20 +56,21 @@ class Bot:
access_token: Union[str, None] = None,
bard_token: Union[str, None] = None,
jailbreakEnabled: Union[bool, None] = True,
bing_auth_cookie: Union[str, None] = '',
bing_auth_cookie: Union[str, None] = "",
markdown_formatted: Union[bool, None] = False,
output_four_images: Union[bool, None] = False,
import_keys_path: Optional[str] = None,
import_keys_password: Optional[str] = None,
flowise_api_url: Optional[str] = None,
flowise_api_key: Optional[str] = None
flowise_api_key: Optional[str] = None,
pandora_api_endpoint: Optional[str] = None,
pandora_api_model: Optional[str] = None,
):
if (homeserver is None or user_id is None
or device_id is None):
if homeserver is None or user_id is None or device_id is None:
logger.warning("homeserver && user_id && device_id is required")
sys.exit(1)
if (password is None and access_token is None):
if password is None and access_token is None:
logger.warning("password or access_toekn is required")
sys.exit(1)
@ -75,7 +91,7 @@ class Bot:
self.session = aiohttp.ClientSession()
if bing_api_endpoint is None:
self.bing_api_endpoint = ''
self.bing_api_endpoint = ""
else:
self.bing_api_endpoint = bing_api_endpoint
@ -85,7 +101,7 @@ class Bot:
self.jailbreakEnabled = jailbreakEnabled
if bing_auth_cookie is None:
self.bing_auth_cookie = ''
self.bing_auth_cookie = ""
else:
self.bing_auth_cookie = bing_auth_cookie
@ -101,27 +117,30 @@ class Bot:
# initialize AsyncClient object
self.store_path = os.getcwd()
self.config = AsyncClientConfig(store=SqliteStore,
store_name="db",
store_sync_tokens=True,
encryption_enabled=True,
)
self.client = AsyncClient(homeserver=self.homeserver, user=self.user_id,
device_id=self.device_id,
config=self.config, store_path=self.store_path,)
self.config = AsyncClientConfig(
store=SqliteStore,
store_name="db",
store_sync_tokens=True,
encryption_enabled=True,
)
self.client = AsyncClient(
homeserver=self.homeserver,
user=self.user_id,
device_id=self.device_id,
config=self.config,
store_path=self.store_path,
)
if self.access_token is not None:
self.client.access_token = self.access_token
# setup event callbacks
self.client.add_event_callback(
self.message_callback, (RoomMessageText, ))
self.client.add_event_callback(
self.decryption_failure, (MegolmEvent, ))
self.client.add_event_callback(
self.invite_callback, (InviteMemberEvent, ))
self.client.add_event_callback(self.message_callback, (RoomMessageText,))
self.client.add_event_callback(self.decryption_failure, (MegolmEvent,))
self.client.add_event_callback(self.invite_callback, (InviteMemberEvent,))
self.client.add_to_device_callback(
self.to_device_callback, (KeyVerificationEvent, ))
self.to_device_callback, (KeyVerificationEvent,)
)
# regular expression to match keyword commands
self.gpt_prog = re.compile(r"^\s*!gpt\s*(.+)$")
@ -131,6 +150,9 @@ class Bot:
self.pic_prog = re.compile(r"^\s*!pic\s*(.+)$")
self.lc_prog = re.compile(r"^\s*!lc\s*(.+)$")
self.help_prog = re.compile(r"^\s*!help\s*.*$")
self.talk_prog = re.compile(r"^\s*!talk\s*(.+)$")
self.goon_prog = re.compile(r"^\s*!goon\s*.*$")
self.new_prog = re.compile(r"^\s*!new\s*.*$")
# initialize chatbot and chatgpt_api_endpoint
if self.api_key is not None:
@ -147,18 +169,32 @@ class Bot:
self.askgpt = askGPT(self.session)
# initialize bingbot
if self.bing_api_endpoint != '':
if self.bing_api_endpoint != "":
self.bingbot = BingBot(
self.session, bing_api_endpoint, jailbreakEnabled=self.jailbreakEnabled)
self.session, bing_api_endpoint, jailbreakEnabled=self.jailbreakEnabled
)
# initialize BingImageGenAsync
if self.bing_auth_cookie != '':
if self.bing_auth_cookie != "":
self.imageGen = ImageGenAsync(self.bing_auth_cookie, quiet=True)
# initialize Bardbot
if bard_token is not None:
self.bardbot = Bardbot(self.bard_token)
# initialize pandora
if pandora_api_endpoint is not None:
self.pandora_api_endpoint = pandora_api_endpoint
self.pandora = Pandora(
api_endpoint=pandora_api_endpoint, clientSession=self.session
)
if pandora_api_model is None:
self.pandora_api_model = "text-davinci-002-render-sha-mobile"
else:
self.pandora_api_model = pandora_api_model
self.pandora_data = {}
def __del__(self):
try:
loop = asyncio.get_running_loop()
@ -170,6 +206,13 @@ class Bot:
async def _close(self):
await self.session.close()
def pandora_init(self, sender_id: str) -> None:
self.pandora_data[sender_id] = {
"conversation_id": None,
"parent_message_id": str(uuid.uuid4()),
"first_time": True,
}
# message_callback RoomMessageText event
async def message_callback(self, room: MatrixRoom, event: RoomMessageText) -> None:
if self.room_id is None:
@ -186,6 +229,9 @@ class Bot:
# sender_id
sender_id = event.sender
if sender_id not in self.pandora_data:
self.pandora_init(sender_id)
# user_message
raw_user_message = event.body
@ -206,59 +252,67 @@ class Bot:
prompt = n.group(1)
if self.api_key is not None:
try:
asyncio.create_task(self.chat(room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message
)
)
asyncio.create_task(
self.chat(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id,
reply_message=str(e))
await send_room_message(
self.client, room_id, reply_message=str(e)
)
else:
logger.warning("No API_KEY provided")
await send_room_message(self.client, room_id,
reply_message="API_KEY not provided")
await send_room_message(
self.client, room_id, reply_message="API_KEY not provided"
)
m = self.gpt_prog.match(content_body)
if m:
prompt = m.group(1)
try:
asyncio.create_task(self.gpt(
room_id,
reply_to_event_id,
prompt, sender_id,
raw_user_message
)
asyncio.create_task(
self.gpt(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id, reply_message=str(e))
# bing ai
if self.bing_api_endpoint != '':
if self.bing_api_endpoint != "":
b = self.bing_prog.match(content_body)
if b:
prompt = b.group(1)
# raw_content_body used for construct formatted_body
try:
asyncio.create_task(self.bing(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message
)
asyncio.create_task(
self.bing(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id,
reply_message=str(e))
await send_room_message(
self.client, room_id, reply_message=str(e)
)
# Image Generation by Microsoft Bing
if self.bing_auth_cookie != '':
if self.bing_auth_cookie != "":
i = self.pic_prog.match(content_body)
if i:
prompt = i.group(1)
@ -266,8 +320,9 @@ class Bot:
asyncio.create_task(self.pic(room_id, prompt))
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id,
reply_message=str(e))
await send_room_message(
self.client, room_id, reply_message=str(e)
)
# Google's Bard
if self.bard_token is not None:
@ -275,13 +330,14 @@ class Bot:
if b:
prompt = b.group(1)
try:
asyncio.create_task(self.bard(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message
)
asyncio.create_task(
self.bard(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
@ -293,13 +349,63 @@ class Bot:
if m:
prompt = m.group(1)
try:
asyncio.create_task(self.lc(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message
asyncio.create_task(
self.lc(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id, reply_message={e})
# pandora
if self.pandora_api_endpoint is not None:
t = self.talk_prog.match(content_body)
if t:
prompt = t.group(1)
try:
asyncio.create_task(
self.talk(
room_id,
reply_to_event_id,
prompt,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id, reply_message={e})
g = self.goon_prog.match(content_body)
if g:
try:
asyncio.create_task(
self.goon(
room_id,
reply_to_event_id,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id, reply_message={e})
n = self.new_prog.match(content_body)
if n:
try:
asyncio.create_task(
self.new(
room_id,
reply_to_event_id,
sender_id,
raw_user_message,
)
)
except Exception as e:
logger.error(e, exc_info=True)
@ -317,8 +423,8 @@ class Bot:
logger.error(
f"Failed to decrypt message: {event.event_id} \
from {event.sender} in {room.room_id}\n" +
"Please make sure the bot current session is verified"
from {event.sender} in {room.room_id}\n"
+ "Please make sure the bot current session is verified"
)
# invite_callback event
@ -334,7 +440,8 @@ class Bot:
if type(result) == JoinError:
logger.error(
f"Error joining room {room.room_id} (attempt %d): %s",
attempt, result.message,
attempt,
result.message,
)
else:
break
@ -356,11 +463,11 @@ class Bot:
try:
client = self.client
logger.debug(
f"Device Event of type {type(event)} received in "
"to_device_cb().")
f"Device Event of type {type(event)} received in " "to_device_cb()."
)
if isinstance(event, KeyVerificationStart): # first step
""" first step: receive KeyVerificationStart
"""first step: receive KeyVerificationStart
KeyVerificationStart(
source={'content':
{'method': 'm.sas.v1',
@ -390,12 +497,13 @@ class Bot:
"""
if "emoji" not in event.short_authentication_string:
estr = ("Other device does not support emoji verification "
f"{event.short_authentication_string}. Aborting.")
estr = (
"Other device does not support emoji verification "
f"{event.short_authentication_string}. Aborting."
)
logger.info(estr)
return
resp = await client.accept_key_verification(
event.transaction_id)
resp = await client.accept_key_verification(event.transaction_id)
if isinstance(resp, ToDeviceError):
estr = f"accept_key_verification() failed with {resp}"
logger.info(estr)
@ -409,7 +517,7 @@ class Bot:
logger.info(estr)
elif isinstance(event, KeyVerificationCancel): # anytime
""" at any time: receive KeyVerificationCancel
"""at any time: receive KeyVerificationCancel
KeyVerificationCancel(source={
'content': {'code': 'm.mismatched_sas',
'reason': 'Mismatched authentication string',
@ -426,12 +534,14 @@ class Bot:
# client.cancel_key_verification(tx_id, reject=False)
# here. The SAS flow is already cancelled.
# We only need to inform the user.
estr = (f"Verification has been cancelled by {event.sender} "
f"for reason \"{event.reason}\".")
estr = (
f"Verification has been cancelled by {event.sender} "
f'for reason "{event.reason}".'
)
logger.info(estr)
elif isinstance(event, KeyVerificationKey): # second step
""" Second step is to receive KeyVerificationKey
"""Second step is to receive KeyVerificationKey
KeyVerificationKey(
source={'content': {
'key': 'SomeCryptoKey',
@ -456,36 +566,38 @@ class Bot:
# automatic match, so we use y
yn = "y"
if yn.lower() == "y":
estr = ("Match! The verification for this "
"device will be accepted.")
estr = (
"Match! The verification for this " "device will be accepted."
)
logger.info(estr)
resp = await client.confirm_short_auth_string(
event.transaction_id)
resp = await client.confirm_short_auth_string(event.transaction_id)
if isinstance(resp, ToDeviceError):
estr = ("confirm_short_auth_string() "
f"failed with {resp}")
estr = "confirm_short_auth_string() " f"failed with {resp}"
logger.info(estr)
elif yn.lower() == "n": # no, don't match, reject
estr = ("No match! Device will NOT be verified "
"by rejecting verification.")
estr = (
"No match! Device will NOT be verified "
"by rejecting verification."
)
logger.info(estr)
resp = await client.cancel_key_verification(
event.transaction_id, reject=True)
event.transaction_id, reject=True
)
if isinstance(resp, ToDeviceError):
estr = (f"cancel_key_verification failed with {resp}")
estr = f"cancel_key_verification failed with {resp}"
logger.info(estr)
else: # C or anything for cancel
estr = ("Cancelled by user! Verification will be "
"cancelled.")
estr = "Cancelled by user! Verification will be " "cancelled."
logger.info(estr)
resp = await client.cancel_key_verification(
event.transaction_id, reject=False)
event.transaction_id, reject=False
)
if isinstance(resp, ToDeviceError):
estr = (f"cancel_key_verification failed with {resp}")
estr = f"cancel_key_verification failed with {resp}"
logger.info(estr)
elif isinstance(event, KeyVerificationMac): # third step
""" Third step is to receive KeyVerificationMac
"""Third step is to receive KeyVerificationMac
KeyVerificationMac(
source={'content': {
'mac': {'ed25519:DEVICEIDXY': 'SomeKey1',
@ -505,185 +617,265 @@ class Bot:
todevice_msg = sas.get_mac()
except LocalProtocolError as e:
# e.g. it might have been cancelled by ourselves
estr = (f"Cancelled or protocol error: Reason: {e}.\n"
f"Verification with {event.sender} not concluded. "
"Try again?")
estr = (
f"Cancelled or protocol error: Reason: {e}.\n"
f"Verification with {event.sender} not concluded. "
"Try again?"
)
logger.info(estr)
else:
resp = await client.to_device(todevice_msg)
if isinstance(resp, ToDeviceError):
estr = f"to_device failed with {resp}"
logger.info(estr)
estr = (f"sas.we_started_it = {sas.we_started_it}\n"
f"sas.sas_accepted = {sas.sas_accepted}\n"
f"sas.canceled = {sas.canceled}\n"
f"sas.timed_out = {sas.timed_out}\n"
f"sas.verified = {sas.verified}\n"
f"sas.verified_devices = {sas.verified_devices}\n")
estr = (
f"sas.we_started_it = {sas.we_started_it}\n"
f"sas.sas_accepted = {sas.sas_accepted}\n"
f"sas.canceled = {sas.canceled}\n"
f"sas.timed_out = {sas.timed_out}\n"
f"sas.verified = {sas.verified}\n"
f"sas.verified_devices = {sas.verified_devices}\n"
)
logger.info(estr)
estr = ("Emoji verification was successful!\n"
"Initiate another Emoji verification from "
"another device or room if desired. "
"Or if done verifying, hit Control-C to stop the "
"bot in order to restart it as a service or to "
"run it in the background.")
estr = (
"Emoji verification was successful!\n"
"Initiate another Emoji verification from "
"another device or room if desired. "
"Or if done verifying, hit Control-C to stop the "
"bot in order to restart it as a service or to "
"run it in the background."
)
logger.info(estr)
else:
estr = (f"Received unexpected event type {type(event)}. "
f"Event is {event}. Event will be ignored.")
estr = (
f"Received unexpected event type {type(event)}. "
f"Event is {event}. Event will be ignored."
)
logger.info(estr)
except BaseException:
estr = traceback.format_exc()
logger.info(estr)
# !chat command
async def chat(self, room_id, reply_to_event_id, prompt, sender_id,
raw_user_message):
async def chat(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
):
await self.client.room_typing(room_id, timeout=120000)
try:
text = await self.chatbot.ask_async(prompt)
except Exception as e:
raise Exception(e)
try:
text = text.strip()
await send_room_message(self.client, room_id, reply_message=text,
reply_to_event_id="", sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted)
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
text = await self.chatbot.ask_async(prompt)
text = text.strip()
await send_room_message(
self.client,
room_id,
reply_message=text,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !gpt command
async def gpt(self, room_id, reply_to_event_id, prompt, sender_id,
raw_user_message) -> None:
try:
# sending typing state
await self.client.room_typing(room_id, timeout=240000)
# timeout 240s
text = await asyncio.wait_for(self.askgpt.oneTimeAsk(prompt,
self.chatgpt_api_endpoint,
self.headers),
timeout=240)
except TimeoutError:
logger.error("TimeoutException", exc_info=True)
raise Exception("Timeout error")
except Exception as e:
raise Exception(e)
async def gpt(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
) -> None:
# sending typing state
await self.client.room_typing(room_id, timeout=240000)
# timeout 240s
text = await asyncio.wait_for(
self.askgpt.oneTimeAsk(prompt, self.chatgpt_api_endpoint, self.headers),
timeout=240,
)
try:
text = text.strip()
await send_room_message(self.client, room_id, reply_message=text,
reply_to_event_id="", sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted)
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
text = text.strip()
await send_room_message(
self.client,
room_id,
reply_message=text,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !bing command
async def bing(self, room_id, reply_to_event_id, prompt, sender_id,
raw_user_message) -> None:
try:
# sending typing state
await self.client.room_typing(room_id, timeout=180000)
# timeout 240s
text = await asyncio.wait_for(self.bingbot.ask_bing(prompt), timeout=240)
except TimeoutError:
logger.error("timeoutException", exc_info=True)
raise Exception("Timeout error")
except Exception as e:
raise Exception(e)
async def bing(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
) -> None:
# sending typing state
await self.client.room_typing(room_id, timeout=180000)
# timeout 240s
text = await asyncio.wait_for(self.bingbot.ask_bing(prompt), timeout=240)
try:
text = text.strip()
await send_room_message(self.client, room_id, reply_message=text,
reply_to_event_id="", sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted)
except Exception as e:
logger.error(e, exc_info=True)
text = text.strip()
await send_room_message(
self.client,
room_id,
reply_message=text,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !bard command
async def bard(self, room_id, reply_to_event_id, prompt, sender_id,
raw_user_message) -> None:
try:
# sending typing state
await self.client.room_typing(room_id)
response = await asyncio.to_thread(self.bardbot.ask, prompt)
except Exception as e:
raise Exception(e)
async def bard(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
) -> None:
# sending typing state
await self.client.room_typing(room_id)
response = await asyncio.to_thread(self.bardbot.ask, prompt)
try:
content = str(response['content']).strip()
await send_room_message(self.client, room_id, reply_message=content,
reply_to_event_id="", sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted)
except Exception as e:
logger.error(e, exc_info=True)
content = str(response["content"]).strip()
await send_room_message(
self.client,
room_id,
reply_message=content,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !lc command
async def lc(self, room_id, reply_to_event_id, prompt, sender_id,
raw_user_message) -> None:
try:
# sending typing state
await self.client.room_typing(room_id)
if self.flowise_api_key is not None:
headers = {'Authorization': f'Bearer {self.flowise_api_key}'}
response = await asyncio.to_thread(flowise_query,
self.flowise_api_url, prompt, headers)
else:
response = await asyncio.to_thread(flowise_query,
self.flowise_api_url, prompt)
await send_room_message(self.client, room_id, reply_message=response,
reply_to_event_id="", sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted)
except Exception as e:
raise Exception(e)
async def lc(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
) -> None:
# sending typing state
await self.client.room_typing(room_id)
if self.flowise_api_key is not None:
headers = {"Authorization": f"Bearer {self.flowise_api_key}"}
response = await asyncio.to_thread(
flowise_query, self.flowise_api_url, prompt, headers
)
else:
response = await asyncio.to_thread(
flowise_query, self.flowise_api_url, prompt
)
await send_room_message(
self.client,
room_id,
reply_message=response,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !talk command
async def talk(
self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message
) -> None:
if self.pandora_data[sender_id]["conversation_id"] is not None:
data = {
"prompt": prompt,
"model": self.pandora_api_model,
"parent_message_id": self.pandora_data[sender_id]["parent_message_id"],
"conversation_id": self.pandora_data[sender_id]["conversation_id"],
"stream": False,
}
else:
data = {
"prompt": prompt,
"model": self.pandora_api_model,
"parent_message_id": self.pandora_data[sender_id]["parent_message_id"],
"stream": False,
}
# sending typing state
await self.client.room_typing(room_id)
response = await self.pandora.talk(data)
self.pandora_data[sender_id]["conversation_id"] = response["conversation_id"]
self.pandora_data[sender_id]["parent_message_id"] = response["message"]["id"]
content = response["message"]["content"]["parts"][0]
if self.pandora_data[sender_id]["first_time"]:
self.pandora_data[sender_id]["first_time"] = False
data = {
"model": self.pandora_api_model,
"message_id": self.pandora_data[sender_id]["parent_message_id"],
}
await self.pandora.gen_title(
data, self.pandora_data[sender_id]["conversation_id"]
)
await send_room_message(
self.client,
room_id,
reply_message=content,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !goon command
async def goon(
self, room_id, reply_to_event_id, sender_id, raw_user_message
) -> None:
# sending typing state
await self.client.room_typing(room_id)
data = {
"model": self.pandora_api_model,
"parent_message_id": self.pandora_data[sender_id]["parent_message_id"],
"conversation_id": self.pandora_data[sender_id]["conversation_id"],
"stream": False,
}
response = await self.pandora.goon(data)
self.pandora_data[sender_id]["conversation_id"] = response["conversation_id"]
self.pandora_data[sender_id]["parent_message_id"] = response["message"]["id"]
content = response["message"]["content"]["parts"][0]
await send_room_message(
self.client,
room_id,
reply_message=content,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !new command
async def new(
self, room_id, reply_to_event_id, sender_id, raw_user_message
) -> None:
self.pandora_init(sender_id)
content = "New conversation created, please use !talk to start chatting!"
await send_room_message(
self.client,
room_id,
reply_message=content,
reply_to_event_id="",
sender_id=sender_id,
user_message=raw_user_message,
markdown_formatted=self.markdown_formatted,
)
# !pic command
async def pic(self, room_id, prompt):
try:
await self.client.room_typing(room_id, timeout=180000)
# generate image
try:
links = await self.imageGen.get_images(prompt)
image_path_list = await self.imageGen.save_images(links, "images",
self.output_four_images)
except Exception as e:
logger.error(f"Image Generation error: {e}", exc_info=True)
raise Exception(e)
# send image
try:
for image_path in image_path_list:
await send_room_image(self.client, room_id, image_path)
await self.client.room_typing(room_id, typing_state=False)
except Exception as e:
logger.error(e, exc_info=True)
except Exception as e:
logger.error(e, exc_info=True)
await self.client.room_typing(room_id, timeout=180000)
# generate image
links = await self.imageGen.get_images(prompt)
image_path_list = await self.imageGen.save_images(
links, "images", self.output_four_images
)
# send image
for image_path in image_path_list:
await send_room_image(self.client, room_id, image_path)
await self.client.room_typing(room_id, typing_state=False)
# !help command
async def help(self, room_id):
try:
# sending typing state
await self.client.room_typing(room_id)
help_info = "!gpt [prompt], generate response without context conversation\n" + \
"!chat [prompt], chat with context conversation\n" + \
"!bing [prompt], chat with context conversation powered by Bing AI\n" + \
"!bard [prompt], chat with Google's Bard\n" + \
"!pic [prompt], Image generation by Microsoft Bing\n" + \
"!lc [prompt], chat using langchain api\n" + \
"!help, help message" # noqa: E501
help_info = (
"!gpt [prompt], generate response without context conversation\n"
+ "!chat [prompt], chat with context conversation\n"
+ "!bing [prompt], chat with context conversation powered by Bing AI\n"
+ "!bard [prompt], chat with Google's Bard\n"
+ "!pic [prompt], Image generation by Microsoft Bing\n"
+ "!talk [content], talk using chatgpt web (pandora)\n"
+ "!goon, continue the incomplete conversation (pandora)\n"
+ "!new, start a new conversation (pandora)\n"
+ "!lc [prompt], chat using langchain api\n"
+ "!help, help message"
) # noqa: E501
await send_room_message(self.client, room_id, reply_message=help_info)
except Exception as e:
logger.error(e, exc_info=True)
await send_room_message(self.client, room_id, reply_message=help_info)
# bot login
async def login(self) -> None:
@ -702,16 +894,15 @@ class Bot:
# import keys
async def import_keys(self):
resp = await self.client.import_keys(
self.import_keys_path,
self.import_keys_password
self.import_keys_path, self.import_keys_password
)
if isinstance(resp, EncryptionError):
logger.error(f"import_keys failed with {resp}")
else:
logger.info(
"import_keys success, please remove import_keys configuration!!!")
"import_keys success, please remove import_keys configuration!!!"
)
# sync messages in the room
async def sync_forever(self, timeout=30000, full_state=True) -> None:
await self.client.sync_forever(timeout=timeout, full_state=full_state)

View File

@ -2,7 +2,7 @@ services:
app:
image: hibobmaster/matrixchatgptbot:latest
container_name: matrix_chatgpt_bot
restart: always
restart: unless-stopped
# build:
# context: .
# dockerfile: ./Dockerfile
@ -19,13 +19,23 @@ services:
- matrix_network
# api:
# # bing api
# image: ghcr.io/waylaidwanderer/node-chatgpt-api:latest
# image: hibobmaster/node-chatgpt-api:latest
# container_name: node-chatgpt-api
# restart: always
# restart: unless-stopped
# volumes:
# - ./settings.js:/var/chatgpt-api/settings.js
# networks:
# - matrix_network
# pandora:
# image: pengzhile/pandora
# container_name: pandora
# restart: unless-stopped
# environment:
# - PANDORA_ACCESS_TOKEN="xxxxxxxxxxxxxx"
# - PANDORA_SERVER="0.0.0.0:8008"
# networks:
# - matrix_network
networks:
matrix_network:

View File

@ -15,5 +15,7 @@
"import_keys_path": "element-keys.txt",
"import_keys_password": "xxxxxxxxx",
"flowise_api_url": "http://localhost:3000/api/v1/prediction/6deb3c89-45bf-4ac4-a0b0-b2d5ef249d21",
"flowise_api_key": "U3pe0bbVDWOyoJtsDzFJjRvHKTP3FRjODwuM78exC3A="
"flowise_api_key": "U3pe0bbVDWOyoJtsDzFJjRvHKTP3FRjODwuM78exC3A=",
"pandora_api_endpoint": "http://127.0.0.1:8008",
"pandora_api_model": "text-davinci-002-render-sha-mobile"
}

105
main.py
View File

@ -9,61 +9,66 @@ logger = getlogger()
async def main():
need_import_keys = False
if os.path.exists('config.json'):
fp = open('config.json', 'r', encoding="utf8")
if os.path.exists("config.json"):
fp = open("config.json", "r", encoding="utf8")
config = json.load(fp)
matrix_bot = Bot(homeserver=config.get('homeserver'),
user_id=config.get('user_id'),
password=config.get('password'),
device_id=config.get('device_id'),
room_id=config.get('room_id'),
api_key=config.get('api_key'),
bing_api_endpoint=config.get('bing_api_endpoint'),
access_token=config.get('access_token'),
bard_token=config.get('bard_token'),
jailbreakEnabled=config.get('jailbreakEnabled'),
bing_auth_cookie=config.get('bing_auth_cookie'),
markdown_formatted=config.get('markdown_formatted'),
output_four_images=config.get('output_four_images'),
import_keys_path=config.get('import_keys_path'),
import_keys_password=config.get(
'import_keys_password'),
flowise_api_url=config.get('flowise_api_url'),
flowise_api_key=config.get('flowise_api_key'),
)
if config.get('import_keys_path') and \
config.get('import_keys_password') is not None:
matrix_bot = Bot(
homeserver=config.get("homeserver"),
user_id=config.get("user_id"),
password=config.get("password"),
device_id=config.get("device_id"),
room_id=config.get("room_id"),
api_key=config.get("api_key"),
bing_api_endpoint=config.get("bing_api_endpoint"),
access_token=config.get("access_token"),
bard_token=config.get("bard_token"),
jailbreakEnabled=config.get("jailbreakEnabled"),
bing_auth_cookie=config.get("bing_auth_cookie"),
markdown_formatted=config.get("markdown_formatted"),
output_four_images=config.get("output_four_images"),
import_keys_path=config.get("import_keys_path"),
import_keys_password=config.get("import_keys_password"),
flowise_api_url=config.get("flowise_api_url"),
flowise_api_key=config.get("flowise_api_key"),
pandora_api_endpoint=config.get("pandora_api_endpoint"),
pandora_api_model=config.get("pandora_api_model"),
)
if (
config.get("import_keys_path")
and config.get("import_keys_password") is not None
):
need_import_keys = True
else:
matrix_bot = Bot(homeserver=os.environ.get('HOMESERVER'),
user_id=os.environ.get('USER_ID'),
password=os.environ.get('PASSWORD'),
device_id=os.environ.get("DEVICE_ID"),
room_id=os.environ.get("ROOM_ID"),
api_key=os.environ.get("OPENAI_API_KEY"),
bing_api_endpoint=os.environ.get("BING_API_ENDPOINT"),
access_token=os.environ.get("ACCESS_TOKEN"),
bard_token=os.environ.get("BARD_TOKEN"),
jailbreakEnabled=os.environ.get(
"JAILBREAKENABLED", "false").lower() \
in ('true', '1', 't'),
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
markdown_formatted=os.environ.get(
"MARKDOWN_FORMATTED", "false").lower() \
in ('true', '1', 't'),
output_four_images=os.environ.get(
"OUTPUT_FOUR_IMAGES", "false").lower() \
in ('true', '1', 't'),
import_keys_path=os.environ.get("IMPORT_KEYS_PATH"),
import_keys_password=os.environ.get(
"IMPORT_KEYS_PASSWORD"),
flowise_api_url=os.environ.get("FLOWISE_API_URL"),
flowise_api_key=os.environ.get("FLOWISE_API_KEY"),
)
if os.environ.get("IMPORT_KEYS_PATH") \
and os.environ.get("IMPORT_KEYS_PASSWORD") is not None:
matrix_bot = Bot(
homeserver=os.environ.get("HOMESERVER"),
user_id=os.environ.get("USER_ID"),
password=os.environ.get("PASSWORD"),
device_id=os.environ.get("DEVICE_ID"),
room_id=os.environ.get("ROOM_ID"),
api_key=os.environ.get("OPENAI_API_KEY"),
bing_api_endpoint=os.environ.get("BING_API_ENDPOINT"),
access_token=os.environ.get("ACCESS_TOKEN"),
bard_token=os.environ.get("BARD_TOKEN"),
jailbreakEnabled=os.environ.get("JAILBREAKENABLED", "false").lower()
in ("true", "1", "t"),
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
markdown_formatted=os.environ.get("MARKDOWN_FORMATTED", "false").lower()
in ("true", "1", "t"),
output_four_images=os.environ.get("OUTPUT_FOUR_IMAGES", "false").lower()
in ("true", "1", "t"),
import_keys_path=os.environ.get("IMPORT_KEYS_PATH"),
import_keys_password=os.environ.get("IMPORT_KEYS_PASSWORD"),
flowise_api_url=os.environ.get("FLOWISE_API_URL"),
flowise_api_key=os.environ.get("FLOWISE_API_KEY"),
pandora_api_endpoint=os.environ.get("PANDORA_API_ENDPOINT"),
pandora_api_model=os.environ.get("PANDORA_API_MODEL"),
)
if (
os.environ.get("IMPORT_KEYS_PATH")
and os.environ.get("IMPORT_KEYS_PASSWORD") is not None
):
need_import_keys = True
await matrix_bot.login()

106
pandora.py Normal file
View File

@ -0,0 +1,106 @@
# https://github.com/pengzhile/pandora/blob/master/doc/HTTP-API.md
import uuid
import aiohttp
import asyncio
class Pandora:
def __init__(self, api_endpoint: str, clientSession: aiohttp.ClientSession) -> None:
self.api_endpoint = api_endpoint.rstrip("/")
self.session = clientSession
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.session.close()
async def gen_title(self, data: dict, conversation_id: str) -> None:
"""
data = {
"model": "",
"message_id": "",
}
:param data: dict
:param conversation_id: str
:return: None
"""
api_endpoint = (
self.api_endpoint + f"/api/conversation/gen_title/{conversation_id}"
)
async with self.session.post(api_endpoint, json=data) as resp:
return await resp.json()
async def talk(self, data: dict) -> None:
api_endpoint = self.api_endpoint + "/api/conversation/talk"
"""
data = {
"prompt": "",
"model": "",
"parent_message_id": "",
"conversation_id": "", # ignore at the first time
"stream": True,
}
:param data: dict
:return: None
"""
data["message_id"] = str(uuid.uuid4())
async with self.session.post(api_endpoint, json=data) as resp:
return await resp.json()
async def goon(self, data: dict) -> None:
"""
data = {
"model": "",
"parent_message_id": "",
"conversation_id": "",
"stream": True,
}
"""
api_endpoint = self.api_endpoint + "/api/conversation/goon"
async with self.session.post(api_endpoint, json=data) as resp:
return await resp.json()
async def test():
model = "text-davinci-002-render-sha-mobile"
api_endpoint = "http://127.0.0.1:8008"
async with aiohttp.ClientSession() as session:
client = Pandora(api_endpoint, session)
conversation_id = None
parent_message_id = str(uuid.uuid4())
first_time = True
async with client:
while True:
prompt = input("BobMaster: ")
if conversation_id:
data = {
"prompt": prompt,
"model": model,
"parent_message_id": parent_message_id,
"conversation_id": conversation_id,
"stream": False,
}
else:
data = {
"prompt": prompt,
"model": model,
"parent_message_id": parent_message_id,
"stream": False,
}
response = await client.talk(data)
conversation_id = response["conversation_id"]
parent_message_id = response["message"]["id"]
content = response["message"]["content"]["parts"][0]
print("ChatGPT: " + content + "\n")
if first_time:
first_time = False
data = {
"model": model,
"message_id": parent_message_id,
}
response = await client.gen_title(data, conversation_id)
if __name__ == "__main__":
asyncio.run(test())