From 7b0c1f0868040dfc5448e5260afef26faa47ae03 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Mon, 17 Jul 2023 01:18:08 -0400 Subject: [PATCH 01/36] initial discord bot --- requirements.txt | 1 + skynet.ini.example | 11 + skynet/cli.py | 69 +++++- skynet/config.py | 10 +- skynet/frontend/discord/__init__.py | 280 +++++++++++++++++++++ skynet/frontend/discord/bot.py | 73 ++++++ skynet/frontend/discord/handlers.py | 372 ++++++++++++++++++++++++++++ skynet/frontend/discord/utils.py | 106 ++++++++ 8 files changed, 917 insertions(+), 5 deletions(-) create mode 100644 skynet/frontend/discord/__init__.py create mode 100644 skynet/frontend/discord/bot.py create mode 100644 skynet/frontend/discord/handlers.py create mode 100644 skynet/frontend/discord/utils.py diff --git a/requirements.txt b/requirements.txt index a30a623..6a1a32e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,6 @@ docker aiohttp psycopg2-binary pyTelegramBotAPI +discord.py py-leap@git+https://github.com/guilledk/py-leap.git@v0.1a14 diff --git a/skynet.ini.example b/skynet.ini.example index b498dd8..1f9eab0 100644 --- a/skynet.ini.example +++ b/skynet.ini.example @@ -22,3 +22,14 @@ hyperion_url = https://skynet.ancap.tech ipfs_url = /ip4/169.197.140.154/tcp/4001/p2p/12D3KooWKWogLFNEcNNMKnzU7Snrnuj84RZdMBg3sLiQSQc51oEv token = XXXXXXXXXX:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +[skynet.discord] +account = discord +permission = active +key = 5Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +node_url = https://skynet.ancap.tech +hyperion_url = https://skynet.ancap.tech +ipfs_url = /ip4/169.197.140.154/tcp/4001/p2p/12D3KooWKWogLFNEcNNMKnzU7Snrnuj84RZdMBg3sLiQSQc51oEv + +token = XXXXXXXXXX:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/skynet/cli.py b/skynet/cli.py index 1f7d24c..385b056 100755 --- a/skynet/cli.py +++ b/skynet/cli.py @@ -24,6 +24,7 @@ from .config import * from .nodeos import open_cleos, open_nodeos from .constants import * from .frontend.telegram import SkynetTelegramFrontend +from .frontend.discord import SkynetDiscordFrontend @click.group() @@ -43,7 +44,7 @@ def skynet(*args, **kwargs): @click.option('--seed', '-S', default=None) def txt2img(*args, **kwargs): from . import utils - _, hf_token, _ = init_env_from_config() + _, hf_token, _, _ = init_env_from_config() utils.txt2img(hf_token, **kwargs) @click.command() @@ -58,7 +59,7 @@ def txt2img(*args, **kwargs): @click.option('--seed', '-S', default=None) def img2img(model, prompt, input, output, strength, guidance, steps, seed): from . import utils - _, hf_token, _ = init_env_from_config() + _, hf_token, _, _ = init_env_from_config() utils.img2img( hf_token, model=model, @@ -86,7 +87,7 @@ def upscale(input, output, model): @skynet.command() def download(): from . import utils - _, hf_token, _ = init_env_from_config() + _, hf_token, _, _ = init_env_from_config() utils.download_all_models(hf_token) @skynet.command() @@ -408,7 +409,7 @@ def telegram( ): logging.basicConfig(level=loglevel) - _, _, tg_token = init_env_from_config() + _, _, tg_token, _ = init_env_from_config() key, account, permission = load_account_info( 'telegram', key, account, permission) @@ -435,6 +436,66 @@ def telegram( asyncio.run(_async_main()) +@run.command() +@click.option('--loglevel', '-l', default='INFO', help='logging level') +@click.option( + '--account', '-a', default='discord') +@click.option( + '--permission', '-p', default='active') +@click.option( + '--key', '-k', default=None) +@click.option( + '--hyperion-url', '-y', default=f'https://{DEFAULT_DOMAIN}') +@click.option( + '--node-url', '-n', default=f'https://{DEFAULT_DOMAIN}') +@click.option( + '--ipfs-url', '-i', default=DEFAULT_IPFS_REMOTE) +@click.option( + '--db-host', '-h', default='localhost:5432') +@click.option( + '--db-user', '-u', default='skynet') +@click.option( + '--db-pass', '-u', default='password') +def discord( + loglevel: str, + account: str, + permission: str, + key: str | None, + hyperion_url: str, + ipfs_url: str, + node_url: str, + db_host: str, + db_user: str, + db_pass: str +): + logging.basicConfig(level=loglevel) + + _, _, _, dc_token = init_env_from_config() + + key, account, permission = load_account_info( + 'discord', key, account, permission) + + node_url, _, ipfs_url = load_endpoint_info( + 'discord', node_url, None, None) + + async def _async_main(): + frontend = SkynetDiscordFrontend( + # dc_token, + account, + permission, + node_url, + hyperion_url, + db_host, db_user, db_pass, + remote_ipfs_node=ipfs_url, + key=key + ) + + async with frontend.open(): + await frontend.bot.start(dc_token) + + asyncio.run(_async_main()) + + @run.command() @click.option('--loglevel', '-l', default='INFO', help='logging level') @click.option('--name', '-n', default='skynet-ipfs', help='container name') diff --git a/skynet/config.py b/skynet/config.py index b025feb..d668a41 100755 --- a/skynet/config.py +++ b/skynet/config.py @@ -23,6 +23,7 @@ def init_env_from_config( hf_token: str | None = None, hf_home: str | None = None, tg_token: str | None = None, + dc_token: str | None = None, file_path=DEFAULT_CONFIG_PATH ): config = load_skynet_ini(file_path=file_path) @@ -52,7 +53,14 @@ def init_env_from_config( if 'token' in sub_config: tg_token = sub_config['token'] - return hf_home, hf_token, tg_token + if 'DC_TOKEN' in os.environ: + dc_token = os.environ['DC_TOKEN'] + elif 'skynet.discord' in config: + sub_config = config['skynet.discord'] + if 'token' in sub_config: + dc_token = sub_config['token'] + + return hf_home, hf_token, tg_token, dc_token def load_account_info( diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py new file mode 100644 index 0000000..a9518b3 --- /dev/null +++ b/skynet/frontend/discord/__init__.py @@ -0,0 +1,280 @@ +#!/usr/bin/python + +from json import JSONDecodeError +import random +import logging +import asyncio + +from decimal import Decimal +from hashlib import sha256 +from datetime import datetime +from contextlib import ExitStack, AsyncExitStack +from contextlib import asynccontextmanager as acm + +from leap.cleos import CLEOS +from leap.sugar import Name, asset_from_str, collect_stdout +from leap.hyperion import HyperionAPI +# from telebot.types import InputMediaPhoto + +# import discord + +from skynet.db import open_new_database, open_database_connection +from skynet.ipfs import get_ipfs_file +from skynet.ipfs.docker import open_ipfs_node +from skynet.constants import * + +from . import * +from .bot import DiscordBot + +from .utils import * +from .handlers import create_handler_context + + +class SkynetDiscordFrontend: + + def __init__( + self, + token: str, + account: str, + permission: str, + node_url: str, + hyperion_url: str, + db_host: str, + db_user: str, + # db_pass: str, + remote_ipfs_node: str, + key: str + ): + self.token = token + self.account = account + self.permission = permission + self.node_url = node_url + self.hyperion_url = hyperion_url + self.db_host = db_host + self.db_user = db_user + # self.db_pass = db_pass + self.remote_ipfs_node = remote_ipfs_node + self.key = key + + self.bot = DiscordBot() + self.cleos = CLEOS(None, None, url=node_url, remote=node_url) + self.hyperion = HyperionAPI(hyperion_url) + + self._exit_stack = ExitStack() + self._async_exit_stack = AsyncExitStack() + + async def start(self): + self.ipfs_node = self._exit_stack.enter_context( + open_ipfs_node()) + + self.ipfs_node.connect(self.remote_ipfs_node) + logging.info( + f'connected to remote ipfs node: {self.remote_ipfs_node}') + + # self.db_call = await self._async_exit_stack.enter_async_context( + # open_database_connection( + # self.db_user, self.db_pass, self.db_host)) + + create_handler_context(self) + + async def stop(self): + await self._async_exit_stack.aclose() + self._exit_stack.close() + + @acm + async def open(self): + await self.start() + yield self + await self.stop() + + # async def update_status_message( + # self, status_msg, new_text: str, **kwargs + # ): + # await self.db_call( + # 'update_user_request_by_sid', status_msg.id, new_text) + # return await self.bot.edit_message_text( + # new_text, + # chat_id=status_msg.chat.id, + # message_id=status_msg.id, + # **kwargs + # ) + + # async def append_status_message( + # self, status_msg, add_text: str, **kwargs + # ): + # request = await self.db_call('get_user_request_by_sid', status_msg.id) + # await self.update_status_message( + # status_msg, + # request['status'] + add_text, + # **kwargs + # ) + + async def work_request( + self, + user, + status_msg, + method: str, + params: dict, + file_id: str | None = None, + binary_data: str = '' + ): + if params['seed'] == None: + params['seed'] = random.randint(0, 0xFFFFFFFF) + + sanitized_params = {} + for key, val in params.items(): + if isinstance(val, Decimal): + val = str(val) + + sanitized_params[key] = val + + body = json.dumps({ + 'method': 'diffuse', + 'params': sanitized_params + }) + request_time = datetime.now().isoformat() + + # await self.update_status_message( + # status_msg, + # f'processing a \'{method}\' request by {tg_user_pretty(user)}\n' + # f'[{timestamp_pretty()}] broadcasting transaction to chain...', + # parse_mode='HTML' + # ) + + reward = '20.0000 GPU' + res = await self.cleos.a_push_action( + 'telos.gpu', + 'enqueue', + { + 'user': Name(self.account), + 'request_body': body, + 'binary_data': binary_data, + 'reward': asset_from_str(reward), + 'min_verification': 1 + }, + self.account, self.key, permission=self.permission + ) + + if 'code' in res or 'statusCode' in res: + logging.error(json.dumps(res, indent=4)) + # await self.update_status_message( + # status_msg, + # 'skynet has suffered an internal error trying to fill this request') + # return + + enqueue_tx_id = res['transaction_id'] + enqueue_tx_link = hlink( + 'Your request on Skynet Explorer', + f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{enqueue_tx_id}' + ) + + # await self.append_status_message( + # status_msg, + # f' broadcasted!\n' + # f'{enqueue_tx_link}\n' + # f'[{timestamp_pretty()}] workers are processing request...', + # parse_mode='HTML' + # ) + + out = collect_stdout(res) + + request_id, nonce = out.split(':') + + request_hash = sha256( + (nonce + body + binary_data).encode('utf-8')).hexdigest().upper() + + request_id = int(request_id) + + logging.info(f'{request_id} enqueued.') + + tx_hash = None + ipfs_hash = None + for i in range(60): + try: + submits = await self.hyperion.aget_actions( + account=self.account, + filter='telos.gpu:submit', + sort='desc', + after=request_time + ) + actions = [ + action + for action in submits['actions'] + if action[ + 'act']['data']['request_hash'] == request_hash + ] + if len(actions) > 0: + tx_hash = actions[0]['trx_id'] + data = actions[0]['act']['data'] + ipfs_hash = data['ipfs_hash'] + worker = data['worker'] + logging.info('Found matching submit!') + break + + except JSONDecodeError: + logging.error(f'network error while getting actions, retry..') + + await asyncio.sleep(1) + + if not ipfs_hash: + # await self.update_status_message( + # status_msg, + # f'\n[{timestamp_pretty()}] timeout processing request', + # parse_mode='HTML' + # ) + return + + tx_link = hlink( + 'Your result on Skynet Explorer', + f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}' + ) + + # await self.append_status_message( + # status_msg, + # f' request processed!\n' + # f'{tx_link}\n' + # f'[{timestamp_pretty()}] trying to download image...\n', + # parse_mode='HTML' + # ) + + # attempt to get the image and send it + ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' + resp = await get_ipfs_file(ipfs_link) + + caption = generate_reply_caption( + user, params, tx_hash, worker, reward) + + if not resp or resp.status_code != 200: + logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!') + # await self.update_status_message( + # status_msg, + # caption, + # reply_markup=build_redo_menu(), + # parse_mode='HTML' + # ) + # + else: + logging.info(f'success! sending generated image') + # await self.bot.delete_message( + # chat_id=status_msg.chat.id, message_id=status_msg.id) + # if file_id: # img2img + # await self.bot.send_media_group( + # status_msg.chat.id, + # media=[ + # InputMediaPhoto(file_id), + # InputMediaPhoto( + # resp.raw, + # caption=caption, + # parse_mode='HTML' + # ) + # ], + # ) + # + # else: # txt2img + # await self.bot.send_photo( + # status_msg.chat.id, + # caption=caption, + # photo=resp.raw, + # reply_markup=build_redo_menu(), + # parse_mode='HTML' + # ) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py new file mode 100644 index 0000000..340b89d --- /dev/null +++ b/skynet/frontend/discord/bot.py @@ -0,0 +1,73 @@ +# import os +import discord +# import asyncio +# from dotenv import load_dotenv +# from pathlib import Path +from discord.ext import commands + + +# # Auth +# current_dir = Path(__file__).resolve().parent +# # parent_dir = current_dir.parent +# env_file_path = current_dir / ".env" +# load_dotenv(dotenv_path=env_file_path) +# +# discordToken = os.getenv("DISCORD_TOKEN") + + +# Actual Discord bot. +class DiscordBot(commands.Bot): + + def __init__(self, *args, **kwargs): + intents = discord.Intents( + messages=True, + guilds=True, + typing=True, + members=True, + presences=True, + reactions=True, + message_content=True, + voice_states=True + ) + super().__init__(command_prefix='\\', intents=intents, *args, **kwargs) + + # async def setup_hook(self): + # db.poll_db.start() + + async def on_ready(self): + print(f'{self.user.name} has connected to Discord!') + for guild in self.guilds: + for channel in guild.channels: + if channel.name == "skynet": + await channel.send('Skynet bot online') + + print("\n==============") + print("Logged in as") + print(self.user.name) + print(self.user.id) + print("==============") + + async def on_command_error(self, ctx, error): + if isinstance(error, commands.MissingRequiredArgument): + await ctx.send('You missed a required argument, please try again.') + + # async def on_message(self, message): + # print(f"message from {message.author} what he said {message.content}") + # await message.channel.send(message.content) + +# bot=DiscordBot() +# @bot.command(name='config', help='Responds with the configuration') +# async def config(ctx): +# response = "This is the bot configuration" # Put your bot configuration here +# await ctx.send(response) +# +# @bot.command(name='helper', help='Responds with a help') +# async def helper(ctx): +# response = "This is help information" # Put your help response here +# await ctx.send(response) +# +# @bot.command(name='txt2img', help='Responds with an image') +# async def txt2img(ctx, *, arg): +# response = f"This is your prompt: {arg}" +# await ctx.send(response) +# bot.run(discordToken) diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py new file mode 100644 index 0000000..8ac46a5 --- /dev/null +++ b/skynet/frontend/discord/handlers.py @@ -0,0 +1,372 @@ +#!/usr/bin/python + +import io +import json +import logging + +from datetime import datetime, timedelta + +from PIL import Image +# from telebot.types import CallbackQuery, Message + +from skynet.frontend import validate_user_config_request +from skynet.constants import * + + +def create_handler_context(frontend: 'SkynetDiscordFrontend'): + + bot = frontend.bot + cleos = frontend.cleos + # db_call = frontend.db_call + work_request = frontend.work_request + + ipfs_node = frontend.ipfs_node + + + @bot.command(name='config', help='Responds with the configuration') + async def config(ctx): + response = "This is the bot configuration" # Put your bot configuration here + await ctx.send(response) + + @bot.command(name='helper', help='Responds with a help') + async def helper(ctx): + response = "This is help information" # Put your help response here + await ctx.send(response) + + @bot.command(name='txt2img', help='Responds with an image') + async def send_txt2img(ctx, *, arg): + user = 'tests' + status_msg = 'status' + params = { + 'prompt': prompt, + } + ec = await work_request(user, status_msg, 'txt2img', params) + print(ec) + + # if ec == 0: + # await db_call('increment_generated', user.id) + + response = f"This is your prompt: {arg}" + await ctx.send(response) + + # generic / simple handlers + + # @bot.message_handler(commands=['help']) + # async def send_help(message): + # splt_msg = message.text.split(' ') + # + # if len(splt_msg) == 1: + # await bot.reply_to(message, HELP_TEXT) + # + # else: + # param = splt_msg[1] + # if param in HELP_TOPICS: + # await bot.reply_to(message, HELP_TOPICS[param]) + # + # else: + # await bot.reply_to(message, HELP_UNKWNOWN_PARAM) + # + # @bot.message_handler(commands=['cool']) + # async def send_cool_words(message): + # await bot.reply_to(message, '\n'.join(COOL_WORDS)) + # + # @bot.message_handler(commands=['queue']) + # async def queue(message): + # an_hour_ago = datetime.now() - timedelta(hours=1) + # queue = await cleos.aget_table( + # 'telos.gpu', 'telos.gpu', 'queue', + # index_position=2, + # key_type='i64', + # sort='desc', + # lower_bound=int(an_hour_ago.timestamp()) + # ) + # await bot.reply_to( + # message, f'Total requests on skynet queue: {len(queue)}') + + + # @bot.message_handler(commands=['config']) + # async def set_config(message): + # user = message.from_user.id + # try: + # attr, val, reply_txt = validate_user_config_request( + # message.text) + # + # logging.info(f'user config update: {attr} to {val}') + # await db_call('update_user_config', user, attr, val) + # logging.info('done') + # + # except BaseException as e: + # reply_txt = str(e) + # + # finally: + # await bot.reply_to(message, reply_txt) + # + # @bot.message_handler(commands=['stats']) + # async def user_stats(message): + # user = message.from_user.id + # + # await db_call('get_or_create_user', user) + # generated, joined, role = await db_call('get_user_stats', user) + # + # stats_str = f'generated: {generated}\n' + # stats_str += f'joined: {joined}\n' + # stats_str += f'role: {role}\n' + # + # await bot.reply_to( + # message, stats_str) + # + # @bot.message_handler(commands=['donate']) + # async def donation_info(message): + # await bot.reply_to( + # message, DONATION_INFO) + # + # @bot.message_handler(commands=['say']) + # async def say(message): + # chat = message.chat + # user = message.from_user + # + # if (chat.type == 'group') or (user.id != 383385940): + # return + # + # await bot.send_message(GROUP_ID, message.text[4:]) + + + # generic txt2img handler + + # async def _generic_txt2img(message_or_query): + # if isinstance(message_or_query, CallbackQuery): + # query = message_or_query + # message = query.message + # user = query.from_user + # chat = query.message.chat + # + # else: + # message = message_or_query + # user = message.from_user + # chat = message.chat + # + # reply_id = None + # if chat.type == 'group' and chat.id == GROUP_ID: + # reply_id = message.message_id + # + # user_row = await db_call('get_or_create_user', user.id) + # + # # init new msg + # init_msg = 'started processing txt2img request...' + # status_msg = await bot.reply_to(message, init_msg) + # await db_call( + # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg) + # + # prompt = ' '.join(message.text.split(' ')[1:]) + # + # if len(prompt) == 0: + # await bot.edit_message_text( + # 'Empty text prompt ignored.', + # chat_id=status_msg.chat.id, + # message_id=status_msg.id + # ) + # await db_call('update_user_request', status_msg.id, 'Empty text prompt ignored.') + # return + # + # logging.info(f'mid: {message.id}') + # + # user_config = {**user_row} + # del user_config['id'] + # + # params = { + # 'prompt': prompt, + # **user_config + # } + # + # await db_call( + # 'update_user_stats', user.id, 'txt2img', last_prompt=prompt) + # + # ec = await work_request(user, status_msg, 'txt2img', params) + + # if ec == 0: + # await db_call('increment_generated', user.id) + # + # + # # generic img2img handler + # + # async def _generic_img2img(message_or_query): + # if isinstance(message_or_query, CallbackQuery): + # query = message_or_query + # message = query.message + # user = query.from_user + # chat = query.message.chat + # + # else: + # message = message_or_query + # user = message.from_user + # chat = message.chat + # + # reply_id = None + # if chat.type == 'group' and chat.id == GROUP_ID: + # reply_id = message.message_id + # + # user_row = await db_call('get_or_create_user', user.id) + # + # # init new msg + # init_msg = 'started processing txt2img request...' + # status_msg = await bot.reply_to(message, init_msg) + # await db_call( + # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg) + # + # if not message.caption.startswith('/img2img'): + # await bot.reply_to( + # message, + # 'For image to image you need to add /img2img to the beggining of your caption' + # ) + # return + # + # prompt = ' '.join(message.caption.split(' ')[1:]) + # + # if len(prompt) == 0: + # await bot.reply_to(message, 'Empty text prompt ignored.') + # return + # + # file_id = message.photo[-1].file_id + # file_path = (await bot.get_file(file_id)).file_path + # image_raw = await bot.download_file(file_path) + + # with Image.open(io.BytesIO(image_raw)) as image: + # w, h = image.size + # + # if w > 512 or h > 512: + # logging.warning(f'user sent img of size {image.size}') + # image.thumbnail((512, 512)) + # logging.warning(f'resized it to {image.size}') + # + # image.save(f'ipfs-docker-staging/image.png', format='PNG') + # + # ipfs_hash = ipfs_node.add('image.png') + # ipfs_node.pin(ipfs_hash) + # + # logging.info(f'published input image {ipfs_hash} on ipfs') + # + # logging.info(f'mid: {message.id}') + # + # user_config = {**user_row} + # del user_config['id'] + # + # params = { + # 'prompt': prompt, + # **user_config + # } + # + # await db_call( + # 'update_user_stats', + # user.id, + # 'img2img', + # last_file=file_id, + # last_prompt=prompt, + # last_binary=ipfs_hash + # ) + # + # ec = await work_request( + # user, status_msg, 'img2img', params, + # file_id=file_id, + # binary_data=ipfs_hash + # ) + # + # if ec == 0: + # await db_call('increment_generated', user.id) + # + + # generic redo handler + + # async def _redo(message_or_query): + # is_query = False + # if isinstance(message_or_query, CallbackQuery): + # is_query = True + # query = message_or_query + # message = query.message + # user = query.from_user + # chat = query.message.chat + # + # elif isinstance(message_or_query, Message): + # message = message_or_query + # user = message.from_user + # chat = message.chat + # + # init_msg = 'started processing redo request...' + # if is_query: + # status_msg = await bot.send_message(chat.id, init_msg) + # + # else: + # status_msg = await bot.reply_to(message, init_msg) + # + # method = await db_call('get_last_method_of', user.id) + # prompt = await db_call('get_last_prompt_of', user.id) + # + # file_id = None + # binary = '' + # if method == 'img2img': + # file_id = await db_call('get_last_file_of', user.id) + # binary = await db_call('get_last_binary_of', user.id) + # + # if not prompt: + # await bot.reply_to( + # message, + # 'no last prompt found, do a txt2img cmd first!' + # ) + # return + # + # + # user_row = await db_call('get_or_create_user', user.id) + # await db_call( + # 'new_user_request', user.id, message.id, status_msg.id, status=init_msg) + # user_config = {**user_row} + # del user_config['id'] + # + # params = { + # 'prompt': prompt, + # **user_config + # } + # + # await work_request( + # user, status_msg, 'redo', params, + # file_id=file_id, + # binary_data=binary + # ) + + + # "proxy" handlers just request routers + + # @bot.message_handler(commands=['txt2img']) + # async def send_txt2img(message): + # await _generic_txt2img(message) + # + # @bot.message_handler(func=lambda message: True, content_types=[ + # 'photo', 'document']) + # async def send_img2img(message): + # await _generic_img2img(message) + # + # @bot.message_handler(commands=['img2img']) + # async def img2img_missing_image(message): + # await bot.reply_to( + # message, + # 'seems you tried to do an img2img command without sending image' + # ) + # + # @bot.message_handler(commands=['redo']) + # async def redo(message): + # await _redo(message) + # + # @bot.callback_query_handler(func=lambda call: True) + # async def callback_query(call): + # msg = json.loads(call.data) + # logging.info(call.data) + # method = msg.get('method') + # match method: + # case 'redo': + # await _redo(call) + + + # catch all handler for things we dont support + + # @bot.message_handler(func=lambda message: True) + # async def echo_message(message): + # if message.text[0] == '/': + # await bot.reply_to(message, UNKNOWN_CMD_TEXT) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py new file mode 100644 index 0000000..ad08bba --- /dev/null +++ b/skynet/frontend/discord/utils.py @@ -0,0 +1,106 @@ +#!/usr/bin/python + +import json +import logging +import traceback + +from datetime import datetime, timezone + +from telebot.types import InlineKeyboardButton, InlineKeyboardMarkup +from telebot.async_telebot import ExceptionHandler +from telebot.formatting import hlink + +from skynet.constants import * + + +def timestamp_pretty(): + return datetime.now(timezone.utc).strftime('%H:%M:%S') + + +def tg_user_pretty(tguser): + if tguser.username: + return f'@{tguser.username}' + else: + return f'{tguser.first_name} id: {tguser.id}' + + +class SKYExceptionHandler(ExceptionHandler): + + def handle(exception): + traceback.print_exc() + + +def build_redo_menu(): + btn_redo = InlineKeyboardButton("Redo", callback_data=json.dumps({'method': 'redo'})) + inline_keyboard = InlineKeyboardMarkup() + inline_keyboard.add(btn_redo) + return inline_keyboard + + +def prepare_metainfo_caption(tguser, worker: str, reward: str, meta: dict) -> str: + prompt = meta["prompt"] + if len(prompt) > 256: + prompt = prompt[:256] + + + meta_str = f'by {tg_user_pretty(tguser)}\n' + meta_str += f'performed by {worker}\n' + meta_str += f'reward: {reward}\n' + + meta_str += f'prompt: {prompt}\n' + meta_str += f'seed: {meta["seed"]}\n' + meta_str += f'step: {meta["step"]}\n' + meta_str += f'guidance: {meta["guidance"]}\n' + if meta['strength']: + meta_str += f'strength: {meta["strength"]}\n' + meta_str += f'algo: {meta["model"]}\n' + if meta['upscaler']: + meta_str += f'upscaler: {meta["upscaler"]}\n' + + meta_str += f'Made with Skynet v{VERSION}\n' + meta_str += f'JOIN THE SWARM: @skynetgpu' + return meta_str + + +def generate_reply_caption( + tguser, # telegram user + params: dict, + tx_hash: str, + worker: str, + reward: str +): + explorer_link = hlink( + 'SKYNET Transaction Explorer', + f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}' + ) + + meta_info = prepare_metainfo_caption(tguser, worker, reward, params) + + final_msg = '\n'.join([ + 'Worker finished your task!', + explorer_link, + f'PARAMETER INFO:\n{meta_info}' + ]) + + final_msg = '\n'.join([ + f'{explorer_link}', + f'{meta_info}' + ]) + + logging.info(final_msg) + + return final_msg + + +async def get_global_config(cleos): + return (await cleos.aget_table( + 'telos.gpu', 'telos.gpu', 'config'))[0] + +async def get_user_nonce(cleos, user: str): + return (await cleos.aget_table( + 'telos.gpu', 'telos.gpu', 'users', + index_position=1, + key_type='name', + lower_bound=user, + upper_bound=user + ))[0]['nonce'] From fadb4eab6d9e3b3e7d5d49193dcf67f1a1b65c15 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Tue, 18 Jul 2023 12:40:21 -0400 Subject: [PATCH 02/36] update params for work queue --- skynet/frontend/discord/__init__.py | 10 ++++++---- skynet/frontend/discord/handlers.py | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index a9518b3..42afd17 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -134,6 +134,7 @@ class SkynetDiscordFrontend: }) request_time = datetime.now().isoformat() + # import pdb; pdb.set_trace() # await self.update_status_message( # status_msg, # f'processing a \'{method}\' request by {tg_user_pretty(user)}\n' @@ -154,13 +155,14 @@ class SkynetDiscordFrontend: }, self.account, self.key, permission=self.permission ) + print(res) if 'code' in res or 'statusCode' in res: logging.error(json.dumps(res, indent=4)) - # await self.update_status_message( - # status_msg, - # 'skynet has suffered an internal error trying to fill this request') - # return + await self.bot.send( + status_msg, + 'skynet has suffered an internal error trying to fill this request') + return enqueue_tx_id = res['transaction_id'] enqueue_tx_link = hlink( diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index 8ac46a5..c408840 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -38,8 +38,12 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): user = 'tests' status_msg = 'status' params = { - 'prompt': prompt, + 'prompt': arg, + 'seed': None, + 'step': 35, + 'guidance': 1, } + # import pdb; pdb.set_trace() ec = await work_request(user, status_msg, 'txt2img', params) print(ec) From 1d7633d340b3e5a9030fcd2cb6b0732e00ad25a4 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Tue, 18 Jul 2023 23:44:58 -0400 Subject: [PATCH 03/36] get initial discord bot working with hardcoded config and image return --- skynet/cli.py | 4 +- skynet/frontend/discord/__init__.py | 60 ++++++++++++++++++++--------- skynet/frontend/discord/bot.py | 2 +- skynet/frontend/discord/handlers.py | 13 +++++-- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/skynet/cli.py b/skynet/cli.py index 385b056..34c4562 100755 --- a/skynet/cli.py +++ b/skynet/cli.py @@ -439,7 +439,7 @@ def telegram( @run.command() @click.option('--loglevel', '-l', default='INFO', help='logging level') @click.option( - '--account', '-a', default='discord') + '--account', '-a', default=None) @click.option( '--permission', '-p', default='active') @click.option( @@ -485,7 +485,7 @@ def discord( permission, node_url, hyperion_url, - db_host, db_user, db_pass, + # db_host, db_user, db_pass, remote_ipfs_node=ipfs_url, key=key ) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 42afd17..1c0df56 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -16,7 +16,8 @@ from leap.sugar import Name, asset_from_str, collect_stdout from leap.hyperion import HyperionAPI # from telebot.types import InputMediaPhoto -# import discord +import discord +import io from skynet.db import open_new_database, open_database_connection from skynet.ipfs import get_ipfs_file @@ -34,24 +35,24 @@ class SkynetDiscordFrontend: def __init__( self, - token: str, + # token: str, account: str, permission: str, node_url: str, hyperion_url: str, - db_host: str, - db_user: str, + # db_host: str, + # db_user: str, # db_pass: str, remote_ipfs_node: str, key: str ): - self.token = token + # self.token = token self.account = account self.permission = permission self.node_url = node_url self.hyperion_url = hyperion_url - self.db_host = db_host - self.db_user = db_user + # self.db_host = db_host + # self.db_user = db_user # self.db_pass = db_pass self.remote_ipfs_node = remote_ipfs_node self.key = key @@ -115,6 +116,7 @@ class SkynetDiscordFrontend: status_msg, method: str, params: dict, + ctx: discord.TextChannel, file_id: str | None = None, binary_data: str = '' ): @@ -134,13 +136,17 @@ class SkynetDiscordFrontend: }) request_time = datetime.now().isoformat() - # import pdb; pdb.set_trace() + # maybe get rid of this # await self.update_status_message( # status_msg, # f'processing a \'{method}\' request by {tg_user_pretty(user)}\n' # f'[{timestamp_pretty()}] broadcasting transaction to chain...', # parse_mode='HTML' # ) + message = await ctx.send( + f'processing a \'{method}\' request by {user}\n \ + [{timestamp_pretty()}] *broadcasting transaction to chain...*' + ) reward = '20.0000 GPU' res = await self.cleos.a_push_action( @@ -178,6 +184,12 @@ class SkynetDiscordFrontend: # parse_mode='HTML' # ) + await message.edit( + f'**broadcasted!**\n \ + **{enqueue_tx_link}**\n \ + [{timestamp_pretty()}] *workers are processing request...*' + ) + out = collect_stdout(res) request_id, nonce = out.split(':') @@ -238,13 +250,18 @@ class SkynetDiscordFrontend: # f'[{timestamp_pretty()}] trying to download image...\n', # parse_mode='HTML' # ) + await message.edit( + f'**request processed!**\n \ + **{tx_link}**\n \ + [{timestamp_pretty()}] *trying to download image...*\n' + ) # attempt to get the image and send it ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' resp = await get_ipfs_file(ipfs_link) - caption = generate_reply_caption( - user, params, tx_hash, worker, reward) + # caption = generate_reply_caption( + # user, params, tx_hash, worker, reward) if not resp or resp.status_code != 200: logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!') @@ -257,9 +274,11 @@ class SkynetDiscordFrontend: # else: logging.info(f'success! sending generated image') + image = io.BytesIO(resp.raw) # await self.bot.delete_message( # chat_id=status_msg.chat.id, message_id=status_msg.id) - # if file_id: # img2img + if file_id: # img2img + pass # await self.bot.send_media_group( # status_msg.chat.id, # media=[ @@ -272,11 +291,14 @@ class SkynetDiscordFrontend: # ], # ) # - # else: # txt2img - # await self.bot.send_photo( - # status_msg.chat.id, - # caption=caption, - # photo=resp.raw, - # reply_markup=build_redo_menu(), - # parse_mode='HTML' - # ) + else: # txt2img + # await self.bot.send_photo( + # status_msg.chat.id, + # caption=caption, + # photo=resp.raw, + # reply_markup=build_redo_menu(), + # parse_mode='HTML' + # ) + await ctx.send( + file=discord.File(image, 'image.png') + ) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index 340b89d..d8ecb3b 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -29,7 +29,7 @@ class DiscordBot(commands.Bot): message_content=True, voice_states=True ) - super().__init__(command_prefix='\\', intents=intents, *args, **kwargs) + super().__init__(command_prefix='/', intents=intents, *args, **kwargs) # async def setup_hook(self): # db.poll_db.start() diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index c408840..cbde27e 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -35,16 +35,21 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): @bot.command(name='txt2img', help='Responds with an image') async def send_txt2img(ctx, *, arg): - user = 'tests' + user = 'testworker3' status_msg = 'status' params = { 'prompt': arg, 'seed': None, 'step': 35, - 'guidance': 1, + 'guidance': 7.5, + 'strength': 0.5, + 'width': 512, + 'height': 512, + 'upscaler': None, + 'model': 'prompthero/openjourney', } - # import pdb; pdb.set_trace() - ec = await work_request(user, status_msg, 'txt2img', params) + + ec = await work_request(user, status_msg, 'txt2img', params, ctx) print(ec) # if ec == 0: From 609c741ae9ba047d97a06d92e0c05188568b1457 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 00:58:55 -0400 Subject: [PATCH 04/36] get db, config, help, cool, and txt2img working --- skynet/cli.py | 2 +- skynet/frontend/discord/__init__.py | 47 ++++++------ skynet/frontend/discord/handlers.py | 110 ++++++++++++++++++++++------ 3 files changed, 112 insertions(+), 47 deletions(-) diff --git a/skynet/cli.py b/skynet/cli.py index 34c4562..ce4c351 100755 --- a/skynet/cli.py +++ b/skynet/cli.py @@ -485,7 +485,7 @@ def discord( permission, node_url, hyperion_url, - # db_host, db_user, db_pass, + db_host, db_user, db_pass, remote_ipfs_node=ipfs_url, key=key ) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 1c0df56..445eade 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -40,9 +40,9 @@ class SkynetDiscordFrontend: permission: str, node_url: str, hyperion_url: str, - # db_host: str, - # db_user: str, - # db_pass: str, + db_host: str, + db_user: str, + db_pass: str, remote_ipfs_node: str, key: str ): @@ -51,9 +51,9 @@ class SkynetDiscordFrontend: self.permission = permission self.node_url = node_url self.hyperion_url = hyperion_url - # self.db_host = db_host - # self.db_user = db_user - # self.db_pass = db_pass + self.db_host = db_host + self.db_user = db_user + self.db_pass = db_pass self.remote_ipfs_node = remote_ipfs_node self.key = key @@ -72,9 +72,9 @@ class SkynetDiscordFrontend: logging.info( f'connected to remote ipfs node: {self.remote_ipfs_node}') - # self.db_call = await self._async_exit_stack.enter_async_context( - # open_database_connection( - # self.db_user, self.db_pass, self.db_host)) + self.db_call = await self._async_exit_stack.enter_async_context( + open_database_connection( + self.db_user, self.db_pass, self.db_host)) create_handler_context(self) @@ -143,10 +143,10 @@ class SkynetDiscordFrontend: # f'[{timestamp_pretty()}] broadcasting transaction to chain...', # parse_mode='HTML' # ) - message = await ctx.send( - f'processing a \'{method}\' request by {user}\n \ - [{timestamp_pretty()}] *broadcasting transaction to chain...*' - ) + # message = await ctx.send( + # f'processing a \'{method}\' request by {user}\n \ + # [{timestamp_pretty()}] *broadcasting transaction to chain...*' + # ) reward = '20.0000 GPU' res = await self.cleos.a_push_action( @@ -183,12 +183,11 @@ class SkynetDiscordFrontend: # f'[{timestamp_pretty()}] workers are processing request...', # parse_mode='HTML' # ) - - await message.edit( - f'**broadcasted!**\n \ - **{enqueue_tx_link}**\n \ - [{timestamp_pretty()}] *workers are processing request...*' - ) + # await message.edit(content= + # f'**broadcasted!**\n \ + # **{enqueue_tx_link}**\n \ + # [{timestamp_pretty()}] *workers are processing request...*' + # ) out = collect_stdout(res) @@ -250,11 +249,11 @@ class SkynetDiscordFrontend: # f'[{timestamp_pretty()}] trying to download image...\n', # parse_mode='HTML' # ) - await message.edit( - f'**request processed!**\n \ - **{tx_link}**\n \ - [{timestamp_pretty()}] *trying to download image...*\n' - ) + # await message.edit(content= + # f'**request processed!**\n \ + # **{tx_link}**\n \ + # [{timestamp_pretty()}] *trying to download image...*\n' + # ) # attempt to get the image and send it ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index cbde27e..a72ab72 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -17,46 +17,112 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): bot = frontend.bot cleos = frontend.cleos - # db_call = frontend.db_call + db_call = frontend.db_call work_request = frontend.work_request ipfs_node = frontend.ipfs_node @bot.command(name='config', help='Responds with the configuration') - async def config(ctx): - response = "This is the bot configuration" # Put your bot configuration here - await ctx.send(response) + async def set_config(ctx): + + user = ctx.author + try: + attr, val, reply_txt = validate_user_config_request( + ctx.message.content) + + logging.info(f'user config update: {attr} to {val}') + await db_call('update_user_config', user.id, attr, val) + logging.info('done') + + except BaseException as e: + reply_txt = str(e) + + finally: + await ctx.reply(content=reply_txt) @bot.command(name='helper', help='Responds with a help') async def helper(ctx): - response = "This is help information" # Put your help response here - await ctx.send(response) + splt_msg = ctx.message.content.split(' ') + + if len(splt_msg) == 1: + await ctx.reply(content=HELP_TEXT) + + else: + param = splt_msg[1] + if param in HELP_TOPICS: + await ctx.reply(content=HELP_TOPICS[param]) + + else: + await ctx.reply(content=HELP_UNKWNOWN_PARAM) + + @bot.command(name='cool', help='Display a list of cool prompt words') + async def send_cool_words(ctx): + await ctx.reply(content='\n'.join(COOL_WORDS)) @bot.command(name='txt2img', help='Responds with an image') - async def send_txt2img(ctx, *, arg): - user = 'testworker3' - status_msg = 'status' + async def send_txt2img(ctx): + + # grab user from ctx + user = ctx.author + user_row = await db_call('get_or_create_user', user.id) + + # init new msg + init_msg = 'started processing txt2img request...' + status_msg = await ctx.reply(init_msg) + await db_call( + 'new_user_request', user.id, ctx.message.id, status_msg.id, status=init_msg) + + prompt = ' '.join(ctx.message.content.split(' ')[1:]) + + if len(prompt) == 0: + await status_msg.edit(content= + 'Empty text prompt ignored.' + ) + await db_call('update_user_request', status_msg.id, 'Empty text prompt ignored.') + return + + logging.info(f'mid: {ctx.message.id}') + + user_config = {**user_row} + del user_config['id'] + params = { - 'prompt': arg, - 'seed': None, - 'step': 35, - 'guidance': 7.5, - 'strength': 0.5, - 'width': 512, - 'height': 512, - 'upscaler': None, - 'model': 'prompthero/openjourney', + 'prompt': prompt, + **user_config } - ec = await work_request(user, status_msg, 'txt2img', params, ctx) - print(ec) + await db_call( + 'update_user_stats', user.id, 'txt2img', last_prompt=prompt) + + ec = await work_request(user.name, status_msg, 'txt2img', params, ctx) + + if ec == 0: + await db_call('increment_generated', user.id) + + # TODO: DELETE BELOW + # user = 'testworker3' + # status_msg = 'status' + # params = { + # 'prompt': arg, + # 'seed': None, + # 'step': 35, + # 'guidance': 7.5, + # 'strength': 0.5, + # 'width': 512, + # 'height': 512, + # 'upscaler': None, + # 'model': 'prompthero/openjourney', + # } + # + # ec = await work_request(user, status_msg, 'txt2img', params, ctx) + # print(ec) # if ec == 0: # await db_call('increment_generated', user.id) - response = f"This is your prompt: {arg}" - await ctx.send(response) + # response = f"This is your prompt: {arg}" + # await ctx.send(response) # generic / simple handlers From 06827a0e7090928484308a000597e42168dec7d0 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 12:28:57 -0400 Subject: [PATCH 05/36] add block on any channel but skynet --- skynet/cli.py | 2 +- skynet/frontend/discord/bot.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/skynet/cli.py b/skynet/cli.py index ce4c351..385b056 100755 --- a/skynet/cli.py +++ b/skynet/cli.py @@ -439,7 +439,7 @@ def telegram( @run.command() @click.option('--loglevel', '-l', default='INFO', help='logging level') @click.option( - '--account', '-a', default=None) + '--account', '-a', default='discord') @click.option( '--permission', '-p', default='active') @click.option( diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index d8ecb3b..c82d924 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -47,6 +47,11 @@ class DiscordBot(commands.Bot): print(self.user.id) print("==============") + async def on_message(self, message): + if message.channel.name != 'skynet': + return + await self.process_commands(message) + async def on_command_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): await ctx.send('You missed a required argument, please try again.') From c7cd1503168f2fccc021c9f96cf66b8f3d50c085 Mon Sep 17 00:00:00 2001 From: zoltan Date: Wed, 19 Jul 2023 17:05:13 +0000 Subject: [PATCH 06/36] remove print of a_push_action res --- skynet/frontend/discord/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 445eade..8216956 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -161,7 +161,7 @@ class SkynetDiscordFrontend: }, self.account, self.key, permission=self.permission ) - print(res) + # print(res) if 'code' in res or 'statusCode' in res: logging.error(json.dumps(res, indent=4)) From ecd7d17bbf83d78bbfe6b4412ac65576658120e0 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 13:23:20 -0400 Subject: [PATCH 07/36] change image gen to a reply --- skynet/frontend/discord/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 8216956..9fee423 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -298,6 +298,6 @@ class SkynetDiscordFrontend: # reply_markup=build_redo_menu(), # parse_mode='HTML' # ) - await ctx.send( + await ctx.reply( file=discord.File(image, 'image.png') ) From d22c0556d4314b1a32375c85578e212861b0a617 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 13:28:08 -0400 Subject: [PATCH 08/36] add clean cool words and remove slut from copy --- skynet/constants.py | 22 ++++++++++++++++++++++ skynet/frontend/discord/handlers.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/skynet/constants.py b/skynet/constants.py index d3e319d..fae7805 100644 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -82,6 +82,28 @@ COOL_WORDS = [ 'michelangelo' ] +CLEAN_COOL_WORDS = [ + 'cyberpunk', + 'soviet propaganda poster', + 'rastafari', + 'cannabis', + 'art deco', + 'H R Giger Necronom IV', + 'dimethyltryptamine', + 'lysergic', + 'psilocybin', + 'trippy', + 'lucy in the sky with diamonds', + 'fractal', + 'da vinci', + 'pencil illustration', + 'blueprint', + 'internal diagram', + 'baroque', + 'the last judgment', + 'michelangelo' +] + HELP_TOPICS = { 'step': ''' Diffusion models are iterative processes – a repeated cycle that starts with a\ diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index a72ab72..2919df8 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -58,7 +58,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): @bot.command(name='cool', help='Display a list of cool prompt words') async def send_cool_words(ctx): - await ctx.reply(content='\n'.join(COOL_WORDS)) + await ctx.reply(content='\n'.join(CLEAN_COOL_WORDS)) @bot.command(name='txt2img', help='Responds with an image') async def send_txt2img(ctx): From 239faed71f975fe84c923b5c8d9a51be4b2bff1c Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 13:40:11 -0400 Subject: [PATCH 09/36] add redo command --- skynet/frontend/discord/handlers.py | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index 2919df8..8135d0c 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -100,6 +100,44 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): if ec == 0: await db_call('increment_generated', user.id) + @bot.command(name='redo', help='Redo last request') + async def redo(ctx): + init_msg = 'started processing redo request...' + status_msg = await ctx.reply(init_msg) + user = ctx.author + + method = await db_call('get_last_method_of', user.id) + prompt = await db_call('get_last_prompt_of', user.id) + + file_id = None + binary = '' + if method == 'img2img': + file_id = await db_call('get_last_file_of', user.id) + binary = await db_call('get_last_binary_of', user.id) + + if not prompt: + await ctx.reply( + 'no last prompt found, do a txt2img cmd first!' + ) + return + + user_row = await db_call('get_or_create_user', user.id) + await db_call( + 'new_user_request', user.id, message.id, status_msg.id, status=init_msg) + user_config = {**user_row} + del user_config['id'] + + params = { + 'prompt': prompt, + **user_config + } + + await work_request( + user, status_msg, 'redo', params, + file_id=file_id, + binary_data=binary + ) + # TODO: DELETE BELOW # user = 'testworker3' # status_msg = 'status' From 99744bab3e6aa8669a1f4e913b44acf1243ec5a7 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 13:48:06 -0400 Subject: [PATCH 10/36] fix red --- skynet/frontend/discord/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index 8135d0c..6262b28 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -123,7 +123,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): user_row = await db_call('get_or_create_user', user.id) await db_call( - 'new_user_request', user.id, message.id, status_msg.id, status=init_msg) + 'new_user_request', user.id, ctx.message.id, status_msg.id, status=init_msg) user_config = {**user_row} del user_config['id'] @@ -133,7 +133,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): } await work_request( - user, status_msg, 'redo', params, + user, status_msg, 'redo', params, ctx, file_id=file_id, binary_data=binary ) From 6e8d43e00c9efb99fc3dbe30a90b7611b84136f7 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 15:21:57 -0400 Subject: [PATCH 11/36] add new models --- skynet/constants.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/skynet/constants.py b/skynet/constants.py index fae7805..727cd76 100644 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -5,15 +5,17 @@ VERSION = '0.1a10' DOCKER_RUNTIME_CUDA = 'skynet:runtime-cuda' MODELS = { - 'prompthero/openjourney': { 'short': 'midj'}, - 'runwayml/stable-diffusion-v1-5': { 'short': 'stable'}, - 'Linaqruf/anything-v3.0': { 'short': 'hdanime'}, - 'hakurei/waifu-diffusion': { 'short': 'waifu'}, - 'nitrosocke/Ghibli-Diffusion': { 'short': 'ghibli'}, - 'dallinmackay/Van-Gogh-diffusion': { 'short': 'van-gogh'}, - 'lambdalabs/sd-pokemon-diffusers': { 'short': 'pokemon'}, - 'Envvi/Inkpunk-Diffusion': { 'short': 'ink'}, - 'nousr/robo-diffusion': { 'short': 'robot'} + 'prompthero/openjourney': { 'short': 'midj'}, + 'runwayml/stable-diffusion-v1-5': { 'short': 'stable'}, + 'stabilityai/stable-diffusion-2-1': { 'short': 'stable2'}, + 'stabilityai/stable-diffusion-xl-base-0.9': { 'short': 'stablexl'}, + 'Linaqruf/anything-v3.0': { 'short': 'hdanime'}, + 'hakurei/waifu-diffusion': { 'short': 'waifu'}, + 'nitrosocke/Ghibli-Diffusion': { 'short': 'ghibli'}, + 'dallinmackay/Van-Gogh-diffusion': { 'short': 'van-gogh'}, + 'lambdalabs/sd-pokemon-diffusers': { 'short': 'pokemon'}, + 'Envvi/Inkpunk-Diffusion': { 'short': 'ink'}, + 'nousr/robo-diffusion': { 'short': 'robot'} } SHORT_NAMES = [ From bcb499448e9882c2eb8eae3c45ef89607b390a62 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 15:34:06 -0400 Subject: [PATCH 12/36] add stable2 and stablexl models --- skynet/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/constants.py b/skynet/constants.py index 727cd76..1bae4b1 100644 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -8,7 +8,7 @@ MODELS = { 'prompthero/openjourney': { 'short': 'midj'}, 'runwayml/stable-diffusion-v1-5': { 'short': 'stable'}, 'stabilityai/stable-diffusion-2-1': { 'short': 'stable2'}, - 'stabilityai/stable-diffusion-xl-base-0.9': { 'short': 'stablexl'}, + 'snowkidy/stable-diffusion-xl-base-0.9': { 'short': 'stablexl'}, 'Linaqruf/anything-v3.0': { 'short': 'hdanime'}, 'hakurei/waifu-diffusion': { 'short': 'waifu'}, 'nitrosocke/Ghibli-Diffusion': { 'short': 'ghibli'}, From ae348c7c6fd679cba1475c12e0860a16a888f4b8 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 16:11:48 -0400 Subject: [PATCH 13/36] add param for stablexl --- skynet/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skynet/utils.py b/skynet/utils.py index 2837118..177f62b 100644 --- a/skynet/utils.py +++ b/skynet/utils.py @@ -77,6 +77,10 @@ def pipeline_for(model: str, mem_fraction: float = 1.0, image=False) -> Diffusio 'safety_checker': None } + if model == 'snowkidy/stable-diffusion-xl-base-0.9': + # TODO: figure out what this does + params['addition_embed_type'] = None + if model == 'runwayml/stable-diffusion-v1-5': params['revision'] = 'fp16' From 08da0681cd317f1a0b415f20fb58fb8096db0ce7 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 16:14:03 -0400 Subject: [PATCH 14/36] add param for stablexl --- skynet/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/utils.py b/skynet/utils.py index 177f62b..ad13a15 100644 --- a/skynet/utils.py +++ b/skynet/utils.py @@ -79,7 +79,7 @@ def pipeline_for(model: str, mem_fraction: float = 1.0, image=False) -> Diffusio if model == 'snowkidy/stable-diffusion-xl-base-0.9': # TODO: figure out what this does - params['addition_embed_type'] = None + params['addition_embed_type'] = { 'text_time': None } if model == 'runwayml/stable-diffusion-v1-5': params['revision'] = 'fp16' From ff0114d3414834ace6896c7117d7b88cc9f7cef3 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Wed, 19 Jul 2023 17:25:28 -0400 Subject: [PATCH 15/36] change stable2 model and how stablexl loads, add reqs --- requirements.cuda.0.txt | 3 ++- skynet/constants.py | 2 +- skynet/utils.py | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) mode change 100644 => 100755 skynet/constants.py mode change 100644 => 100755 skynet/utils.py diff --git a/requirements.cuda.0.txt b/requirements.cuda.0.txt index e31de88..f796537 100644 --- a/requirements.cuda.0.txt +++ b/requirements.cuda.0.txt @@ -3,6 +3,7 @@ triton accelerate transformers huggingface_hub -diffusers[torch] +diffusers[torch]>=0.18.0 +invisible-watermark torch==1.13.0+cu117 --extra-index-url https://download.pytorch.org/whl/cu117 diff --git a/skynet/constants.py b/skynet/constants.py old mode 100644 new mode 100755 index 1bae4b1..2bb6d3b --- a/skynet/constants.py +++ b/skynet/constants.py @@ -7,7 +7,7 @@ DOCKER_RUNTIME_CUDA = 'skynet:runtime-cuda' MODELS = { 'prompthero/openjourney': { 'short': 'midj'}, 'runwayml/stable-diffusion-v1-5': { 'short': 'stable'}, - 'stabilityai/stable-diffusion-2-1': { 'short': 'stable2'}, + 'stabilityai/stable-diffusion-2-1-base': { 'short': 'stable2'}, 'snowkidy/stable-diffusion-xl-base-0.9': { 'short': 'stablexl'}, 'Linaqruf/anything-v3.0': { 'short': 'hdanime'}, 'hakurei/waifu-diffusion': { 'short': 'waifu'}, diff --git a/skynet/utils.py b/skynet/utils.py old mode 100644 new mode 100755 index ad13a15..79932a5 --- a/skynet/utils.py +++ b/skynet/utils.py @@ -77,15 +77,13 @@ def pipeline_for(model: str, mem_fraction: float = 1.0, image=False) -> Diffusio 'safety_checker': None } - if model == 'snowkidy/stable-diffusion-xl-base-0.9': - # TODO: figure out what this does - params['addition_embed_type'] = { 'text_time': None } - if model == 'runwayml/stable-diffusion-v1-5': params['revision'] = 'fp16' if image: pipe_class = StableDiffusionImg2ImgPipeline + elif model == 'snowkidy/stable-diffusion-xl-base-0.9': + pipe_class = DiffusionPipeline else: pipe_class = StableDiffusionPipeline From 8625b5747b915b510bb1c2d4fa05ff62aedae902 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 01:16:22 -0400 Subject: [PATCH 16/36] add initial buttons, help and txt2img --- skynet/frontend/discord/__init__.py | 2 +- skynet/frontend/discord/bot.py | 13 +++- skynet/frontend/discord/handlers.py | 2 +- skynet/frontend/discord/ui.py | 93 +++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 skynet/frontend/discord/ui.py diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 9fee423..f020734 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -57,7 +57,7 @@ class SkynetDiscordFrontend: self.remote_ipfs_node = remote_ipfs_node self.key = key - self.bot = DiscordBot() + self.bot = DiscordBot(self) self.cleos = CLEOS(None, None, url=node_url, remote=node_url) self.hyperion = HyperionAPI(hyperion_url) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index c82d924..97cff8e 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -4,6 +4,7 @@ import discord # from dotenv import load_dotenv # from pathlib import Path from discord.ext import commands +from .ui import SkynetView # # Auth @@ -18,7 +19,8 @@ from discord.ext import commands # Actual Discord bot. class DiscordBot(commands.Bot): - def __init__(self, *args, **kwargs): + def __init__(self, bot, *args, **kwargs): + self.bot = bot intents = discord.Intents( messages=True, guilds=True, @@ -39,7 +41,7 @@ class DiscordBot(commands.Bot): for guild in self.guilds: for channel in guild.channels: if channel.name == "skynet": - await channel.send('Skynet bot online') + await channel.send('Skynet bot online', view=SkynetView(self.bot)) print("\n==============") print("Logged in as") @@ -48,7 +50,12 @@ class DiscordBot(commands.Bot): print("==============") async def on_message(self, message): - if message.channel.name != 'skynet': + if isinstance(message.channel, discord.DMChannel): + return + elif message.channel.name != 'skynet': + return + elif message.author != self.user: + await message.channel.send('', view=SkynetView(self.bot)) return await self.process_commands(message) diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index 6262b28..bc0cf56 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -22,7 +22,6 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): ipfs_node = frontend.ipfs_node - @bot.command(name='config', help='Responds with the configuration') async def set_config(ctx): @@ -138,6 +137,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): binary_data=binary ) + # TODO: DELETE BELOW # user = 'testworker3' # status_msg = 'status' diff --git a/skynet/frontend/discord/ui.py b/skynet/frontend/discord/ui.py new file mode 100644 index 0000000..6704bbe --- /dev/null +++ b/skynet/frontend/discord/ui.py @@ -0,0 +1,93 @@ +import discord +import logging +from skynet.constants import * + + +class SkynetView(discord.ui.View): + + def __init__(self, bot): + self.bot = bot + super().__init__(timeout=None) + self.add_item(Txt2ImgButton('Txt2Img', discord.ButtonStyle.green, self.bot)) + self.add_item(HelpButton('Help', discord.ButtonStyle.grey)) + + +class Txt2ImgButton(discord.ui.Button): + + def __init__(self, label:str, style:discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style = style) + + async def callback(self, interaction): + db_call = self.bot.db_call + work_request = self.bot.work_request + msg = await grab('Text Prompt:', interaction) + # grab user from msg + user = msg.author + user_row = await db_call('get_or_create_user', user.id) + + # init new msg + init_msg = 'started processing txt2img request...' + status_msg = await msg.reply(init_msg) + await db_call( + 'new_user_request', user.id, msg.id, status_msg.id, status=init_msg) + + prompt = msg.content + + if len(prompt) == 0: + await status_msg.edit(content= + 'Empty text prompt ignored.' + ) + await db_call('update_user_request', status_msg.id, 'Empty text prompt ignored.') + return + + logging.info(f'mid: {msg.id}') + + user_config = {**user_row} + del user_config['id'] + + params = { + 'prompt': prompt, + **user_config + } + + await db_call( + 'update_user_stats', user.id, 'txt2img', last_prompt=prompt) + + ec = await work_request(user.name, status_msg, 'txt2img', params, msg) + + if ec == 0: + await db_call('increment_generated', user.id) + + +class HelpButton(discord.ui.Button): + + def __init__(self, label:str, style:discord.ButtonStyle): + super().__init__(label=label, style = style) + + async def callback(self, interaction): + msg = await grab('What would you like help with? (a for all)', interaction) + + param = msg.content + + if param == 'a': + await msg.reply(content=HELP_TEXT) + + else: + if param in HELP_TOPICS: + await msg.reply(content=HELP_TOPICS[param]) + + else: + await msg.reply(content=HELP_UNKWNOWN_PARAM) + + + +async def grab(prompt, interaction): + def vet(m): + return m.author == interaction.user and m.channel == interaction.channel + + await interaction.response.send_message(prompt, ephemeral=True) + message = await interaction.client.wait_for('message', check=vet) + return message + + From 2b2e82e28f54cd3142cbd029c439f9a3f30adb45 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 01:24:49 -0400 Subject: [PATCH 17/36] fix /command bug --- skynet/frontend/discord/bot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index 97cff8e..e467e5f 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -55,9 +55,8 @@ class DiscordBot(commands.Bot): elif message.channel.name != 'skynet': return elif message.author != self.user: - await message.channel.send('', view=SkynetView(self.bot)) - return - await self.process_commands(message) + await self.process_commands(message) + await message.channel.send('', view=SkynetView(self.bot)) async def on_command_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): From 0eef370d157ebbd0e7fba0445d22b3f3afb35954 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 01:27:21 -0400 Subject: [PATCH 18/36] fix /command bug 2 --- skynet/frontend/discord/bot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index e467e5f..665e8d5 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -54,8 +54,9 @@ class DiscordBot(commands.Bot): return elif message.channel.name != 'skynet': return - elif message.author != self.user: - await self.process_commands(message) + elif message.author == self.user: + return + await self.process_commands(message) await message.channel.send('', view=SkynetView(self.bot)) async def on_command_error(self, ctx, error): From 2e47ee97f2c53e6975bb31efb22f893d6f801f30 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 01:35:54 -0400 Subject: [PATCH 19/36] add delay for button to reappear --- skynet/frontend/discord/bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index 665e8d5..cde4144 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -1,6 +1,6 @@ # import os import discord -# import asyncio +import asyncio # from dotenv import load_dotenv # from pathlib import Path from discord.ext import commands @@ -57,6 +57,7 @@ class DiscordBot(commands.Bot): elif message.author == self.user: return await self.process_commands(message) + await asyncio.sleep(3) await message.channel.send('', view=SkynetView(self.bot)) async def on_command_error(self, ctx, error): From 2440fe32db2785700f6c04fc3d244091ef9d57d5 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 17:02:14 -0400 Subject: [PATCH 20/36] update max size params and add discord return card --- skynet/constants.py | 4 +-- skynet/frontend/discord/__init__.py | 21 ++++++++----- skynet/frontend/discord/ui.py | 2 +- skynet/frontend/discord/utils.py | 47 +++++++++++++++-------------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/skynet/constants.py b/skynet/constants.py index 2bb6d3b..0ddfa65 100755 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -133,8 +133,8 @@ MP_ENABLED_ROLES = ['god'] MIN_STEP = 1 MAX_STEP = 100 -MAX_WIDTH = 512 -MAX_HEIGHT = 656 +MAX_WIDTH = 1024 +MAX_HEIGHT = 1024 MAX_GUIDANCE = 20 DEFAULT_SEED = None diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index f020734..29809e8 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -116,10 +116,12 @@ class SkynetDiscordFrontend: status_msg, method: str, params: dict, - ctx: discord.TextChannel, + ctx: discord.ext.commands.context.Context | discord.Message, file_id: str | None = None, binary_data: str = '' ): + send = ctx.channel.send + if params['seed'] == None: params['seed'] = random.randint(0, 0xFFFFFFFF) @@ -258,9 +260,9 @@ class SkynetDiscordFrontend: # attempt to get the image and send it ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' resp = await get_ipfs_file(ipfs_link) - - # caption = generate_reply_caption( - # user, params, tx_hash, worker, reward) + + caption, embed = generate_reply_caption( + user, params, tx_hash, worker, reward) if not resp or resp.status_code != 200: logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!') @@ -273,7 +275,9 @@ class SkynetDiscordFrontend: # else: logging.info(f'success! sending generated image') - image = io.BytesIO(resp.raw) + # image = io.BytesIO(resp.raw) + # embed.set_image(url=ipfs_link) + # embed.add_field(name='params', value=caption) # await self.bot.delete_message( # chat_id=status_msg.chat.id, message_id=status_msg.id) if file_id: # img2img @@ -298,6 +302,7 @@ class SkynetDiscordFrontend: # reply_markup=build_redo_menu(), # parse_mode='HTML' # ) - await ctx.reply( - file=discord.File(image, 'image.png') - ) + + embed.set_image(url=ipfs_link) + embed.add_field(name='Parameters:', value=caption) + await send(embed=embed) diff --git a/skynet/frontend/discord/ui.py b/skynet/frontend/discord/ui.py index 6704bbe..cb54d5c 100644 --- a/skynet/frontend/discord/ui.py +++ b/skynet/frontend/discord/ui.py @@ -54,7 +54,7 @@ class Txt2ImgButton(discord.ui.Button): await db_call( 'update_user_stats', user.id, 'txt2img', last_prompt=prompt) - ec = await work_request(user.name, status_msg, 'txt2img', params, msg) + ec = await work_request(user, status_msg, 'txt2img', params, msg) if ec == 0: await db_call('increment_generated', user.id) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index ad08bba..81724b9 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -9,6 +9,7 @@ from datetime import datetime, timezone from telebot.types import InlineKeyboardButton, InlineKeyboardMarkup from telebot.async_telebot import ExceptionHandler from telebot.formatting import hlink +import discord from skynet.constants import * @@ -37,59 +38,59 @@ def build_redo_menu(): return inline_keyboard -def prepare_metainfo_caption(tguser, worker: str, reward: str, meta: dict) -> str: +def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict) -> str: prompt = meta["prompt"] if len(prompt) > 256: prompt = prompt[:256] + meta_str = f'__by {user.name}__\n' + meta_str += f'*performed by {worker}*\n' + meta_str += f'__**reward: {reward}**__\n' - meta_str = f'by {tg_user_pretty(tguser)}\n' - meta_str += f'performed by {worker}\n' - meta_str += f'reward: {reward}\n' - - meta_str += f'prompt: {prompt}\n' - meta_str += f'seed: {meta["seed"]}\n' - meta_str += f'step: {meta["step"]}\n' - meta_str += f'guidance: {meta["guidance"]}\n' + meta_str += f'`prompt:` {prompt}\n' + meta_str += f'`seed: {meta["seed"]}`\n' + meta_str += f'`step: {meta["step"]}`\n' + meta_str += f'`guidance: {meta["guidance"]}`\n' if meta['strength']: - meta_str += f'strength: {meta["strength"]}\n' - meta_str += f'algo: {meta["model"]}\n' + meta_str += f'`strength: {meta["strength"]}`\n' + meta_str += f'`algo: {meta["model"]}`\n' if meta['upscaler']: - meta_str += f'upscaler: {meta["upscaler"]}\n' + meta_str += f'`upscaler: {meta["upscaler"]}`\n' - meta_str += f'Made with Skynet v{VERSION}\n' - meta_str += f'JOIN THE SWARM: @skynetgpu' + meta_str += f'__**Made with Skynet v{VERSION}**__\n' + meta_str += f'**JOIN THE SWARM: @skynetgpu**' return meta_str def generate_reply_caption( - tguser, # telegram user + user, # discord user params: dict, tx_hash: str, worker: str, reward: str ): - explorer_link = hlink( - 'SKYNET Transaction Explorer', - f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}' - ) + explorer_link = discord.Embed( + title='[SKYNET Transaction Explorer]', + url=f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}', + color=discord.Color.blue()) - meta_info = prepare_metainfo_caption(tguser, worker, reward, params) + meta_info = prepare_metainfo_caption(user, worker, reward, params) + # why do we have this? final_msg = '\n'.join([ 'Worker finished your task!', - explorer_link, + # explorer_link, f'PARAMETER INFO:\n{meta_info}' ]) final_msg = '\n'.join([ - f'{explorer_link}', + # f'***{explorer_link}***', f'{meta_info}' ]) logging.info(final_msg) - return final_msg + return final_msg, explorer_link async def get_global_config(cleos): From 53ed74e9a3fd309aa36837801aaaf67258b953ef Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Thu, 20 Jul 2023 20:54:59 -0400 Subject: [PATCH 21/36] add and finalize buttons --- skynet/frontend/discord/__init__.py | 5 +- skynet/frontend/discord/bot.py | 4 +- skynet/frontend/discord/handlers.py | 13 ++-- skynet/frontend/discord/ui.py | 109 +++++++++++++++++++++++++--- 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 29809e8..50878d3 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -29,6 +29,7 @@ from .bot import DiscordBot from .utils import * from .handlers import create_handler_context +from .ui import SkynetView class SkynetDiscordFrontend: @@ -260,7 +261,7 @@ class SkynetDiscordFrontend: # attempt to get the image and send it ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' resp = await get_ipfs_file(ipfs_link) - + caption, embed = generate_reply_caption( user, params, tx_hash, worker, reward) @@ -305,4 +306,4 @@ class SkynetDiscordFrontend: embed.set_image(url=ipfs_link) embed.add_field(name='Parameters:', value=caption) - await send(embed=embed) + await send(embed=embed, view=SkynetView(self)) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index cde4144..ac8744f 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -57,8 +57,8 @@ class DiscordBot(commands.Bot): elif message.author == self.user: return await self.process_commands(message) - await asyncio.sleep(3) - await message.channel.send('', view=SkynetView(self.bot)) + # await asyncio.sleep(3) + # await message.channel.send('', view=SkynetView(self.bot)) async def on_command_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index bc0cf56..dbf19f4 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -11,6 +11,7 @@ from PIL import Image from skynet.frontend import validate_user_config_request from skynet.constants import * +from .ui import SkynetView def create_handler_context(frontend: 'SkynetDiscordFrontend'): @@ -38,26 +39,26 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): reply_txt = str(e) finally: - await ctx.reply(content=reply_txt) + await ctx.reply(content=reply_txt, view=SkynetView(frontend)) @bot.command(name='helper', help='Responds with a help') async def helper(ctx): splt_msg = ctx.message.content.split(' ') if len(splt_msg) == 1: - await ctx.reply(content=HELP_TEXT) + await ctx.reply(content=HELP_TEXT, view=SkynetView(frontend)) else: param = splt_msg[1] if param in HELP_TOPICS: - await ctx.reply(content=HELP_TOPICS[param]) + await ctx.reply(content=HELP_TOPICS[param], view=SkynetView(frontend)) else: - await ctx.reply(content=HELP_UNKWNOWN_PARAM) + await ctx.reply(content=HELP_UNKWNOWN_PARAM, view=SkynetView(frontend)) @bot.command(name='cool', help='Display a list of cool prompt words') async def send_cool_words(ctx): - await ctx.reply(content='\n'.join(CLEAN_COOL_WORDS)) + await ctx.reply(content='\n'.join(CLEAN_COOL_WORDS), view=SkynetView(frontend)) @bot.command(name='txt2img', help='Responds with an image') async def send_txt2img(ctx): @@ -94,7 +95,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): await db_call( 'update_user_stats', user.id, 'txt2img', last_prompt=prompt) - ec = await work_request(user.name, status_msg, 'txt2img', params, ctx) + ec = await work_request(user, status_msg, 'txt2img', params, ctx) if ec == 0: await db_call('increment_generated', user.id) diff --git a/skynet/frontend/discord/ui.py b/skynet/frontend/discord/ui.py index cb54d5c..0f9452e 100644 --- a/skynet/frontend/discord/ui.py +++ b/skynet/frontend/discord/ui.py @@ -1,6 +1,7 @@ import discord import logging from skynet.constants import * +from skynet.frontend import validate_user_config_request class SkynetView(discord.ui.View): @@ -8,20 +9,23 @@ class SkynetView(discord.ui.View): def __init__(self, bot): self.bot = bot super().__init__(timeout=None) - self.add_item(Txt2ImgButton('Txt2Img', discord.ButtonStyle.green, self.bot)) - self.add_item(HelpButton('Help', discord.ButtonStyle.grey)) + self.add_item(RedoButton('redo', discord.ButtonStyle.green, self.bot)) + self.add_item(Txt2ImgButton('txt2img', discord.ButtonStyle.green, self.bot)) + self.add_item(ConfigButton('config', discord.ButtonStyle.grey, self.bot)) + self.add_item(HelpButton('help', discord.ButtonStyle.grey, self.bot)) + self.add_item(CoolButton('cool', discord.ButtonStyle.gray, self.bot)) class Txt2ImgButton(discord.ui.Button): - def __init__(self, label:str, style:discord.ButtonStyle, bot): + def __init__(self, label: str, style: discord.ButtonStyle, bot): self.bot = bot - super().__init__(label=label, style = style) + super().__init__(label=label, style=style) async def callback(self, interaction): db_call = self.bot.db_call work_request = self.bot.work_request - msg = await grab('Text Prompt:', interaction) + msg = await grab('Enter your prompt:', interaction) # grab user from msg user = msg.author user_row = await db_call('get_or_create_user', user.id) @@ -60,10 +64,95 @@ class Txt2ImgButton(discord.ui.Button): await db_call('increment_generated', user.id) +class RedoButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + db_call = self.bot.db_call + work_request = self.bot.work_request + init_msg = 'started processing redo request...' + await interaction.response.send_message(init_msg) + status_msg = await interaction.original_response() + user = interaction.user + + method = await db_call('get_last_method_of', user.id) + prompt = await db_call('get_last_prompt_of', user.id) + + file_id = None + binary = '' + if method == 'img2img': + file_id = await db_call('get_last_file_of', user.id) + binary = await db_call('get_last_binary_of', user.id) + + if not prompt: + await interaction.response.edit_message( + 'no last prompt found, do a txt2img cmd first!' + ) + return + + user_row = await db_call('get_or_create_user', user.id) + await db_call( + 'new_user_request', user.id, interaction.id, status_msg.id, status=init_msg) + user_config = {**user_row} + del user_config['id'] + + params = { + 'prompt': prompt, + **user_config + } + await work_request( + user, status_msg, 'redo', params, interaction, + file_id=file_id, + binary_data=binary + ) + + +class ConfigButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + db_call = self.bot.db_call + msg = await grab('What params do you want to change? (format: )', interaction) + + user = interaction.user + try: + attr, val, reply_txt = validate_user_config_request( + '/config ' + msg.content) + + logging.info(f'user config update: {attr} to {val}') + await db_call('update_user_config', user.id, attr, val) + logging.info('done') + + except BaseException as e: + reply_txt = str(e) + + finally: + await msg.reply(content=reply_txt, view=SkynetView(self.bot)) + + +class CoolButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + await interaction.response.send_message( + content='\n'.join(CLEAN_COOL_WORDS), + view=SkynetView(self.bot)) + + class HelpButton(discord.ui.Button): - def __init__(self, label:str, style:discord.ButtonStyle): - super().__init__(label=label, style = style) + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) async def callback(self, interaction): msg = await grab('What would you like help with? (a for all)', interaction) @@ -71,14 +160,14 @@ class HelpButton(discord.ui.Button): param = msg.content if param == 'a': - await msg.reply(content=HELP_TEXT) + await msg.reply(content=HELP_TEXT, view=SkynetView(self.bot)) else: if param in HELP_TOPICS: - await msg.reply(content=HELP_TOPICS[param]) + await msg.reply(content=HELP_TOPICS[param], view=SkynetView(self.bot)) else: - await msg.reply(content=HELP_UNKWNOWN_PARAM) + await msg.reply(content=HELP_UNKWNOWN_PARAM, view=SkynetView(self.bot)) From 58c6a2070e4f49b20b7fd5d212ba1034ca1d2faa Mon Sep 17 00:00:00 2001 From: zoltan Date: Fri, 21 Jul 2023 03:43:59 +0000 Subject: [PATCH 22/36] change send function on failure (not sure if this fixed it) --- skynet/frontend/discord/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 50878d3..656f84f 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -168,7 +168,7 @@ class SkynetDiscordFrontend: if 'code' in res or 'statusCode' in res: logging.error(json.dumps(res, indent=4)) - await self.bot.send( + await self.bot.channel.send( status_msg, 'skynet has suffered an internal error trying to fill this request') return From 426018720882fe6e52922aee2d079d9f5c057503 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 16:57:54 -0400 Subject: [PATCH 23/36] add img2img support, add stats and donate button, finalize UI, add live updates --- skynet/constants.py | 1 + skynet/db/functions.py | 3 +- skynet/frontend/discord/__init__.py | 125 +++++++-------------- skynet/frontend/discord/handlers.py | 139 +++++++++++++++++++++--- skynet/frontend/discord/ui.py | 161 +++++++++++++++++++++++++--- skynet/frontend/discord/utils.py | 42 +++++--- 6 files changed, 344 insertions(+), 127 deletions(-) diff --git a/skynet/constants.py b/skynet/constants.py index 0ddfa65..4dc1c48 100755 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -36,6 +36,7 @@ commands work on a user per user basis! config is individual to each user! /txt2img TEXT - request an image based on a prompt +/img2img TEXT - request an image base on an image and a promtp /redo - redo last command (only works for txt2img for now!) diff --git a/skynet/db/functions.py b/skynet/db/functions.py index d98b099..f52703e 100644 --- a/skynet/db/functions.py +++ b/skynet/db/functions.py @@ -96,7 +96,8 @@ def open_new_database(cleanup=True): 'POSTGRES_PASSWORD': rpassword }, detach=True, - remove=True + # could remove this if we ant the dockers to be persistent. + # remove=True ) try: diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 656f84f..7ab0f17 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -89,6 +89,7 @@ class SkynetDiscordFrontend: yield self await self.stop() + # maybe do this? # async def update_status_message( # self, status_msg, new_text: str, **kwargs # ): @@ -139,17 +140,14 @@ class SkynetDiscordFrontend: }) request_time = datetime.now().isoformat() - # maybe get rid of this - # await self.update_status_message( - # status_msg, - # f'processing a \'{method}\' request by {tg_user_pretty(user)}\n' - # f'[{timestamp_pretty()}] broadcasting transaction to chain...', - # parse_mode='HTML' - # ) - # message = await ctx.send( - # f'processing a \'{method}\' request by {user}\n \ - # [{timestamp_pretty()}] *broadcasting transaction to chain...*' - # ) + await status_msg.delete() + msg_text = f'processing a \'{method}\' request by {user.name}\n[{timestamp_pretty()}] *broadcasting transaction to chain...* ' + embed = discord.Embed( + title='live updates', + description=msg_text, + color=discord.Color.blue()) + + message = await send(embed=embed) reward = '20.0000 GPU' res = await self.cleos.a_push_action( @@ -164,7 +162,6 @@ class SkynetDiscordFrontend: }, self.account, self.key, permission=self.permission ) - # print(res) if 'code' in res or 'statusCode' in res: logging.error(json.dumps(res, indent=4)) @@ -174,23 +171,15 @@ class SkynetDiscordFrontend: return enqueue_tx_id = res['transaction_id'] - enqueue_tx_link = hlink( - 'Your request on Skynet Explorer', - f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{enqueue_tx_id}' - ) + enqueue_tx_link = f'[**Your request on Skynet Explorer**](https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{enqueue_tx_id})' - # await self.append_status_message( - # status_msg, - # f' broadcasted!\n' - # f'{enqueue_tx_link}\n' - # f'[{timestamp_pretty()}] workers are processing request...', - # parse_mode='HTML' - # ) - # await message.edit(content= - # f'**broadcasted!**\n \ - # **{enqueue_tx_link}**\n \ - # [{timestamp_pretty()}] *workers are processing request...*' - # ) + msg_text += f'**broadcasted!** \n{enqueue_tx_link}\n[{timestamp_pretty()}] *workers are processing request...* ' + embed = discord.Embed( + title='live updates', + description=msg_text, + color=discord.Color.blue()) + + await message.edit(embed=embed) out = collect_stdout(res) @@ -233,77 +222,45 @@ class SkynetDiscordFrontend: await asyncio.sleep(1) if not ipfs_hash: - # await self.update_status_message( - # status_msg, - # f'\n[{timestamp_pretty()}] timeout processing request', - # parse_mode='HTML' - # ) + + msg_text += f'\n[{timestamp_pretty()}] **timeout processing request**' + embed = discord.Embed( + title='live updates', + description=msg_text, + color=discord.Color.blue()) + + await message.edit(embed=embed) return - tx_link = hlink( - 'Your result on Skynet Explorer', - f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}' - ) + tx_link = f'[**Your result on Skynet Explorer**](https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash})' - # await self.append_status_message( - # status_msg, - # f' request processed!\n' - # f'{tx_link}\n' - # f'[{timestamp_pretty()}] trying to download image...\n', - # parse_mode='HTML' - # ) - # await message.edit(content= - # f'**request processed!**\n \ - # **{tx_link}**\n \ - # [{timestamp_pretty()}] *trying to download image...*\n' - # ) + msg_text += f'**request processed!**\n{tx_link}\n[{timestamp_pretty()}] *trying to download image...*\n ' + embed = discord.Embed( + title='live updates', + description=msg_text, + color=discord.Color.blue()) + + await message.edit(embed=embed) # attempt to get the image and send it ipfs_link = f'https://ipfs.{DEFAULT_DOMAIN}/ipfs/{ipfs_hash}/image.png' resp = await get_ipfs_file(ipfs_link) + # reword this function, may not need caption caption, embed = generate_reply_caption( user, params, tx_hash, worker, reward) if not resp or resp.status_code != 200: logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!') - # await self.update_status_message( - # status_msg, - # caption, - # reply_markup=build_redo_menu(), - # parse_mode='HTML' - # ) - # + await message.edit(embed=embed, view=SkynetView(self)) else: logging.info(f'success! sending generated image') - # image = io.BytesIO(resp.raw) - # embed.set_image(url=ipfs_link) - # embed.add_field(name='params', value=caption) - # await self.bot.delete_message( - # chat_id=status_msg.chat.id, message_id=status_msg.id) + await message.delete() if file_id: # img2img - pass - # await self.bot.send_media_group( - # status_msg.chat.id, - # media=[ - # InputMediaPhoto(file_id), - # InputMediaPhoto( - # resp.raw, - # caption=caption, - # parse_mode='HTML' - # ) - # ], - # ) - # - else: # txt2img - # await self.bot.send_photo( - # status_msg.chat.id, - # caption=caption, - # photo=resp.raw, - # reply_markup=build_redo_menu(), - # parse_mode='HTML' - # ) - + embed.set_thumbnail( + url='https://ipfs.skygpu.net/ipfs/' + binary_data + '/image.png') + embed.set_image(url=ipfs_link) + await send(embed=embed, view=SkynetView(self)) + else: # txt2img embed.set_image(url=ipfs_link) - embed.add_field(name='Parameters:', value=caption) await send(embed=embed, view=SkynetView(self)) diff --git a/skynet/frontend/discord/handlers.py b/skynet/frontend/discord/handlers.py index dbf19f4..c6f2735 100644 --- a/skynet/frontend/discord/handlers.py +++ b/skynet/frontend/discord/handlers.py @@ -41,24 +41,44 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): finally: await ctx.reply(content=reply_txt, view=SkynetView(frontend)) - @bot.command(name='helper', help='Responds with a help') - async def helper(ctx): + bot.remove_command('help') + @bot.command(name='help', help='Responds with a help') + async def help(ctx): splt_msg = ctx.message.content.split(' ') if len(splt_msg) == 1: - await ctx.reply(content=HELP_TEXT, view=SkynetView(frontend)) + await ctx.send(content=f'```{HELP_TEXT}```', view=SkynetView(frontend)) else: param = splt_msg[1] if param in HELP_TOPICS: - await ctx.reply(content=HELP_TOPICS[param], view=SkynetView(frontend)) + await ctx.send(content=f'```{HELP_TOPICS[param]}```', view=SkynetView(frontend)) else: - await ctx.reply(content=HELP_UNKWNOWN_PARAM, view=SkynetView(frontend)) + await ctx.send(content=f'```{HELP_UNKWNOWN_PARAM}```', view=SkynetView(frontend)) @bot.command(name='cool', help='Display a list of cool prompt words') async def send_cool_words(ctx): - await ctx.reply(content='\n'.join(CLEAN_COOL_WORDS), view=SkynetView(frontend)) + clean_cool_word = '\n'.join(CLEAN_COOL_WORDS) + await ctx.send(content=f'```{clean_cool_word}```', view=SkynetView(frontend)) + + @bot.command(name='stats', help='See user statistics' ) + async def user_stats(ctx): + user = ctx.author + + await db_call('get_or_create_user', user.id) + generated, joined, role = await db_call('get_user_stats', user.id) + + stats_str = f'```generated: {generated}\n' + stats_str += f'joined: {joined}\n' + stats_str += f'role: {role}\n```' + + await ctx.reply(stats_str, view=SkynetView(frontend)) + + @bot.command(name='donate', help='See donate info') + async def donation_info(ctx): + await ctx.reply( + f'```\n{DONATION_INFO}```', view=SkynetView(frontend)) @bot.command(name='txt2img', help='Responds with an image') async def send_txt2img(ctx): @@ -69,7 +89,7 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): # init new msg init_msg = 'started processing txt2img request...' - status_msg = await ctx.reply(init_msg) + status_msg = await ctx.send(init_msg) await db_call( 'new_user_request', user.id, ctx.message.id, status_msg.id, status=init_msg) @@ -97,13 +117,13 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): ec = await work_request(user, status_msg, 'txt2img', params, ctx) - if ec == 0: + if ec == None: await db_call('increment_generated', user.id) @bot.command(name='redo', help='Redo last request') async def redo(ctx): init_msg = 'started processing redo request...' - status_msg = await ctx.reply(init_msg) + status_msg = await ctx.send(init_msg) user = ctx.author method = await db_call('get_last_method_of', user.id) @@ -116,8 +136,9 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): binary = await db_call('get_last_binary_of', user.id) if not prompt: - await ctx.reply( - 'no last prompt found, do a txt2img cmd first!' + await status_msg.edit( + content='no last prompt found, do a txt2img cmd first!', + view=SkynetView(frontend) ) return @@ -132,12 +153,106 @@ def create_handler_context(frontend: 'SkynetDiscordFrontend'): **user_config } - await work_request( + ec = await work_request( user, status_msg, 'redo', params, ctx, file_id=file_id, binary_data=binary ) + if ec == None: + await db_call('increment_generated', user.id) + + @bot.command(name='img2img', help='Responds with an image') + async def send_img2img(ctx): + # if isinstance(message_or_query, CallbackQuery): + # query = message_or_query + # message = query.message + # user = query.from_user + # chat = query.message.chat + # + # else: + # message = message_or_query + # user = message.from_user + # chat = message.chat + + # reply_id = None + # if chat.type == 'group' and chat.id == GROUP_ID: + # reply_id = message.message_id + # + user = ctx.author + user_row = await db_call('get_or_create_user', user.id) + + # init new msg + init_msg = 'started processing img2img request...' + status_msg = await ctx.send(init_msg) + await db_call( + 'new_user_request', user.id, ctx.message.id, status_msg.id, status=init_msg) + + if not ctx.message.content.startswith('/img2img'): + await ctx.reply( + 'For image to image you need to add /img2img to the beggining of your caption' + ) + return + + prompt = ' '.join(ctx.message.content.split(' ')[1:]) + + if len(prompt) == 0: + await ctx.reply('Empty text prompt ignored.') + return + + # file_id = message.photo[-1].file_id + # file_path = (await bot.get_file(file_id)).file_path + # image_raw = await bot.download_file(file_path) + # + + file = ctx.message.attachments[-1] + file_id = str(file.id) + # file bytes + image_raw = await file.read() + with Image.open(io.BytesIO(image_raw)) as image: + w, h = image.size + + if w > 512 or h > 512: + logging.warning(f'user sent img of size {image.size}') + image.thumbnail((512, 512)) + logging.warning(f'resized it to {image.size}') + + image.save(f'ipfs-docker-staging/image.png', format='PNG') + + ipfs_hash = ipfs_node.add('image.png') + ipfs_node.pin(ipfs_hash) + + logging.info(f'published input image {ipfs_hash} on ipfs') + + logging.info(f'mid: {ctx.message.id}') + + user_config = {**user_row} + del user_config['id'] + + params = { + 'prompt': prompt, + **user_config + } + + await db_call( + 'update_user_stats', + user.id, + 'img2img', + last_file=file_id, + last_prompt=prompt, + last_binary=ipfs_hash + ) + + ec = await work_request( + user, status_msg, 'img2img', params, ctx, + file_id=file_id, + binary_data=ipfs_hash + ) + + if ec == None: + await db_call('increment_generated', user.id) + + # TODO: DELETE BELOW # user = 'testworker3' diff --git a/skynet/frontend/discord/ui.py b/skynet/frontend/discord/ui.py index 0f9452e..95d71b1 100644 --- a/skynet/frontend/discord/ui.py +++ b/skynet/frontend/discord/ui.py @@ -1,4 +1,6 @@ +import io import discord +from PIL import Image import logging from skynet.constants import * from skynet.frontend import validate_user_config_request @@ -9,11 +11,14 @@ class SkynetView(discord.ui.View): def __init__(self, bot): self.bot = bot super().__init__(timeout=None) - self.add_item(RedoButton('redo', discord.ButtonStyle.green, self.bot)) - self.add_item(Txt2ImgButton('txt2img', discord.ButtonStyle.green, self.bot)) - self.add_item(ConfigButton('config', discord.ButtonStyle.grey, self.bot)) - self.add_item(HelpButton('help', discord.ButtonStyle.grey, self.bot)) - self.add_item(CoolButton('cool', discord.ButtonStyle.gray, self.bot)) + self.add_item(RedoButton('redo', discord.ButtonStyle.primary, self.bot)) + self.add_item(Txt2ImgButton('txt2img', discord.ButtonStyle.primary, self.bot)) + self.add_item(Img2ImgButton('img2img', discord.ButtonStyle.primary, self.bot)) + self.add_item(StatsButton('stats', discord.ButtonStyle.secondary, self.bot)) + self.add_item(DonateButton('donate', discord.ButtonStyle.secondary, self.bot)) + self.add_item(ConfigButton('config', discord.ButtonStyle.secondary, self.bot)) + self.add_item(HelpButton('help', discord.ButtonStyle.secondary, self.bot)) + self.add_item(CoolButton('cool', discord.ButtonStyle.secondary, self.bot)) class Txt2ImgButton(discord.ui.Button): @@ -32,7 +37,7 @@ class Txt2ImgButton(discord.ui.Button): # init new msg init_msg = 'started processing txt2img request...' - status_msg = await msg.reply(init_msg) + status_msg = await msg.channel.send(init_msg) await db_call( 'new_user_request', user.id, msg.id, status_msg.id, status=init_msg) @@ -60,7 +65,93 @@ class Txt2ImgButton(discord.ui.Button): ec = await work_request(user, status_msg, 'txt2img', params, msg) - if ec == 0: + if ec == None: + await db_call('increment_generated', user.id) + + +class Img2ImgButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + db_call = self.bot.db_call + work_request = self.bot.work_request + ipfs_node = self.bot.ipfs_node + msg = await grab('Attach an Image. Enter your prompt:', interaction) + + user = msg.author + user_row = await db_call('get_or_create_user', user.id) + + # init new msg + init_msg = 'started processing img2img request...' + status_msg = await msg.channel.send(init_msg) + await db_call( + 'new_user_request', user.id, msg.id, status_msg.id, status=init_msg) + + # if not msg.content.startswith('/img2img'): + # await msg.reply( + # 'For image to image you need to add /img2img to the beggining of your caption' + # ) + # return + + prompt = msg.content + + if len(prompt) == 0: + await msg.reply('Empty text prompt ignored.') + return + + # file_id = message.photo[-1].file_id + # file_path = (await bot.get_file(file_id)).file_path + # image_raw = await bot.download_file(file_path) + # + + file = msg.attachments[-1] + file_id = str(file.id) + # file bytes + image_raw = await file.read() + with Image.open(io.BytesIO(image_raw)) as image: + w, h = image.size + + if w > 512 or h > 512: + logging.warning(f'user sent img of size {image.size}') + image.thumbnail((512, 512)) + logging.warning(f'resized it to {image.size}') + + image.save(f'ipfs-docker-staging/image.png', format='PNG') + + ipfs_hash = ipfs_node.add('image.png') + ipfs_node.pin(ipfs_hash) + + logging.info(f'published input image {ipfs_hash} on ipfs') + + logging.info(f'mid: {msg.id}') + + user_config = {**user_row} + del user_config['id'] + + params = { + 'prompt': prompt, + **user_config + } + + await db_call( + 'update_user_stats', + user.id, + 'img2img', + last_file=file_id, + last_prompt=prompt, + last_binary=ipfs_hash + ) + + ec = await work_request( + user, status_msg, 'img2img', params, msg, + file_id=file_id, + binary_data=ipfs_hash + ) + + if ec == None: await db_call('increment_generated', user.id) @@ -88,8 +179,9 @@ class RedoButton(discord.ui.Button): binary = await db_call('get_last_binary_of', user.id) if not prompt: - await interaction.response.edit_message( - 'no last prompt found, do a txt2img cmd first!' + await status_msg.edit( + content='no last prompt found, do a txt2img cmd first!', + view=SkynetView(self.bot) ) return @@ -103,12 +195,15 @@ class RedoButton(discord.ui.Button): 'prompt': prompt, **user_config } - await work_request( + ec = await work_request( user, status_msg, 'redo', params, interaction, file_id=file_id, binary_data=binary ) + if ec == None: + await db_call('increment_generated', user.id) + class ConfigButton(discord.ui.Button): @@ -136,7 +231,29 @@ class ConfigButton(discord.ui.Button): await msg.reply(content=reply_txt, view=SkynetView(self.bot)) -class CoolButton(discord.ui.Button): +class StatsButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + db_call = self.bot.db_call + + user = interaction.user + + await db_call('get_or_create_user', user.id) + generated, joined, role = await db_call('get_user_stats', user.id) + + stats_str = f'```generated: {generated}\n' + stats_str += f'joined: {joined}\n' + stats_str += f'role: {role}\n```' + + await interaction.response.send_message( + content=stats_str, view=SkynetView(self.bot)) + + +class DonateButton(discord.ui.Button): def __init__(self, label: str, style: discord.ButtonStyle, bot): self.bot = bot @@ -144,7 +261,20 @@ class CoolButton(discord.ui.Button): async def callback(self, interaction): await interaction.response.send_message( - content='\n'.join(CLEAN_COOL_WORDS), + content=f'```\n{DONATION_INFO}```', + view=SkynetView(self.bot)) + + +class CoolButton(discord.ui.Button): + + def __init__(self, label: str, style: discord.ButtonStyle, bot): + self.bot = bot + super().__init__(label=label, style=style) + + async def callback(self, interaction): + clean_cool_word = '\n'.join(CLEAN_COOL_WORDS) + await interaction.response.send_message( + content=f'```{clean_cool_word}```', view=SkynetView(self.bot)) @@ -160,15 +290,14 @@ class HelpButton(discord.ui.Button): param = msg.content if param == 'a': - await msg.reply(content=HELP_TEXT, view=SkynetView(self.bot)) + await msg.reply(content=f'```{HELP_TEXT}```', view=SkynetView(self.bot)) else: if param in HELP_TOPICS: - await msg.reply(content=HELP_TOPICS[param], view=SkynetView(self.bot)) + await msg.reply(content=f'```{HELP_TOPICS[param]}```', view=SkynetView(self.bot)) else: - await msg.reply(content=HELP_UNKWNOWN_PARAM, view=SkynetView(self.bot)) - + await msg.reply(content=f'```{HELP_UNKWNOWN_PARAM}```', view=SkynetView(self.bot)) async def grab(prompt, interaction): diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index 81724b9..1fd6618 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -38,27 +38,41 @@ def build_redo_menu(): return inline_keyboard -def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict) -> str: +def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) -> str: prompt = meta["prompt"] if len(prompt) > 256: prompt = prompt[:256] + + gen_str = f'generated by {user.name}\n' + gen_str += f'performed by {worker}\n' + gen_str += f'reward: {reward}\n' - meta_str = f'__by {user.name}__\n' - meta_str += f'*performed by {worker}*\n' - meta_str += f'__**reward: {reward}**__\n' + embed.add_field( + name='General Info', value=f'```{gen_str}```', inline=False) + # meta_str = f'__by {user.name}__\n' + # meta_str += f'*performed by {worker}*\n' + # meta_str += f'__**reward: {reward}**__\n' + embed.add_field(name='Prompt', value=f'```{prompt}\n```', inline=False) + + # meta_str = f'`prompt:` {prompt}\n' + + meta_str = f'seed: {meta["seed"]}\n' + meta_str += f'step: {meta["step"]}\n' + meta_str += f'guidance: {meta["guidance"]}\n' - meta_str += f'`prompt:` {prompt}\n' - meta_str += f'`seed: {meta["seed"]}`\n' - meta_str += f'`step: {meta["step"]}`\n' - meta_str += f'`guidance: {meta["guidance"]}`\n' if meta['strength']: - meta_str += f'`strength: {meta["strength"]}`\n' - meta_str += f'`algo: {meta["model"]}`\n' + meta_str += f'strength: {meta["strength"]}\n' + meta_str += f'algo: {meta["model"]}\n' if meta['upscaler']: - meta_str += f'`upscaler: {meta["upscaler"]}`\n' + meta_str += f'upscaler: {meta["upscaler"]}\n' + + embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) + + foot_str = f'Made with Skynet v{VERSION}\n' + foot_str += f'JOIN THE SWARM: @skynetgpu' + + embed.set_footer(text=foot_str) - meta_str += f'__**Made with Skynet v{VERSION}**__\n' - meta_str += f'**JOIN THE SWARM: @skynetgpu**' return meta_str @@ -74,7 +88,7 @@ def generate_reply_caption( url=f'https://explorer.{DEFAULT_DOMAIN}/v2/explore/transaction/{tx_hash}', color=discord.Color.blue()) - meta_info = prepare_metainfo_caption(user, worker, reward, params) + meta_info = prepare_metainfo_caption(user, worker, reward, params, explorer_link) # why do we have this? final_msg = '\n'.join([ From 82ea4d57c407d6c23c0307d4a37e84c495e4302a Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 17:04:41 -0400 Subject: [PATCH 24/36] add error message for ipfs fail --- skynet/frontend/discord/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 7ab0f17..674b2a2 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -252,6 +252,7 @@ class SkynetDiscordFrontend: if not resp or resp.status_code != 200: logging.error(f'couldn\'t get ipfs hosted image at {ipfs_link}!') + embed.add_field(name='Error', value=f'couldn\'t get ipfs hosted image [**here**]({ipfs_link})!') await message.edit(embed=embed, view=SkynetView(self)) else: logging.info(f'success! sending generated image') From 5ca350d5c0b32bef9f6e4f9ac8ee1ec0d7ca52e5 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 17:32:19 -0400 Subject: [PATCH 25/36] fix timeout message --- skynet/frontend/discord/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 674b2a2..7687c3b 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -223,10 +223,10 @@ class SkynetDiscordFrontend: if not ipfs_hash: - msg_text += f'\n[{timestamp_pretty()}] **timeout processing request**' + timeout_text = f'\n[{timestamp_pretty()}] **timeout processing request**' embed = discord.Embed( title='live updates', - description=msg_text, + description=timeout_text, color=discord.Color.blue()) await message.edit(embed=embed) From aa9cde50c798c8c0503dfcb3c13bd0111271baac Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:33:16 -0400 Subject: [PATCH 26/36] change the footer text --- skynet/frontend/discord/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index 1fd6618..6af8c91 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -69,7 +69,7 @@ def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) foot_str = f'Made with Skynet v{VERSION}\n' - foot_str += f'JOIN THE SWARM: @skynetgpu' + foot_str += f'JOIN THE SWARM. Join our [**server**](https://discord.gg/JYM4YPMgK) for more info.' embed.set_footer(text=foot_str) From 9ad30ffc101b704f8bceb4ba26f50751243481c4 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:38:51 -0400 Subject: [PATCH 27/36] change the footer text again --- skynet/frontend/discord/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index 6af8c91..e9f7935 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -68,8 +68,9 @@ def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) + embed.add_field(value=f'JOIN THE SWARM. Join our [**server**](https://discord.gg/JYM4YPMgK) for more info.', inline=False) + foot_str = f'Made with Skynet v{VERSION}\n' - foot_str += f'JOIN THE SWARM. Join our [**server**](https://discord.gg/JYM4YPMgK) for more info.' embed.set_footer(text=foot_str) From 1e94f4396534160739f1911cee0f1b55927e9bb2 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:41:56 -0400 Subject: [PATCH 28/36] change the footer text again, again --- skynet/frontend/discord/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index e9f7935..931d954 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -68,9 +68,8 @@ def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) - embed.add_field(value=f'JOIN THE SWARM. Join our [**server**](https://discord.gg/JYM4YPMgK) for more info.', inline=False) - foot_str = f'Made with Skynet v{VERSION}\n' + foot_str += f'JOIN THE SWARM. For more info, join our server here: https://discord.gg/JYM4YPMgK' embed.set_footer(text=foot_str) From 3315b05888f268a3e5cb101d78c1df961f9e6ac5 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:46:07 -0400 Subject: [PATCH 29/36] change the footer text again, again, again --- skynet/frontend/discord/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index 931d954..c033129 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -69,7 +69,7 @@ def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) foot_str = f'Made with Skynet v{VERSION}\n' - foot_str += f'JOIN THE SWARM. For more info, join our server here: https://discord.gg/JYM4YPMgK' + foot_str += f'JOIN THE SWARM.\nFor more info, join our server here: https://discord.gg/JYM4YPMgK' embed.set_footer(text=foot_str) From 70c13c242ad3d08b0be7ab226967d3763243b3d3 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:52:15 -0400 Subject: [PATCH 30/36] change the footer text, and increase timeout interval to 2 mins --- skynet/frontend/discord/__init__.py | 2 +- skynet/frontend/discord/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 7687c3b..91f9dcc 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -194,7 +194,7 @@ class SkynetDiscordFrontend: tx_hash = None ipfs_hash = None - for i in range(60): + for i in range(120): try: submits = await self.hyperion.aget_actions( account=self.account, diff --git a/skynet/frontend/discord/utils.py b/skynet/frontend/discord/utils.py index c033129..5e6522d 100644 --- a/skynet/frontend/discord/utils.py +++ b/skynet/frontend/discord/utils.py @@ -69,7 +69,7 @@ def prepare_metainfo_caption(user, worker: str, reward: str, meta: dict, embed) embed.add_field(name='Parameters', value=f'```{meta_str}```', inline=False) foot_str = f'Made with Skynet v{VERSION}\n' - foot_str += f'JOIN THE SWARM.\nFor more info, join our server here: https://discord.gg/JYM4YPMgK' + foot_str += f'JOIN THE SWARM: https://discord.gg/JYM4YPMgK' embed.set_footer(text=foot_str) From 469e90e65027a9102e280bc4261fb3c6d4123c07 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 18:57:28 -0400 Subject: [PATCH 31/36] change back the interval to 60, and double ipfs interval --- skynet/frontend/discord/__init__.py | 2 +- skynet/ipfs/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skynet/frontend/discord/__init__.py b/skynet/frontend/discord/__init__.py index 91f9dcc..7687c3b 100644 --- a/skynet/frontend/discord/__init__.py +++ b/skynet/frontend/discord/__init__.py @@ -194,7 +194,7 @@ class SkynetDiscordFrontend: tx_hash = None ipfs_hash = None - for i in range(120): + for i in range(60): try: submits = await self.hyperion.aget_actions( account=self.account, diff --git a/skynet/ipfs/__init__.py b/skynet/ipfs/__init__.py index bb4e0fe..1a0d4ef 100644 --- a/skynet/ipfs/__init__.py +++ b/skynet/ipfs/__init__.py @@ -27,7 +27,7 @@ class IPFSHTTP: async def get_ipfs_file(ipfs_link: str): logging.info(f'attempting to get image at {ipfs_link}') resp = None - for i in range(10): + for i in range(20): try: resp = await asks.get(ipfs_link, timeout=3) From d74bfb4c59b8038246b5cb344f92546d9eb853ff Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 19:09:11 -0400 Subject: [PATCH 32/36] add intro message --- skynet/frontend/discord/bot.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index ac8744f..afe2220 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -42,6 +42,17 @@ class DiscordBot(commands.Bot): for channel in guild.channels: if channel.name == "skynet": await channel.send('Skynet bot online', view=SkynetView(self.bot)) + intro_msg = await channel.send('Welcome to the Skynet discord bot.\n \ + Skynet is a decentralized compute layer, focused on \ + supporting AI paradigms. Skynet leverages blockchain \ + technology to manage work requests and fills. \ + We are currently featuring image generation and \ + support 11 different models. Get started with the \ + /help command, or just click on some buttons. \ + Here is an example command to generate an image: \ + /txt2img a big red tractor in a giant field of corn') + + await intro_msg.pin() print("\n==============") print("Logged in as") From 26684f7b83677fa99ecbb67a5a0c63d962cfb260 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 19:12:32 -0400 Subject: [PATCH 33/36] add intro message, edit --- skynet/frontend/discord/bot.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index afe2220..9a29f6d 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -42,16 +42,7 @@ class DiscordBot(commands.Bot): for channel in guild.channels: if channel.name == "skynet": await channel.send('Skynet bot online', view=SkynetView(self.bot)) - intro_msg = await channel.send('Welcome to the Skynet discord bot.\n \ - Skynet is a decentralized compute layer, focused on \ - supporting AI paradigms. Skynet leverages blockchain \ - technology to manage work requests and fills. \ - We are currently featuring image generation and \ - support 11 different models. Get started with the \ - /help command, or just click on some buttons. \ - Here is an example command to generate an image: \ - /txt2img a big red tractor in a giant field of corn') - + intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons. Here is an example command to generate an image: /txt2img a big red tractor in a giant field of corn') await intro_msg.pin() print("\n==============") From 751812ec52c619b3d44f8e45d7583ebcea07a92c Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 19:13:33 -0400 Subject: [PATCH 34/36] add intro message, edit, again --- skynet/frontend/discord/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index 9a29f6d..10f81f1 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -42,7 +42,7 @@ class DiscordBot(commands.Bot): for channel in guild.channels: if channel.name == "skynet": await channel.send('Skynet bot online', view=SkynetView(self.bot)) - intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons. Here is an example command to generate an image: /txt2img a big red tractor in a giant field of corn') + intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons.\nHere is an example command to generate an image: /txt2img a big red tractor in a giant field of corn') await intro_msg.pin() print("\n==============") From 965393907f7f70cc3f38dab264e8c4c9aa9f8ad6 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 19:14:39 -0400 Subject: [PATCH 35/36] add intro message, edit, again --- skynet/frontend/discord/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index 10f81f1..b8673c4 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -42,7 +42,7 @@ class DiscordBot(commands.Bot): for channel in guild.channels: if channel.name == "skynet": await channel.send('Skynet bot online', view=SkynetView(self.bot)) - intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons.\nHere is an example command to generate an image: /txt2img a big red tractor in a giant field of corn') + intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons. Here is an example command to generate an image:\n/txt2img a big red tractor in a giant field of corn') await intro_msg.pin() print("\n==============") From fd8ea3299a9d3e5befb79570e77975b2aca571f0 Mon Sep 17 00:00:00 2001 From: Konstantine Tsafatinos Date: Fri, 21 Jul 2023 21:37:31 -0400 Subject: [PATCH 36/36] update intro message --- skynet/frontend/discord/bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skynet/frontend/discord/bot.py b/skynet/frontend/discord/bot.py index b8673c4..accc926 100644 --- a/skynet/frontend/discord/bot.py +++ b/skynet/frontend/discord/bot.py @@ -42,7 +42,8 @@ class DiscordBot(commands.Bot): for channel in guild.channels: if channel.name == "skynet": await channel.send('Skynet bot online', view=SkynetView(self.bot)) - intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons. Here is an example command to generate an image:\n/txt2img a big red tractor in a giant field of corn') + # intro_msg = await channel.send('Welcome to the Skynet discord bot.\nSkynet is a decentralized compute layer, focused on supporting AI paradigms. Skynet leverages blockchain technology to manage work requests and fills. We are currently featuring image generation and support 11 different models. Get started with the /help command, or just click on some buttons. Here is an example command to generate an image:\n/txt2img a big red tractor in a giant field of corn') + intro_msg = await channel.send("Welcome to Skynet's Discord Bot,\n\nSkynet operates as a decentralized compute layer, offering a wide array of support for diverse AI paradigms through the use of blockchain technology. Our present focus is image generation, powered by 11 distinct models.\n\nTo begin exploring, use the '/help' command or directly interact with the provided buttons. Here is an example command to generate an image:\n\n'/txt2img a big red tractor in a giant field of corn'") await intro_msg.pin() print("\n==============")