Compare commits

..

1 Commits

Author SHA1 Message Date
Guillermo Rodriguez bb1c82eb66
Start creating generic abstraction for chatbot 2025-02-18 22:24:20 -03:00
10 changed files with 103 additions and 211 deletions

View File

@ -77,8 +77,7 @@ explicit = true
torch = { index = "torch" } torch = { index = "torch" }
triton = { index = "torch" } triton = { index = "torch" }
torchvision = { index = "torch" } torchvision = { index = "torch" }
# py-leap = { git = "https://github.com/guilledk/py-leap.git", branch = "struct_unwrap" } py-leap = { git = "https://github.com/guilledk/py-leap.git", branch = "struct_unwrap" }
py-leap = { path = "../py-leap", editable = true }
pytest-dockerctl = { git = "https://github.com/pikers/pytest-dockerctl.git", branch = "g_update" } pytest-dockerctl = { git = "https://github.com/pikers/pytest-dockerctl.git", branch = "g_update" }
[build-system] [build-system]

View File

@ -165,7 +165,7 @@ def run(*args, **kwargs):
@run.command() @run.command()
def db(): def db():
from skynet.frontend.chatbot.db import open_new_database from .db import open_new_database
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
with open_new_database(cleanup=False) as db_params: with open_new_database(cleanup=False) as db_params:

View File

@ -32,17 +32,12 @@ class FrontendConfig(msgspec.Struct):
account: str account: str
permission: str permission: str
key: str key: str
node_url: str
hyperion_url: str
ipfs_url: str ipfs_url: str
token: str token: str
db_host: str
db_user: str
db_pass: str
db_name: str = 'skynet'
node_url: str = 'https://testnet.telos.net'
hyperion_url: str = 'https://testnet.skygpu.net'
ipfs_domain: str = 'ipfs.skygpu.net' ipfs_domain: str = 'ipfs.skygpu.net'
explorer_domain: str = 'explorer.skygpu.net' explorer_domain: str = 'explorer.skygpu.net'
request_timeout: int = 60 * 3
proto_version: int = 0 proto_version: int = 0
reward: str = '20.0000 GPU' reward: str = '20.0000 GPU'
receiver: str = 'gpu.scd' receiver: str = 'gpu.scd'

View File

@ -1,10 +1,6 @@
import random import random
from ..constants import ( from ..constants import *
MODELS,
get_model_by_shortname,
MAX_STEP, MIN_STEP, MAX_WIDTH, MAX_HEIGHT, MAX_GUIDANCE
)
class ConfigRequestFormatError(BaseException): class ConfigRequestFormatError(BaseException):
@ -30,17 +26,17 @@ class ConfigSizeDivisionByEight(BaseException):
def validate_user_config_request(req: str): def validate_user_config_request(req: str):
params = req.split(' ') params = req.split(' ')
if len(params) < 2: if len(params) < 3:
raise ConfigRequestFormatError('config request format incorrect') raise ConfigRequestFormatError('config request format incorrect')
else: else:
try: try:
attr = params[0] attr = params[1]
match attr: match attr:
case 'model' | 'algo': case 'model' | 'algo':
attr = 'model' attr = 'model'
val = params[1] val = params[2]
shorts = [model_info.short for model_info in MODELS.values()] shorts = [model_info.short for model_info in MODELS.values()]
if val not in shorts: if val not in shorts:
raise ConfigUnknownAlgorithm(f'no model named {val}') raise ConfigUnknownAlgorithm(f'no model named {val}')
@ -48,38 +44,38 @@ def validate_user_config_request(req: str):
val = get_model_by_shortname(val) val = get_model_by_shortname(val)
case 'step': case 'step':
val = int(params[1]) val = int(params[2])
val = max(min(val, MAX_STEP), MIN_STEP) val = max(min(val, MAX_STEP), MIN_STEP)
case 'width': case 'width':
val = max(min(int(params[1]), MAX_WIDTH), 16) val = max(min(int(params[2]), MAX_WIDTH), 16)
if val % 8 != 0: if val % 8 != 0:
raise ConfigSizeDivisionByEight( raise ConfigSizeDivisionByEight(
'size must be divisible by 8!') 'size must be divisible by 8!')
case 'height': case 'height':
val = max(min(int(params[1]), MAX_HEIGHT), 16) val = max(min(int(params[2]), MAX_HEIGHT), 16)
if val % 8 != 0: if val % 8 != 0:
raise ConfigSizeDivisionByEight( raise ConfigSizeDivisionByEight(
'size must be divisible by 8!') 'size must be divisible by 8!')
case 'seed': case 'seed':
val = params[1] val = params[2]
if val == 'auto': if val == 'auto':
val = None val = None
else: else:
val = int(params[1]) val = int(params[2])
case 'guidance': case 'guidance':
val = float(params[1]) val = float(params[2])
val = max(min(val, MAX_GUIDANCE), 0) val = max(min(val, MAX_GUIDANCE), 0)
case 'strength': case 'strength':
val = float(params[1]) val = float(params[2])
val = max(min(val, 0.99), 0.01) val = max(min(val, 0.99), 0.01)
case 'upscaler': case 'upscaler':
val = params[1] val = params[2]
if val == 'off': if val == 'off':
val = None val = None
elif val != 'x4': elif val != 'x4':
@ -87,7 +83,7 @@ def validate_user_config_request(req: str):
f'\"{val}\" is not a valid upscaler') f'\"{val}\" is not a valid upscaler')
case 'autoconf': case 'autoconf':
val = params[1] val = params[2]
if val == 'on': if val == 'on':
val = True val = True

