Support chatting with chatGPT web
This commit is contained in:
parent
5c736cf86d
commit
2e3fb4ae30
6 changed files with 223 additions and 6 deletions
|
@ -4,4 +4,6 @@ USERNAME="@chatgpt"
|
||||||
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
BING_API_ENDPOINT="http://api:3000/conversation"
|
BING_API_ENDPOINT="http://api:3000/conversation"
|
||||||
BARD_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx."
|
BARD_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx."
|
||||||
BING_AUTH_COOKIE="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
BING_AUTH_COOKIE="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
PANDORA_API_ENDPOINT="http://127.0.0.1:8008"
|
||||||
|
PANDORA_API_MODEL="text-davinci-002-render-sha-mobile"
|
19
README.md
19
README.md
|
@ -1,11 +1,12 @@
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This is a simple Mattermost 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 `!help` depending on the first word of the prompt.
|
This is a simple Mattermost 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` and `!goon` and `!new` and `!help` depending on the first word of the prompt.
|
||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
|
|
||||||
1. Support Openai ChatGPT and Bing AI and Google Bard(US only at the moment)
|
1. Support Openai ChatGPT and Bing AI and Google Bard
|
||||||
2. Support Bing Image Creator
|
2. Support Bing Image Creator
|
||||||
|
3. [pandora](https://github.com/pengzhile/pandora)
|
||||||
|
|
||||||
## Installation and Setup
|
## Installation and Setup
|
||||||
|
|
||||||
|
@ -17,6 +18,20 @@ Edit `config.json` or `.env` with proper values
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- `!help` help message
|
||||||
|
- `!gpt + [prompt]` generate a one time response from chatGPT
|
||||||
|
- `!chat + [prompt]` chat using official chatGPT api with context conversation
|
||||||
|
- `!bing + [prompt]` chat with Bing AI with context conversation
|
||||||
|
- `!bard + [prompt]` chat with Google's Bard
|
||||||
|
- `!pic + [prompt]` generate an image from Bing Image Creator
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
![demo1](https://i.imgur.com/XRAQB4B.jpg)
|
![demo1](https://i.imgur.com/XRAQB4B.jpg)
|
||||||
|
|
98
bot.py
98
bot.py
|
@ -11,6 +11,8 @@ from bing import BingBot
|
||||||
from bard import Bardbot
|
from bard import Bardbot
|
||||||
from BingImageGen import ImageGenAsync
|
from BingImageGen import ImageGenAsync
|
||||||
from log import getlogger
|
from log import getlogger
|
||||||
|
from pandora import Pandora
|
||||||
|
import uuid
|
||||||
|
|
||||||
logger = getlogger()
|
logger = getlogger()
|
||||||
|
|
||||||
|
@ -26,6 +28,8 @@ class Bot:
|
||||||
openai_api_key: Optional[str] = None,
|
openai_api_key: Optional[str] = None,
|
||||||
openai_api_endpoint: Optional[str] = None,
|
openai_api_endpoint: Optional[str] = None,
|
||||||
bing_api_endpoint: Optional[str] = None,
|
bing_api_endpoint: Optional[str] = None,
|
||||||
|
pandora_api_endpoint: Optional[str] = None,
|
||||||
|
pandora_api_model: Optional[str] = None,
|
||||||
bard_token: Optional[str] = None,
|
bard_token: Optional[str] = None,
|
||||||
bing_auth_cookie: Optional[str] = None,
|
bing_auth_cookie: Optional[str] = None,
|
||||||
port: int = 443,
|
port: int = 443,
|
||||||
|
@ -112,6 +116,18 @@ class Bot:
|
||||||
"bing_api_endpoint is not provided, !bing command will not work"
|
"bing_api_endpoint is not provided, !bing command will not work"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# initialize pandora
|
||||||
|
if pandora_api_endpoint is not None:
|
||||||
|
self.pandora_api_endpoint = pandora_api_endpoint
|
||||||
|
self.pandora = Pandora(
|
||||||
|
api_endpoint=pandora_api_endpoint
|
||||||
|
)
|
||||||
|
self.pandora_init()
|
||||||
|
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.bard_token = bard_token
|
self.bard_token = bard_token
|
||||||
# initialize bard
|
# initialize bard
|
||||||
if self.bard_token is not None:
|
if self.bard_token is not None:
|
||||||
|
@ -128,24 +144,35 @@ class Bot:
|
||||||
"bing_auth_cookie is not provided, !pic command will not work"
|
"bing_auth_cookie is not provided, !pic command will not work"
|
||||||
)
|
)
|
||||||
|
|
||||||
# regular expression to match keyword [!gpt {prompt}] [!chat {prompt}] [!bing {prompt}] [!pic {prompt}] [!bard {prompt}]
|
# regular expression to match keyword
|
||||||
self.gpt_prog = re.compile(r"^\s*!gpt\s*(.+)$")
|
self.gpt_prog = re.compile(r"^\s*!gpt\s*(.+)$")
|
||||||
self.chat_prog = re.compile(r"^\s*!chat\s*(.+)$")
|
self.chat_prog = re.compile(r"^\s*!chat\s*(.+)$")
|
||||||
self.bing_prog = re.compile(r"^\s*!bing\s*(.+)$")
|
self.bing_prog = re.compile(r"^\s*!bing\s*(.+)$")
|
||||||
self.bard_prog = re.compile(r"^\s*!bard\s*(.+)$")
|
self.bard_prog = re.compile(r"^\s*!bard\s*(.+)$")
|
||||||
self.pic_prog = re.compile(r"^\s*!pic\s*(.+)$")
|
self.pic_prog = re.compile(r"^\s*!pic\s*(.+)$")
|
||||||
self.help_prog = re.compile(r"^\s*!help\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*.*$")
|
||||||
|
|
||||||
# close session
|
# close session
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
self.driver.disconnect()
|
self.driver.disconnect()
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
await self.session.close()
|
await self.session.close()
|
||||||
|
|
||||||
def login(self) -> None:
|
def login(self) -> None:
|
||||||
self.driver.login()
|
self.driver.login()
|
||||||
|
|
||||||
|
def pandora_init(self) -> None:
|
||||||
|
self.conversation_id = None
|
||||||
|
self.parent_message_id = str(uuid.uuid4())
|
||||||
|
self.first_time = True
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
await self.driver.init_websocket(self.websocket_handler)
|
await self.driver.init_websocket(self.websocket_handler)
|
||||||
|
|
||||||
|
@ -162,6 +189,7 @@ class Bot:
|
||||||
channel_id = raw_data_dict["channel_id"]
|
channel_id = raw_data_dict["channel_id"]
|
||||||
sender_name = response["data"]["sender_name"]
|
sender_name = response["data"]["sender_name"]
|
||||||
raw_message = raw_data_dict["message"]
|
raw_message = raw_data_dict["message"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
self.message_callback(
|
self.message_callback(
|
||||||
|
@ -217,6 +245,69 @@ class Bot:
|
||||||
logger.error(e, exc_info=True)
|
logger.error(e, exc_info=True)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
|
if self.pandora_api_endpoint is not None:
|
||||||
|
# !talk command trigger handler
|
||||||
|
if self.talk_prog.match(message):
|
||||||
|
prompt = self.talk_prog.match(message).group(1)
|
||||||
|
try:
|
||||||
|
if self.conversation_id is not None:
|
||||||
|
data = {
|
||||||
|
"prompt": prompt,
|
||||||
|
"model": self.pandora_api_model,
|
||||||
|
"parent_message_id": self.parent_message_id,
|
||||||
|
"conversation_id": self.conversation_id,
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
data = {
|
||||||
|
"prompt": prompt,
|
||||||
|
"model": self.pandora_api_model,
|
||||||
|
"parent_message_id": self.parent_message_id,
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
response = await self.pandora.talk(data)
|
||||||
|
self.conversation_id = response['conversation_id']
|
||||||
|
self.parent_message_id = response['message']['id']
|
||||||
|
content = response['message']['content']['parts'][0]
|
||||||
|
if self.first_time:
|
||||||
|
self.first_time = False
|
||||||
|
data = {
|
||||||
|
"model": self.pandora_api_model,
|
||||||
|
"message_id": self.parent_message_id,
|
||||||
|
}
|
||||||
|
await self.pandora.gen_title(data, self.conversation_id)
|
||||||
|
|
||||||
|
await asyncio.to_thread(
|
||||||
|
self.send_message, channel_id, f"{content}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info=True)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
# !goon command trigger handler
|
||||||
|
if self.goon_prog.match(message) and self.conversation_id is not None:
|
||||||
|
try:
|
||||||
|
data = {
|
||||||
|
"model": self.pandora_api_model,
|
||||||
|
"parent_message_id": self.parent_message_id,
|
||||||
|
"conversation_id": self.conversation_id,
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
response = await self.pandora.goon(data)
|
||||||
|
self.conversation_id = response['conversation_id']
|
||||||
|
self.parent_message_id = response['message']['id']
|
||||||
|
content = response['message']['content']['parts'][0]
|
||||||
|
await asyncio.to_thread(
|
||||||
|
self.send_message, channel_id, f"{content}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info=True)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
# !new command trigger handler
|
||||||
|
if self.new_prog.match(message):
|
||||||
|
self.pandora_init()
|
||||||
|
|
||||||
if self.bard_token is not None:
|
if self.bard_token is not None:
|
||||||
# !bard command trigger handler
|
# !bard command trigger handler
|
||||||
if self.bard_prog.match(message):
|
if self.bard_prog.match(message):
|
||||||
|
@ -265,7 +356,7 @@ class Bot:
|
||||||
self.driver.posts.create_post(
|
self.driver.posts.create_post(
|
||||||
options={
|
options={
|
||||||
"channel_id": channel_id,
|
"channel_id": channel_id,
|
||||||
"message": message,
|
"message": message
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -321,6 +412,9 @@ class Bot:
|
||||||
+ "!bing [content], chat with context conversation powered by Bing AI\n"
|
+ "!bing [content], chat with context conversation powered by Bing AI\n"
|
||||||
+ "!bard [content], chat with Google's Bard\n"
|
+ "!bard [content], chat with Google's Bard\n"
|
||||||
+ "!pic [prompt], Image generation by Microsoft Bing\n"
|
+ "!pic [prompt], Image generation by Microsoft Bing\n"
|
||||||
|
+ "!talk [content], talk using chatgpt web\n"
|
||||||
|
+ "!goon, continue the incomplete conversation\n"
|
||||||
|
+ "!new, start a new conversation\n"
|
||||||
+ "!help, help message"
|
+ "!help, help message"
|
||||||
)
|
)
|
||||||
return help_info
|
return help_info
|
||||||
|
|
|
@ -5,5 +5,7 @@
|
||||||
"openai_api_key": "sk-xxxxxxxxxxxxxxxxxxx",
|
"openai_api_key": "sk-xxxxxxxxxxxxxxxxxxx",
|
||||||
"bing_api_endpoint": "http://api:3000/conversation",
|
"bing_api_endpoint": "http://api:3000/conversation",
|
||||||
"bard_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.",
|
"bard_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.",
|
||||||
"bing_auth_cookie": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"bing_auth_cookie": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"pandora_api_endpoint": "http://127.0.0.1:8008",
|
||||||
|
"pandora_api_model": "text-davinci-002-render-sha-mobile"
|
||||||
}
|
}
|
4
main.py
4
main.py
|
@ -20,6 +20,8 @@ async def main():
|
||||||
bing_api_endpoint=config.get("bing_api_endpoint"),
|
bing_api_endpoint=config.get("bing_api_endpoint"),
|
||||||
bard_token=config.get("bard_token"),
|
bard_token=config.get("bard_token"),
|
||||||
bing_auth_cookie=config.get("bing_auth_cookie"),
|
bing_auth_cookie=config.get("bing_auth_cookie"),
|
||||||
|
pandora_api_endpoint=config.get("pandora_api_endpoint"),
|
||||||
|
pandora_api_model=config.get("pandora_api_model"),
|
||||||
port=config.get("port"),
|
port=config.get("port"),
|
||||||
timeout=config.get("timeout"),
|
timeout=config.get("timeout"),
|
||||||
)
|
)
|
||||||
|
@ -36,6 +38,8 @@ async def main():
|
||||||
bing_api_endpoint=os.environ.get("BING_API_ENDPOINT"),
|
bing_api_endpoint=os.environ.get("BING_API_ENDPOINT"),
|
||||||
bard_token=os.environ.get("BARD_TOKEN"),
|
bard_token=os.environ.get("BARD_TOKEN"),
|
||||||
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
|
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
|
||||||
|
pandora_api_endpoint=os.environ.get("PANDORA_API_ENDPOINT"),
|
||||||
|
pandora_api_model=os.environ.get("PANDORA_API_MODEL"),
|
||||||
port=os.environ.get("PORT"),
|
port=os.environ.get("PORT"),
|
||||||
timeout=os.environ.get("TIMEOUT"),
|
timeout=os.environ.get("TIMEOUT"),
|
||||||
)
|
)
|
||||||
|
|
100
pandora.py
Normal file
100
pandora.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
# 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) -> None:
|
||||||
|
self.api_endpoint = api_endpoint.rstrip('/')
|
||||||
|
self.session = aiohttp.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"
|
||||||
|
client = Pandora(api_endpoint)
|
||||||
|
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())
|
Loading…
Reference in a new issue