Chatbot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
15 KiB

import asyncio
import nio
from nio import (AsyncClient, AsyncClientConfig, MatrixRoom, RoomMessageText, InviteEvent, UploadResponse)
import os, sys
2 years ago
import time
import importlib
import configparser
import logging
import aiofiles.os
import magic
from PIL import Image
2 years ago
import re
2 years ago
from .helpers import Event
from .chatlog import BotChatHistory
ai = importlib.import_module("matrix_pygmalion_bot.ai.runpod_pygmalion")
#from .llama_cpp import generate, get_full_prompt, get_full_prompt_chat_style
#from .runpod_pygmalion import generate_sync, get_full_prompt
import matrix_pygmalion_bot.translate as translate
STORE_PATH = "./.store/"
logger = logging.getLogger(__name__)
config = configparser.ConfigParser()
bots = []
2 years ago
background_tasks = set()
class Callbacks(object):
"""Class to pass client to callback methods."""
def __init__(self, client: AsyncClient, bot):
self.client = client
self.bot = bot
async def message_cb(self, room: MatrixRoom, event: RoomMessageText) -> None:
2 years ago
if not hasattr(event, 'body'):
return
relates_to = None
if 'm.relates_to' in event.source["content"]:
relates_to = event.source["content"]['m.relates_to']["event_id"]
2 years ago
is_command = False
if event.body.startswith('!'):
is_command = True
2 years ago
language = "en"
if not (self.bot.translate is None) and not is_command:
2 years ago
language = self.bot.translate
2 years ago
2 years ago
if 'original_message' in event.source["content"]:
english_original_message = event.source["content"]['original_message']
else:
english_original_message = None
2 years ago
chat_message = self.bot.chat_history.room(room.display_name).add(event.event_id, event.server_timestamp, room.user_name(event.sender), event.sender == self.client.user, is_command, relates_to, event.body, language, english_original_message)
if self.bot.not_synced:
return
print(
"Message received for room {} | {}: {}".format(
room.display_name, room.user_name(event.sender), event.body
)
)
2 years ago
await self.client.room_read_markers(room.room_id, event.event_id, event.event_id)
# Ignore messages from ourselves
2 years ago
if chat_message.is_own_message:
2 years ago
return
2 years ago
# Ignore message from strangers
if not (self.bot.owner is None):
if not (event.sender == self.bot.owner or chat_message.is_own_message):
return
2 years ago
if event.body.startswith('!replybot'):
print(event)
await self.bot.send_message(self.client, room.room_id, "Hello World!")
return
2 years ago
elif event.body.startswith('!image'):
prompt = event.body.removeprefix('!image').strip()
negative_prompt = "out of frame, (ugly:1.3), (fused fingers), (too many fingers), (bad anatomy:1.5), (watermark:1.5), (words), letters, untracked eyes, asymmetric eyes, floating head, (logo:1.5), (bad hands:1.3), (mangled hands:1.2), (missing hands), (missing arms), backward hands, floating jewelry, unattached jewelry, floating head, doubled head, unattached head, doubled head, head in body, (misshapen body:1.1), (badly fitted headwear:1.2), floating arms, (too many arms:1.5), limbs fused with body, (facial blemish:1.5), badly fitted clothes, imperfect eyes, untracked eyes, crossed eyes, hair growing from clothes, partial faces, hair not attached to head"
if len(prompt) == 0:
prompt = "a beautiful woman"
output = await ai.generate_image(prompt, negative_prompt, self.bot.runpod_api_key)
for imagefile in output:
await self.bot.send_image(self.client, room.room_id, imagefile)
return
2 years ago
elif event.body.startswith('!begin'):
2 years ago
self.bot.chat_history.room(room.display_name).clear()
2 years ago
self.bot.tick = 0
2 years ago
await self.bot.write_conf2(self.bot.name)
await self.bot.send_message(self.client, room.room_id, self.bot.greeting)
return
elif event.body.startswith('!!!'):
2 years ago
if self.bot.chat_history.room(room.display_name).getLen() < 3:
2 years ago
return
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1) # current
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1)
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1)
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
return
2 years ago
elif event.body.startswith('!!'):
2 years ago
if self.bot.chat_history.room(room.display_name).getLen() < 3:
2 years ago
return
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1)# current
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1)
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
2 years ago
chat_message = self.bot.chat_history.room(room.display_name).getLastItem() # new current
2 years ago
# don't return, we generate a new answer
2 years ago
elif event.body.startswith('!replace'):
2 years ago
if self.bot.chat_history.room(room.display_name).getLen() < 3:
2 years ago
return
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1) # current
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
2 years ago
chat_history_item = self.bot.chat_history.room(room.display_name).remove(1)
2 years ago
await self.client.room_redact(room.room_id, chat_history_item.event_id, reason="user-request")
new_answer = event.body.removeprefix('!replace').strip()
2 years ago
await self.bot.send_message(self.client, room.room_id, new_answer, reply_to=chat_history_item.relates_to_event)
2 years ago
return
2 years ago
# Other commands
2 years ago
if re.search("^(?=.*\bsend\b)(?=.*\bpicture\b).*$", event.body):
2 years ago
# send, mail, drop, snap picture, photo, image, portrait
pass
2 years ago
full_prompt = await ai.get_full_prompt(chat_message.getTranslation("en"), self.bot, self.bot.chat_history.room(room.display_name))
num_tokens = await ai.num_tokens(full_prompt)
logger.info(full_prompt)
logger.info(f"num tokens:" + str(num_tokens))
# answer = ""
# time = 0
# error = None
# try:
# async for output in generate(full_prompt):
# await asyncio.sleep(0.1)
# answer += output
# if time % 5 == 0:
# await self.client.room_typing(room.room_id, True, 15000)
# time +=1
# print(output, end='', flush=True)
# except Exception as e:
# error = e.__str__()
# answer = answer.strip()
# print("")
await self.client.room_typing(room.room_id, True, 15000)
2 years ago
answer = await ai.generate_sync(full_prompt, self.bot.runpod_api_key, self.bot.name)
answer = answer.strip()
await self.client.room_typing(room.room_id, False)
if not (self.bot.translate is None):
translated_answer = translate.translate(answer, "en", self.bot.translate)
2 years ago
await self.bot.send_message(self.client, room.room_id, translated_answer, reply_to=chat_message.event_id, original_message=answer)
else:
2 years ago
await self.bot.send_message(self.client, room.room_id, answer, reply_to=chat_message.event_id)
async def invite_cb(self, room: MatrixRoom, event: InviteEvent) -> None:
"""Automatically join all rooms we get invited to"""
result = await self.client.join(room.room_id)
print('Invited to room: {} {}'.format(room.name, room.room_id))
if isinstance(result, nio.responses.JoinResponse):
print('Joined')
else:
print("Error joining room: {}".format(str(result)))
class ChatBot(object):
"""Main chatbot"""
def __init__(self, homeserver, user_id, password):
self.homeserver = homeserver
self.user_id = user_id
self.password = password
self.runpod_api_key = None
self.client = None
self.callbacks = None
self.config = None
self.not_synced = True
self.owner = None
self.translate = None
self.name = None
self.persona = None
self.scenario = None
self.greeting = None
2 years ago
self.events = []
2 years ago
self.tick = 0
2 years ago
self.chat_history = None
if STORE_PATH and not os.path.isdir(STORE_PATH):
os.mkdir(STORE_PATH)
def character_init(self, name, persona, scenario, greeting):
self.name = name
self.persona = persona
self.scenario = scenario
self.greeting = greeting
2 years ago
async def event_loop(self):
2 years ago
try:
while True:
await asyncio.sleep(60)
for event in self.events:
event.loop(self, self.tick)
self.tick += 1
if self.tick % 10 == 0:
await self.write_conf2(self.name)
finally:
await self.write_conf2(self.name)
2 years ago
async def add_event(self, event_string):
2 years ago
items = event_string.split(',', 4)
2 years ago
for item in items:
item = item.strip()
2 years ago
event = Event(int(items[0]), int(items[1]), float(items[2]), int(items[3]), items[4].lstrip())
2 years ago
self.events.append(event)
2 years ago
logger.info("event added to event_loop")
2 years ago
pass
2 years ago
async def login(self):
self.config = AsyncClientConfig(store_sync_tokens=True)
self.client = AsyncClient(self.homeserver, self.user_id, store_path=STORE_PATH, config=self.config)
2 years ago
self.chat_history = BotChatHistory(self.name)
self.callbacks = Callbacks(self.client, self)
self.client.add_event_callback(self.callbacks.message_cb, RoomMessageText)
self.client.add_event_callback(self.callbacks.invite_cb, InviteEvent)
sync_task = asyncio.create_task(self.watch_for_sync(self.client.synced))
2 years ago
event_loop = asyncio.create_task(self.event_loop())
background_tasks.add(event_loop)
event_loop.add_done_callback(background_tasks.discard)
try:
response = await self.client.login(self.password)
print(response)
2 years ago
#sync_forever_task = asyncio.create_task(self.client.sync_forever(timeout=30000, full_state=True))
except (asyncio.CancelledError, KeyboardInterrupt):
print("Received interrupt.")
await self.client.close()
2 years ago
#return sync_forever_task
async def watch_for_sync(self, sync_event):
print("Awaiting sync")
await sync_event.wait()
print("Client is synced")
self.not_synced = False
2 years ago
async def read_conf2(self, section):
config2 = configparser.ConfigParser()
config2.read('bot.conf2')
2 years ago
if config2.has_section(section) and config2.has_option(section, 'current_tick'):
self.tick = int(config2[section]['current_tick'])
2 years ago
async def write_conf2(self, section):
config2 = configparser.ConfigParser()
config2.read('bot.conf2')
config2[section] = {}
2 years ago
config2[section]['current_tick'] = str(self.tick)
2 years ago
with open('bot.conf2', 'w') as configfile:
config2.write(configfile)
async def send_message(self, client, room_id, message, reply_to=None, original_message=None):
content={"msgtype": "m.text", "body": message}
if reply_to:
content["m.relates_to"] = {"event_id": reply_to, "rel_type": "de.xd0.mpygbot.in_reply_to"}
if original_message:
content["original_message"] = original_message
await client.room_send(
room_id=room_id,
message_type="m.room.message",
content=content,
)
async def send_image(self, client, room_id, image):
"""Send image to room
https://matrix-nio.readthedocs.io/en/latest/examples.html#sending-an-image
"""
mime_type = magic.from_file(image, mime=True) # e.g. "image/jpeg"
if not mime_type.startswith("image/"):
logger.error("Drop message because file does not have an image mime type.")
return
im = Image.open(image)
(width, height) = im.size # im.size returns (width,height) tuple
# first do an upload of image, then send URI of upload to room
file_stat = await aiofiles.os.stat(image)
async with aiofiles.open(image, "r+b") as f:
resp, maybe_keys = await client.upload(
f,
content_type=mime_type, # image/jpeg
filename=os.path.basename(image),
filesize=file_stat.st_size,
)
if isinstance(resp, UploadResponse):
print("Image was uploaded successfully to server. ")
else:
print(f"Failed to upload image. Failure response: {resp}")
content = {
"body": os.path.basename(image), # descriptive title
"info": {
"size": file_stat.st_size,
"mimetype": mime_type,
"thumbnail_info": None, # TODO
"w": width, # width in pixel
"h": height, # height in pixel
"thumbnail_url": None, # TODO
},
"msgtype": "m.image",
"url": resp.content_uri,
}
try:
await client.room_send(room_id, message_type="m.room.message", content=content)
print("Image was sent successfully")
except Exception:
print(f"Image send of file {image} failed.")
async def main() -> None:
config.read('bot.conf')
logging.basicConfig(level=logging.INFO)
for section in config.sections():
if section == 'DEFAULT' or section == 'Common':
pass
botname = section
homeserver = config[section]['url']
user_id = config[section]['username']
password = config[section]['password']
bot = ChatBot(homeserver, user_id, password)
bot.character_init(botname, config[section]['persona'], config[section]['scenario'], config[section]['greeting'])
if config.has_option(section, 'owner'):
bot.owner = config[section]['owner']
if config.has_option(section, 'translate'):
bot.translate = config[section]['translate']
translate.init(bot.translate, "en")
translate.init("en", bot.translate)
if config.has_option(section, 'image_prompt'):
bot.image_prompt = config[section]['image_prompt']
2 years ago
if config.has_option(section, 'events'):
events = config[section]['events'].strip().split('\n')
for event in events:
await bot.add_event(event)
if config.has_option('DEFAULT', 'runpod_api_key'):
bot.runpod_api_key = config['DEFAULT']['runpod_api_key']
2 years ago
await bot.read_conf2(section)
2 years ago
#await bot.write_conf2(section)
bots.append(bot)
await bot.login()
2 years ago
print("gather")
async with asyncio.TaskGroup() as tg:
for bot in bots:
task = tg.create_task(bot.client.sync_forever(timeout=30000, full_state=True))
asyncio.get_event_loop().run_until_complete(main())