View File

@ -22,8 +22,7 @@ from skynet.constants import (
HELP_TOPICS, HELP_TOPICS,
HELP_UNKWNOWN_PARAM, HELP_UNKWNOWN_PARAM,
COOL_WORDS, COOL_WORDS,
DONATION_INFO, DONATION_INFO
UNKNOWN_CMD_TEXT
) )
from skynet.frontend import validate_user_config_request from skynet.frontend import validate_user_config_request
from skynet.frontend.chatbot.db import FrontendUserDB from skynet.frontend.chatbot.db import FrontendUserDB
@ -55,11 +54,7 @@ def perform_auto_conf(config: dict) -> dict:
def sanitize_params(params: dict) -> dict: def sanitize_params(params: dict) -> dict:
if ( if 'seed' not in params:
'seed' not in params
or
params['seed'] is None
):
params['seed'] = randint(0, 0xffffffff) params['seed'] = randint(0, 0xffffffff)
s_params = {} s_params = {}
@ -87,8 +82,7 @@ class BaseChatbot(ABC):
self.config = config self.config = config
self.ipfs = AsyncIPFSHTTP(config.ipfs_url) self.ipfs = AsyncIPFSHTTP(config.ipfs_url)
self.cleos = CLEOS(endpoint=config.node_url) self.cleos = CLEOS(endpoint=config.node_url)
self.cleos.load_abi(config.receiver, GPU_CONTRACT_ABI) self.cleos.load_abi('gpu.scd', GPU_CONTRACT_ABI)
self.cleos.import_key(config.account, config.key)
self.hyperion = HyperionAPI(config.hyperion_url) self.hyperion = HyperionAPI(config.hyperion_url)
async def init(self): async def init(self):
@ -123,12 +117,9 @@ class BaseChatbot(ABC):
''' '''
... ...
async def create_status_msg(self, msg: BaseMessage, init_text: str, force_user: BaseUser | None = None) -> tuple[BaseUser, BaseMessage, dict]: async def create_status_msg(self, msg: BaseMessage, init_text: str) -> tuple[BaseUser, BaseMessage, dict]:
# maybe init user # maybe init user
user = msg.author user = msg.author
if force_user:
user = force_user
user_row = await self.db.get_or_create_user(user.id) user_row = await self.db.get_or_create_user(user.id)
# create status msg # create status msg
@ -160,7 +151,7 @@ class BaseChatbot(ABC):
... ...
@abstractmethod @abstractmethod
async def update_request_status_step_0(self, status_msg: BaseMessage, user_msg: BaseMessage): async def update_request_status_step_0(self, status_msg: BaseMessage, user: BaseUser):
''' '''
First step in request status message lifecycle, should notify which user sent the request First step in request status message lifecycle, should notify which user sent the request
and that we are about to broadcast the request to chain and that we are about to broadcast the request to chain
@ -205,24 +196,19 @@ class BaseChatbot(ABC):
async def handle_request( async def handle_request(
self, self,
msg: BaseMessage, msg: BaseMessage
force_user: BaseUser | None = None
): ):
if msg.chat.is_private: if msg.chat.is_private:
return return
if ( if len(msg.text) == 0:
len(msg.text) == 0 await self.reply_to(msg.id, 'empty prompt ignored.')
and
msg.command != BaseCommands.REDO
):
await self.reply_to(msg, 'empty prompt ignored.')
return return
# maybe initialize user db row and send a new msg thats gonna # maybe initialize user db row and send a new msg thats gonna
# be updated throughout the request lifecycle # be updated throughout the request lifecycle
user, status_msg, user_row = await self.create_status_msg( user, status_msg, user_row = await self.create_status_msg(
msg, f'started processing a {msg.command} request...', force_user=force_user) msg, 'started processing a {msg.command} request...')
# if this is a redo msg, we attempt to get the input params from db # if this is a redo msg, we attempt to get the input params from db
# else use msg properties # else use msg properties
@ -238,19 +224,11 @@ class BaseChatbot(ABC):
inputs = await self.db.get_last_inputs_of(user.id) inputs = await self.db.get_last_inputs_of(user.id)
if not prompt: if not prompt:
await self.reply_to(msg, 'no last prompt found, try doing a non-redo request first') await self.reply_to(msg.id, 'no last prompt found, try doing a non-redo request first')
return return
case _: case _:
await self.reply_to(msg, f'unknown request of type {msg.command}') await self.reply_to(msg.id, f'unknown request of type {msg.command}')
return
if (
msg.command == BaseCommands.IMG2IMG
and
len(inputs) == 0
):
await self.edit_msg(status_msg, 'seems you tried to do an img2img command without sending image')
return return
# maybe apply recomended settings to this request # maybe apply recomended settings to this request
@ -258,8 +236,6 @@ class BaseChatbot(ABC):
if user_row['autoconf']: if user_row['autoconf']:
user_row = perform_auto_conf(user_row) user_row = perform_auto_conf(user_row)
user_row = sanitize_params(user_row)
body = BodyV0( body = BodyV0(
method=command, method=command,
params=BodyV0Params( params=BodyV0Params(
@ -271,7 +247,7 @@ class BaseChatbot(ABC):
# publish inputs to ipfs # publish inputs to ipfs
input_cids = [] input_cids = []
for i in inputs: for i in inputs:
await i.publish(self.ipfs, user_row) i.publish()
input_cids.append(i.cid) input_cids.append(i.cid)
inputs_str = ','.join((i for i in input_cids)) inputs_str = ','.join((i for i in input_cids))
@ -285,25 +261,22 @@ class BaseChatbot(ABC):
last_inputs=inputs last_inputs=inputs
) )
await self.update_request_status_step_0(status_msg, msg) await self.update_request_status_step_0(status_msg)
# prepare and send enqueue request # prepare and send enqueue request
request_time = datetime.now().isoformat() request_time = datetime.now().isoformat()
str_body = msgspec.json.encode(body).decode('utf-8') str_body = msgspec.json.encode(body).decode('utf-8')
enqueue_receipt = await self.cleos.a_push_action( enqueue_receipt = await self.cleos.a_push_action(
self.config.receiver, self.receiver,
'enqueue', 'enqueue',
[ (
self.config.account, self.config.account,
str_body, str_body,
inputs_str, inputs_str,
self.config.reward, self.config.reward,
1 1
], )
self.config.account,
key=self.cleos.private_keys[self.config.account],
permission=self.config.permission
) )
await self.update_request_status_step_1(status_msg, enqueue_receipt) await self.update_request_status_step_1(status_msg, enqueue_receipt)
@ -322,7 +295,7 @@ class BaseChatbot(ABC):
''' '''
request_id, nonce = console_lines[-1].rstrip().split(':') request_id, nonce = console_lines[-1].rstrip().split(':')
request_hash = sha256( request_hash = sha256(
(nonce + str_body + inputs_str).encode('utf-8')).hexdigest().upper() (nonce + str_body + inputs_str).encode('utf-8')).hexdigest.upper()
request_id = int(request_id) request_id = int(request_id)
@ -370,13 +343,12 @@ class BaseChatbot(ABC):
data = action['act']['data'] data = action['act']['data']
result_cid = data['ipfs_hash'] result_cid = data['ipfs_hash']
worker = data['worker'] worker = data['worker']
logging.info(f'found matching submit! tx: {submit_tx_hash} cid: {result_cid}') logging.info('found matching submit! tx: {submit_tx_hash} cid: {ipfs_hash}')
break break
except json.JSONDecodeError: except json.JSONDecodeError:
if i < self.config.request_timeout: if i < self.config.request_timeout:
logging.error('network error while searching for submit, retry...') logging.error('network error while searching for submit, retry...')
await asyncio.sleep(1) await asyncio.sleep(1)
# if we found matching submit submit_tx_hash, worker, and result_cid will not be None # if we found matching submit submit_tx_hash, worker, and result_cid will not be None
@ -393,7 +365,7 @@ class BaseChatbot(ABC):
result_img = None result_img = None
if get_img_response and get_img_response.status_code == 200: if get_img_response and get_img_response.status_code == 200:
try: try:
with Image.open(io.BytesIO(get_img_response.read())) as img: with Image.open(io.BytesIO(get_img_response.raw)) as img:
w, h = img.size w, h = img.size
if ( if (
@ -414,7 +386,7 @@ class BaseChatbot(ABC):
logging.warning(f'couldn\'t get ipfs result at {result_link}!') logging.warning(f'couldn\'t get ipfs result at {result_link}!')
await self.update_request_status_final( await self.update_request_status_final(
msg, status_msg, user, body.params, inputs, submit_tx_hash, worker, result_img) msg, status_msg, user, body, inputs, submit_tx_hash, worker, result_img)
await self.db.increment_generated(user.id) await self.db.increment_generated(user.id)
@ -470,14 +442,7 @@ class BaseChatbot(ABC):
await self.reply_to(msg, DONATION_INFO) await self.reply_to(msg, DONATION_INFO)
async def say(self, msg: BaseMessage): async def say(self, msg: BaseMessage):
if ( if not msg.chat.is_private or not msg.author.is_admin:
msg.chat.is_private
or
not msg.author.is_admin
):
return return
await self.new_msg(self.main_group, msg.text) await self.new_msg(self.main_group, msg.text)
async def echo_unknown(self, msg: BaseMessage):
await self.reply_to(msg, UNKNOWN_CMD_TEXT)

View File

@ -59,6 +59,25 @@ CREATE TABLE IF NOT EXISTS skynet.user_requests(
); );
""" """
def try_decode_uid(uid: str) -> tuple[str | None, int | None]:
"""
Attempts to decode the user ID. The user ID can be just an integer
or of the format 'proto+uid'. Returns (None, int) if it's just an
integer or (proto, int) if it's 'proto+uid'. Returns (None, None)
if neither format is valid.
"""
try:
return None, int(uid)
except ValueError:
pass
try:
proto, uid_str = uid.split("+", 1)
return proto, int(uid_str)
except ValueError:
logging.warning(f"Got non-chat-proto UID?: {uid}")
return None, None
@cm @cm
def open_new_database(cleanup: bool = True): def open_new_database(cleanup: bool = True):
@ -99,17 +118,14 @@ def open_new_database(cleanup: bool = True):
time.sleep(1) time.sleep(1)
logging.info("Creating 'skynet' database...") logging.info("Creating 'skynet' database...")
conn = psycopg2.connect( with psycopg2.connect(
user="postgres", password=root_password, host="localhost", port=port user="postgres", password=root_password, host="localhost", port=port
) ) as conn:
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
conn.autocommit = True with conn.cursor() as cursor:
cursor = conn.cursor()
cursor.execute(f"CREATE USER skynet WITH PASSWORD '{skynet_password}'") cursor.execute(f"CREATE USER skynet WITH PASSWORD '{skynet_password}'")
cursor.execute("CREATE DATABASE skynet") cursor.execute("CREATE DATABASE skynet")
cursor.execute("GRANT ALL PRIVILEGES ON DATABASE skynet TO skynet") cursor.execute("GRANT ALL PRIVILEGES ON DATABASE skynet TO skynet")
cursor.close()
conn.close()
logging.info("Database setup complete.") logging.info("Database setup complete.")
yield container, skynet_password, db_host yield container, skynet_password, db_host
@ -188,7 +204,7 @@ class FrontendUserDB:
records = await conn.fetch( records = await conn.fetch(
"SELECT * FROM skynet.user_config WHERE id = $1", user_id "SELECT * FROM skynet.user_config WHERE id = $1", user_id
) )
return dict(records[0]) if len(records) == 1 else None return records[0] if len(records) == 1 else None
async def get_user(self, user_id: int): async def get_user(self, user_id: int):
"""Alias for get_user_config (same data returned).""" """Alias for get_user_config (same data returned)."""
@ -414,7 +430,7 @@ class FrontendUserDB:
) )
if not last_inputs_str: if not last_inputs_str:
return [] return None
last_inputs = [] last_inputs = []
for i in last_inputs_str.split(','): for i in last_inputs_str.split(','):

View File

@ -1,21 +1,19 @@
import json
import logging
import traceback import traceback
from typing import Self, Awaitable from typing import Self, Awaitable
from datetime import datetime, timezone from datetime import datetime, timezone
from telebot.types import ( from telebot.types import (
AsyncTeleBot,
User as TGUser, User as TGUser,
Chat as TGChat, Chat as TGChat,
PhotoSize as TGPhotoSize, PhotoSize as TGPhotoSize,
Message as TGMessage, Message as TGMessage,
CallbackQuery,
InputMediaPhoto, InputMediaPhoto,
InlineKeyboardButton, InlineKeyboardButton,
InlineKeyboardMarkup InlineKeyboardMarkup
) )
from telebot.async_telebot import AsyncTeleBot, ExceptionHandler from telebot.async_telebot import ExceptionHandler
from telebot.formatting import hlink from telebot.formatting import hlink
from skynet.types import BodyV0Params from skynet.types import BodyV0Params
@ -23,7 +21,7 @@ from skynet.config import FrontendConfig
from skynet.constants import VERSION from skynet.constants import VERSION
from skynet.frontend.chatbot import BaseChatbot from skynet.frontend.chatbot import BaseChatbot
from skynet.frontend.chatbot.db import FrontendUserDB from skynet.frontend.chatbot.db import FrontendUserDB
from skynet.frontend.chatbot.types import ( from skynet.frontend.types import (
BaseUser, BaseUser,
BaseChatRoom, BaseChatRoom,
BaseFileInput, BaseFileInput,
@ -32,7 +30,6 @@ from skynet.frontend.chatbot.types import (
) )
GROUP_ID = -1001541979235 GROUP_ID = -1001541979235
TEST_GROUP_ID = -4099622703
ADMIN_USER_ID = 383385940 ADMIN_USER_ID = 383385940
@ -103,9 +100,6 @@ class TelegramFileInput(BaseFileInput):
raise ValueError raise ValueError
def set_cid(self, cid: str):
self._cid = cid
async def download(self, bot: AsyncTeleBot) -> bytes: async def download(self, bot: AsyncTeleBot) -> bytes:
file_path = (await bot.get_file(self.id)).file_path file_path = (await bot.get_file(self.id)).file_path
self._raw = await bot.download_file(file_path) self._raw = await bot.download_file(file_path)
@ -118,9 +112,6 @@ class TelegramMessage(BaseMessage):
self._msg = msg self._msg = msg
self._cmd = cmd self._cmd = cmd
self._chat = TelegramChatRoom(msg.chat) self._chat = TelegramChatRoom(msg.chat)
self._inputs: list[TelegramFileInput] | None = None
self._author = None
@property @property
def id(self) -> int: def id(self) -> int:
@ -132,17 +123,10 @@ class TelegramMessage(BaseMessage):
@property @property
def text(self) -> str: def text(self) -> str:
# remove command name, slash and first space return self._msg.text[len(self._cmd) + 1:]
if self._msg.text:
return self._msg.text[len(self._cmd) + 2:]
return self._msg.caption[len(self._cmd) + 2:]
@property @property
def author(self) -> TelegramUser: def author(self) -> TelegramUser:
if self._author:
return self._author
return TelegramUser(self._msg.from_user) return TelegramUser(self._msg.from_user)
@property @property
@ -151,16 +135,11 @@ class TelegramMessage(BaseMessage):
@property @property
def inputs(self) -> list[TelegramFileInput]: def inputs(self) -> list[TelegramFileInput]:
if self._inputs is None: return [
self._inputs = []
if self._msg.photo:
self._inputs = [
TelegramFileInput(photo=p) TelegramFileInput(photo=p)
for p in self._msg.photo for p in self._msg.photo
] ]
return self._inputs
# generic tg utils # generic tg utils
@ -207,18 +186,19 @@ def prepare_metainfo_caption(user: TelegramUser, worker: str, reward: str, param
def generate_reply_caption( def generate_reply_caption(
config: FrontendConfig,
user: TelegramUser, user: TelegramUser,
params: BodyV0Params, params: BodyV0Params,
tx_hash: str, tx_hash: str,
worker: str, worker: str,
reward: str,
explorer_domain: str
): ):
explorer_link = hlink( explorer_link = hlink(
'SKYNET Transaction Explorer', 'SKYNET Transaction Explorer',
f'https://{config.explorer_domain}/v2/explore/transaction/{tx_hash}' f'https://{explorer_domain}/v2/explore/transaction/{tx_hash}'
) )
meta_info = prepare_metainfo_caption(user, worker, config.reward, params) meta_info = prepare_metainfo_caption(user, worker, reward, params)
final_msg = '\n'.join([ final_msg = '\n'.join([
'Worker finished your task!', 'Worker finished your task!',
@ -259,43 +239,14 @@ class TelegramChatbot(BaseChatbot):
append_handler(bot, BaseCommands.SAY, self.say) append_handler(bot, BaseCommands.SAY, self.say)
append_handler(bot, BaseCommands.TXT2IMG, self.handle_request) append_handler(bot, BaseCommands.TXT2IMG, self.handle_request)
append_handler(bot, BaseCommands.IMG2IMG, self.handle_request) append_handler(bot, BaseCommands.IMG2IMG, self.handle_request)
@bot.message_handler(func=lambda _: True, content_types=['photo', 'document'])
async def handle_img2img(tg_msg: TGMessage):
msg = TelegramMessage(cmd='img2img', msg=tg_msg)
for file in msg.inputs:
await file.download(bot)
await self.handle_request(msg)
append_handler(bot, BaseCommands.REDO, self.handle_request) append_handler(bot, BaseCommands.REDO, self.handle_request)
@bot.message_handler(func=lambda _: True)
async def unknown_cmd(tg_msg: TGMessage):
if tg_msg.text[0] == '/':
msg = TelegramMessage(cmd='unknown', msg=tg_msg)
await self.echo_unknown(msg)
@bot.callback_query_handler(func=lambda _: True)
async def callback_query(call: CallbackQuery):
call_json = json.loads(call.data)
method = call_json.get('method')
match method:
case 'redo':
msg = await self.new_msg(self.main_group, 'processing a redo request...')
msg._cmd = 'redo'
await self.handle_request(msg, force_user=TelegramUser(user=call.from_user))
await bot.delete_message(chat_id=self.main_group.id, message_id=msg.id)
self.bot = bot
self._main_room: TelegramChatRoom | None = None self._main_room: TelegramChatRoom | None = None
async def init(self): async def init(self):
tg_group = await self.bot.get_chat(TEST_GROUP_ID) tg_group = await self.bot.get_chat(GROUP_ID)
self._main_room = TelegramChatRoom(chat=tg_group) self._main_room = TelegramChatRoom(chat=tg_group)
logging.info('initialized')
async def run(self): async def run(self):
await self.init() await self.init()
@ -330,14 +281,14 @@ class TelegramChatbot(BaseChatbot):
f'\n[{timestamp_pretty()}] <b>timeout processing request</b>', f'\n[{timestamp_pretty()}] <b>timeout processing request</b>',
) )
async def update_request_status_step_0(self, status_msg: TelegramMessage, user_msg: TelegramMessage): async def update_request_status_step_0(self, status_msg: TelegramMessage):
''' '''
First step in request status message lifecycle, should notify which user sent the request First step in request status message lifecycle, should notify which user sent the request
and that we are about to broadcast the request to chain and that we are about to broadcast the request to chain
''' '''
await self.update_status_msg( await self.update_status_msg(
status_msg, status_msg,
f'processing a \'{user_msg.command}\' request by {user_msg.author.name}\n' f'processing a \'{status_msg.command}\' request by {status_msg.author.name}\n'
f'[{timestamp_pretty()}] <i>broadcasting transaction to chain...</i>' f'[{timestamp_pretty()}] <i>broadcasting transaction to chain...</i>'
) )
@ -349,7 +300,7 @@ class TelegramChatbot(BaseChatbot):
enqueue_tx_id = tx_result['transaction_id'] enqueue_tx_id = tx_result['transaction_id']
enqueue_tx_link = hlink( enqueue_tx_link = hlink(
'Your request on Skynet Explorer', 'Your request on Skynet Explorer',
f'https://{self.config.explorer_domain}/v2/explore/transaction/{enqueue_tx_id}' f'https://{self.explorer_domain}/v2/explore/transaction/{enqueue_tx_id}'
) )
await self.append_status_msg( await self.append_status_msg(
status_msg, status_msg,
@ -365,7 +316,7 @@ class TelegramChatbot(BaseChatbot):
''' '''
tx_link = hlink( tx_link = hlink(
'Your result on Skynet Explorer', 'Your result on Skynet Explorer',
f'https://{self.config.explorer_domain}/v2/explore/transaction/{submit_tx_hash}' f'https://{self.explorer_domain}/v2/explore/transaction/{submit_tx_hash}'
) )
await self.append_status_msg( await self.append_status_msg(
status_msg, status_msg,
@ -391,7 +342,7 @@ class TelegramChatbot(BaseChatbot):
reply caption and if provided also sent the found result img reply caption and if provided also sent the found result img
''' '''
caption = generate_reply_caption( caption = generate_reply_caption(
self.config, user, params, submit_tx_hash, worker) user, worker, self.config.reward, params)
await self.bot.delete_message( await self.bot.delete_message(
chat_id=status_msg.chat.id, chat_id=status_msg.chat.id,
@ -413,8 +364,8 @@ class TelegramChatbot(BaseChatbot):
parse_mode='HTML' parse_mode='HTML'
) )
case _: case 1:
_input = inputs[-1] _input = inputs.pop()
await self.bot.send_media_group( await self.bot.send_media_group(
status_msg.chat.id, status_msg.chat.id,
media=[ media=[
@ -422,3 +373,6 @@ class TelegramChatbot(BaseChatbot):
InputMediaPhoto(result_img, caption=caption, parse_mode='HTML') InputMediaPhoto(result_img, caption=caption, parse_mode='HTML')
] ]
) )
case _:
raise NotImplementedError

View File

@ -1,9 +1,8 @@
import io import io
from abc import ABC, abstractproperty, abstractmethod from ABC import ABC, abstractproperty, abstractmethod
from enum import StrEnum from enum import StrEnum
from typing import Self from typing import Self
from pathlib import Path
from PIL import Image from PIL import Image
from skynet.ipfs import AsyncIPFSHTTP from skynet.ipfs import AsyncIPFSHTTP
@ -53,10 +52,6 @@ class BaseFileInput(ABC):
async def download(self, *args) -> bytes: async def download(self, *args) -> bytes:
... ...
@abstractmethod
def set_cid(self, cid: str):
...
async def publish(self, ipfs_api: AsyncIPFSHTTP, user_row: dict): async def publish(self, ipfs_api: AsyncIPFSHTTP, user_row: dict):
with Image.open(io.BytesIO(self._raw)) as img: with Image.open(io.BytesIO(self._raw)) as img:
w, h = img.size w, h = img.size
@ -68,12 +63,11 @@ class BaseFileInput(ABC):
): ):
img.thumbnail((user_row['width'], user_row['height'])) img.thumbnail((user_row['width'], user_row['height']))
img_path = Path('/tmp/ipfs-staging/img.png') img_path = '/tmp/ipfs-staging/img.png'
img.save(img_path, format='PNG') img.save(img_path, format='PNG')
ipfs_info = await ipfs_api.add(img_path) ipfs_info = await ipfs_api.add(img_path)
ipfs_hash = ipfs_info['Hash'] ipfs_hash = ipfs_info['Hash']
self.set_cid(ipfs_hash)
await ipfs_api.pin(ipfs_hash) await ipfs_api.pin(ipfs_hash)

View File

@ -85,7 +85,6 @@ class BodyV0Params(Struct):
strength: str | float | None = None strength: str | float | None = None
output_type: str | None = 'png' output_type: str | None = 'png'
upscaler: str | None = None upscaler: str | None = None
autoconf: bool | None = None
class BodyV0(Struct): class BodyV0(Struct):

48
uv.lock
View File

@ -1,5 +1,4 @@
version = 1 version = 1
revision = 1
requires-python = ">=3.10, <3.13" requires-python = ">=3.10, <3.13"
resolution-markers = [ resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'darwin'", "python_full_version >= '3.12' and sys_platform == 'darwin'",
@ -272,7 +271,7 @@ name = "cffi"
version = "1.17.1" version = "1.17.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pycparser", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "pycparser" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 }
wheels = [ wheels = [
@ -1272,7 +1271,7 @@ name = "nvidia-cudnn-cu12"
version = "9.1.0.70" version = "9.1.0.70"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "nvidia-cublas-cu12" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 },
@ -1299,9 +1298,9 @@ name = "nvidia-cusolver-cu12"
version = "11.4.5.107" version = "11.4.5.107"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "nvidia-cublas-cu12" },
{ name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "nvidia-cusparse-cu12" },
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "nvidia-nvjitlink-cu12" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 },
@ -1312,7 +1311,7 @@ name = "nvidia-cusparse-cu12"
version = "12.1.0.106" version = "12.1.0.106"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "nvidia-nvjitlink-cu12" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 },
@ -1599,7 +1598,7 @@ wheels = [
[[package]] [[package]]
name = "py-leap" name = "py-leap"
version = "0.1a35" version = "0.1a35"
source = { editable = "../py-leap" } source = { git = "https://github.com/guilledk/py-leap.git?branch=struct_unwrap#18b3c73e724922a060db5f8ea2b9d9727b6152cc" }
dependencies = [ dependencies = [
{ name = "base58" }, { name = "base58" },
{ name = "cryptos" }, { name = "cryptos" },
@ -1609,33 +1608,6 @@ dependencies = [
{ name = "ripemd-hash" }, { name = "ripemd-hash" },
] ]
[package.metadata]
requires-dist = [
{ name = "base58", specifier = ">=2.1.1,<3" },
{ name = "cryptos", specifier = ">=2.0.9,<3" },
{ name = "httpx", specifier = ">=0.28.1,<0.29" },
{ name = "msgspec", specifier = ">=0.19.0" },
{ name = "requests", specifier = "<2.32.0" },
{ name = "ripemd-hash", specifier = ">=1.0.1,<2" },
]
[package.metadata.requires-dev]
dev = [
{ name = "docker", specifier = ">=6.1.3,<7" },
{ name = "pdbpp", specifier = ">=0.10.3,<0.11" },
{ name = "pytest", specifier = ">=8.3.4,<9" },
{ name = "pytest-trio", specifier = ">=0.8.0,<0.9" },
]
docs = [
{ name = "sphinx", specifier = "==7.1.2" },
{ name = "sphinx-rtd-theme", specifier = "==1.3.0" },
]
snaps = [
{ name = "bs4", specifier = ">=0.0.2,<0.0.3" },
{ name = "tdqm", specifier = ">=0.0.1,<0.0.2" },
{ name = "zstandard", specifier = ">=0.21.0,<0.22" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@ -1661,6 +1633,8 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/61/74/49f5d20c514ccc631b940cc9dfec45dcce418dc84a98463a2e2ebec33904/pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e", size = 2257982 }, { url = "https://files.pythonhosted.org/packages/61/74/49f5d20c514ccc631b940cc9dfec45dcce418dc84a98463a2e2ebec33904/pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e", size = 2257982 },
{ url = "https://files.pythonhosted.org/packages/92/4b/d33ef74e2cc0025a259936661bb53432c5bbbadc561c5f2e023bcd73ce4c/pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e", size = 1779052 }, { url = "https://files.pythonhosted.org/packages/92/4b/d33ef74e2cc0025a259936661bb53432c5bbbadc561c5f2e023bcd73ce4c/pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e", size = 1779052 },
{ url = "https://files.pythonhosted.org/packages/5b/be/7c991840af1184009fc86267160948350d1bf875f153c97bb471ad944e40/pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0", size = 1816307 }, { url = "https://files.pythonhosted.org/packages/5b/be/7c991840af1184009fc86267160948350d1bf875f153c97bb471ad944e40/pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0", size = 1816307 },
{ url = "https://files.pythonhosted.org/packages/af/ac/24125ad36778914a36f08d61ba5338cb9159382c638d9761ee19c8de822c/pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8", size = 1694999 },
{ url = "https://files.pythonhosted.org/packages/93/73/be7a54a5903508070e5508925ba94493a1f326cfeecfff750e3eb250ea28/pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c", size = 1769437 },
{ url = "https://files.pythonhosted.org/packages/e5/9f/39a6187f3986841fa6a9f35c6fdca5030ef73ff708b45a993813a51d7d10/pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31", size = 1619607 }, { url = "https://files.pythonhosted.org/packages/e5/9f/39a6187f3986841fa6a9f35c6fdca5030ef73ff708b45a993813a51d7d10/pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31", size = 1619607 },
{ url = "https://files.pythonhosted.org/packages/f8/70/60bb08e9e9841b18d4669fb69d84b64ce900aacd7eb0ebebd4c7b9bdecd3/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3", size = 1653571 }, { url = "https://files.pythonhosted.org/packages/f8/70/60bb08e9e9841b18d4669fb69d84b64ce900aacd7eb0ebebd4c7b9bdecd3/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3", size = 1653571 },
{ url = "https://files.pythonhosted.org/packages/c9/6f/191b73509291c5ff0dddec9cc54797b1d73303c12b2e4017b24678e57099/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37", size = 1691548 }, { url = "https://files.pythonhosted.org/packages/c9/6f/191b73509291c5ff0dddec9cc54797b1d73303c12b2e4017b24678e57099/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37", size = 1691548 },
@ -2152,7 +2126,7 @@ requires-dist = [
{ name = "outcome", specifier = ">=1.3.0.post0" }, { name = "outcome", specifier = ">=1.3.0.post0" },
{ name = "pillow", specifier = ">=10.0.1,<11" }, { name = "pillow", specifier = ">=10.0.1,<11" },
{ name = "protobuf", specifier = ">=5.29.3,<6" }, { name = "protobuf", specifier = ">=5.29.3,<6" },
{ name = "py-leap", editable = "../py-leap" }, { name = "py-leap", git = "https://github.com/guilledk/py-leap.git?branch=struct_unwrap" },
{ name = "pytz", specifier = "~=2023.3.post1" }, { name = "pytz", specifier = "~=2023.3.post1" },
{ name = "toml", specifier = ">=0.10.2,<0.11" }, { name = "toml", specifier = ">=0.10.2,<0.11" },
{ name = "trio", specifier = ">=0.22.2,<0.23" }, { name = "trio", specifier = ">=0.22.2,<0.23" },
@ -2462,7 +2436,7 @@ name = "triton"
version = "3.1.0" version = "3.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "filelock", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "filelock" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 }, { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 },