diff --git a/.gitignore b/.gitignore index 279dbf3..bd811a6 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -nostrzaps.db .DS_Store *.db diff --git a/backends/nova_server.py b/backends/nova_server.py index ce34f4c..9142193 100644 --- a/backends/nova_server.py +++ b/backends/nova_server.py @@ -33,7 +33,7 @@ def send_request_to_nova_server(request_form, address): url = ('http://' + address + '/process') headers = {'Content-type': 'application/x-www-form-urlencoded'} response = requests.post(url, headers=headers, data=request_form) - return response.content + return response.text """ @@ -58,9 +58,9 @@ def check_nova_server_status(jobID, address): response_status = requests.post(url_status, headers=headers, data=data) response_log = requests.post(url_log, headers=headers, data=data) status = int(json.loads(response_status.text)['status']) - - log = str(response_log.content)[length:] - length = len(str(response_log.content)) + log_content = str(json.loads(response_log.text)['message']).replace("ERROR", "").replace("INFO", "") + log = log_content[length:] + length = len(log_content) if log != "": print(log + " Status: " + str(status)) # WAITING = 0, RUNNING = 1, FINISHED = 2, ERROR = 3 @@ -74,7 +74,7 @@ def check_nova_server_status(jobID, address): data = {"jobID": jobID} response = requests.post(url_fetch, headers=headers, data=data) content_type = response.headers['content-type'] - print(content_type) + print("Content-type: " + str(content_type)) if content_type == "image/jpeg": image = Image.open(io.BytesIO(response.content)) image.save("./outputs/image.jpg") diff --git a/dvm.py b/dvm.py index 2cf123a..2fb5a64 100644 --- a/dvm.py +++ b/dvm.py @@ -84,7 +84,7 @@ class DVM: print("[" + self.dvm_config.NIP89.name + "] Request by blacklisted user, skipped") elif task_supported: - print("Received new Task: " + task + " from " + user.name) + print("[" + self.dvm_config.NIP89.name + "] Received new Task: " + task + " from " + user.name) amount = get_amount_per_task(task, self.dvm_config, duration) if amount is None: return @@ -95,11 +95,11 @@ class DVM: task_is_free = True if user.iswhitelisted or task_is_free: - print("[" + self.dvm_config.NIP89.name + "] Free or Whitelisted for task " + task + ". Starting processing..") + print("[" + self.dvm_config.NIP89.name + "] Free task or Whitelisted for task " + task + ". Starting processing..") send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client, dvm_config=self.dvm_config) do_work(nip90_event, is_from_bot=False) - # otherwise send payment request + else: bid = 0 for tag in nip90_event.tags(): @@ -254,17 +254,17 @@ class DVM: amount = x.amount x.result = data x.is_processed = True - if self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: + if self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and not is_paid: send_nostr_reply_event(data, original_event_str,) send_job_status_reaction(original_event, "success", amount, dvm_config=self.dvm_config) # or payment-required, or both? - elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: + elif not self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and not is_paid: send_job_status_reaction(original_event, "success", amount, dvm_config=self.dvm_config) # or payment-required, or both? - if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: + if self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and is_paid: self.job_list.remove(x) - elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: + elif not self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and is_paid: self.job_list.remove(x) send_nostr_reply_event(data, original_event_str) break @@ -296,7 +296,7 @@ class DVM: response_kind = original_event.kind() + 1000 reply_event = EventBuilder(response_kind, str(content), reply_tags).to_event(key) send_event(reply_event, client=self.client, dvm_config=self.dvm_config) - print("[" + self.dvm_config.NIP89.name + "]" + str(response_kind) + " Job Response event sent: " + reply_event.as_json()) + print("[" + self.dvm_config.NIP89.name + "] " + str(response_kind) + " Job Response event sent: " + reply_event.as_json()) return reply_event.as_json() def respond_to_error(content: str, original_event_as_str: str, is_from_bot=False): diff --git a/interfaces/dvmtaskinterface.py b/interfaces/dvmtaskinterface.py index e5c02db..426fbcd 100644 --- a/interfaces/dvmtaskinterface.py +++ b/interfaces/dvmtaskinterface.py @@ -1,3 +1,5 @@ +import json + from utils.nip89_utils import NIP89Announcement @@ -30,13 +32,19 @@ class DVMTaskInterface: pass @staticmethod - def setOptions(request_form): + def set_options(request_form): print("Setting options...") opts = [] - if request_form.get("optStr"): + if request_form.get("options"): + opts = json.loads(request_form["options"]) + print(opts) + + # old format, deprecated, will remove + elif request_form.get("optStr"): + opts = [] for k, v in [option.split("=") for option in request_form["optStr"].split(";")]: t = (k, v) opts.append(t) - print(k + "=" + v) print("...done.") + return dict(opts) diff --git a/main.py b/main.py index 9be20b2..dd10f61 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from threading import Thread import dotenv import utils.env as env @@ -14,7 +15,7 @@ def run_nostr_dvm_with_local_config(): #Generate a optional Admin Config, in this case, whenever we give our DVMS this config, they will (re)broadcast # their NIP89 announcement admin_config = AdminConfig() - admin_config.REBROADCASTNIP89 = True + admin_config.REBROADCASTNIP89 = False # Spawn the DVMs # Add NIP89 events for each DVM @@ -86,7 +87,7 @@ def run_nostr_dvm_with_local_config(): #We add an optional AdminConfig for this one, and tell the dvm to rebroadcast its NIP89 sketcher = ImageGenerationSDXL("Sketcher", dvm_config, admin_config, default_model="mohawk", default_lora="timburton") d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG2) - content = ("{\"name\":\"" + unstableartist.NAME + "\"," + content = ("{\"name\":\"" + sketcher.NAME + "\"," "\"image\":\"https://image.nostr.build/229c14e440895da30de77b3ca145d66d4b04efb4027ba3c44ca147eecde891f1.jpg\"," "\"about\":\"I draw images based on a prompt with a Model called unstable diffusion.\"," "\"nip90Params\":{}}") @@ -94,6 +95,8 @@ def run_nostr_dvm_with_local_config(): dvm_config.NIP89 = sketcher.NIP89_announcement(d_tag, content) + + if __name__ == '__main__': env_path = Path('.env') diff --git a/tasks/imagegenerationsdxl.py b/tasks/imagegenerationsdxl.py index 42bfee1..524c437 100644 --- a/tasks/imagegenerationsdxl.py +++ b/tasks/imagegenerationsdxl.py @@ -1,3 +1,4 @@ +import json import os from multiprocessing.pool import ThreadPool from threading import Thread @@ -14,6 +15,8 @@ This File contains a Module to transform Text input on NOVA-Server and receive r Accepted Inputs: Prompt (text) Outputs: An url to an Image +Params: -model # models: juggernaut, dynavision, colossusProject, newreality, unstable + -lora # loras (weights on top of models) voxel, """ @@ -24,7 +27,8 @@ class ImageGenerationSDXL(DVMTaskInterface): COST: int = 50 PK: str - def __init__(self, name, dvm_config: DVMConfig, admin_config: AdminConfig = None, default_model=None, default_lora=None): + def __init__(self, name, dvm_config: DVMConfig, admin_config: AdminConfig = None, default_model=None, + default_lora=None): self.NAME = name dvm_config.SUPPORTED_TASKS = [self] dvm_config.DB = "db/" + self.NAME + ".db" @@ -36,14 +40,13 @@ class ImageGenerationSDXL(DVMTaskInterface): nostr_dvm_thread = Thread(target=dvm, args=[dvm_config, admin_config]) nostr_dvm_thread.start() - def is_input_supported(self, input_type, input_content): if input_type != "text": return False return True def create_request_form_from_nostr_event(self, event, client=None, dvm_config=None): - request_form = {"jobID": event.id().to_hex() + "_"+ self.NAME.replace(" ", "")} + request_form = {"jobID": event.id().to_hex() + "_" + self.NAME.replace(" ", "")} request_form["trainerFilePath"] = 'modules\\stablediffusionxl\\stablediffusionxl.trainer' prompt = "" @@ -53,7 +56,6 @@ class ImageGenerationSDXL(DVMTaskInterface): else: model = self.default_model - # models: juggernautXL, dynavisionXL, colossusProjectXL, newrealityXL, unstable ratio_width = "1" ratio_height = "1" width = "" @@ -72,7 +74,7 @@ class ImageGenerationSDXL(DVMTaskInterface): prompt = tag.as_vec()[1] elif tag.as_vec()[0] == 'param': - print(tag.as_vec()[2]) + print("Param: " + tag.as_vec()[1] + ": " + tag.as_vec()[2]) if tag.as_vec()[1] == "negative_prompt": negative_prompt = tag.as_vec()[2] elif tag.as_vec()[1] == "lora": @@ -91,12 +93,12 @@ class ImageGenerationSDXL(DVMTaskInterface): split = tag.as_vec()[2].split(":") ratio_width = split[0] ratio_height = split[1] - #if size is set it will overwrite ratio. + # if size is set it will overwrite ratio. elif tag.as_vec()[1] == "size": if len(tag.as_vec()) > 3: - width = (tag.as_vec()[2]) - height = (tag.as_vec()[3]) + width = (tag.as_vec()[2]) + height = (tag.as_vec()[3]) elif len(tag.as_vec()) == 3: split = tag.as_vec()[2].split("x") if len(split) > 1: @@ -105,18 +107,39 @@ class ImageGenerationSDXL(DVMTaskInterface): elif tag.as_vec()[1] == "model": model = tag.as_vec()[2] - prompt = prompt.replace(";", ",") - request_form['data'] = '[{"id":"input_prompt","type":"input","src":"request:text","data":"' + prompt + '","active":"True"},{"id":"negative_prompt","type":"input","src":"request:text","data":"' + negative_prompt + '","active":"True"},{"id":"output_image","type":"output","src":"request:image","active":"True"}]' - request_form['options'] = ('[{"model":" + ' + model - + '","ratio":"' + str(ratio_width) + ':' + str(ratio_height) - + '","width":"' + str(width) + ':' + str(height) - + '","strength":"' + str(strength) - + '","guidance_scale":"' + str(guidance_scale) - + '","lora":"' + str(lora) - + '","lora_weight":"' + str(lora_weight) - + '"}]') + io_input = { + "id": "input_prompt", + "type": "input", + "src": "request:text", + "data": prompt + } + io_negative = { + "id": "negative_prompt", + "type": "input", + "src": "request:text", + "data": negative_prompt + } + io_output = { + "id": "output_image", + "type": "output", + "src": "request:image" + } + request_form['data'] = json.dumps([io_input, io_negative, io_output]) + options = { + "model": model, + "ratio": ratio_width + '-' + ratio_height, + "width": width, + "height": height, + "strength": strength, + "guidance_scale": guidance_scale, + "lora": lora, + "lora_weight": lora_weight + } + request_form['options'] = json.dumps(options) + + # old format, deprecated, will remove request_form["optStr"] = ('model=' + model + ';ratio=' + str(ratio_width) + '-' + str(ratio_height) + ';size=' + str(width) + '-' + str(height) + ';strength=' + str(strength) + ';guidance_scale=' + str(guidance_scale) + ';lora=' + lora + ';lora_weight=' + lora_weight) @@ -126,8 +149,9 @@ class ImageGenerationSDXL(DVMTaskInterface): def process(self, request_form): try: # Call the process route of NOVA-Server with our request form. - success = send_request_to_nova_server(request_form, os.environ["NOVA_SERVER"]) - print(success) + response = send_request_to_nova_server(request_form, os.environ["NOVA_SERVER"]) + if bool(json.loads(response)['success']): + print("Job " + request_form['jobID'] + " sent to nova-server") pool = ThreadPool(processes=1) thread = pool.apply_async(check_nova_server_status, (request_form['jobID'], os.environ["NOVA_SERVER"])) diff --git a/tasks/textextractionpdf.py b/tasks/textextractionpdf.py index 95aad4f..c5ce214 100644 --- a/tasks/textextractionpdf.py +++ b/tasks/textextractionpdf.py @@ -1,3 +1,4 @@ +import json import os import re from threading import Thread @@ -14,6 +15,7 @@ This File contains a Module to extract Text from a PDF file locally on the DVM M Accepted Inputs: Url to pdf file, Event containing an URL to a PDF file Outputs: Text containing the extracted contents of the PDF file +Params: None """ @@ -21,7 +23,7 @@ class TextExtractionPDF(DVMTaskInterface): NAME: str = "" KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT TASK: str = "pdf-to-text" - COST: int = 20 + COST: int = 0 PK: str def __init__(self, name, dvm_config: DVMConfig, admin_config: AdminConfig = None): @@ -59,7 +61,10 @@ class TextExtractionPDF(DVMTaskInterface): evt = get_event_by_id(input_content, client=client, config=dvm_config) url = re.search("(?Phttps?://[^\s]+)", evt.content()).group("url") - request_form["optStr"] = 'url=' + url + options = { + "url": url, + } + request_form['options'] = json.dumps(options) return request_form def process(self, request_form): @@ -67,7 +72,7 @@ class TextExtractionPDF(DVMTaskInterface): from pathlib import Path import requests - options = DVMTaskInterface.setOptions(request_form) + options = DVMTaskInterface.set_options(request_form) try: file_path = Path('temp.pdf') diff --git a/tasks/translation.py b/tasks/translation.py index a13748d..1511fbe 100644 --- a/tasks/translation.py +++ b/tasks/translation.py @@ -1,3 +1,4 @@ +import json from threading import Thread from dvm import DVM @@ -12,6 +13,7 @@ This File contains a Module to call Google Translate Services locally on the DVM Accepted Inputs: Text, Events, Jobs (Text Extraction, Summary, Translation) Outputs: Text containing the Translation in the desired language. +Params: -language The target language """ @@ -79,15 +81,18 @@ class Translation(DVMTaskInterface): text = evt.content() break - request_form["optStr"] = ('translation_lang=' + translation_lang + ';text=' + - text.replace('\U0001f919', "").replace("=", "equals"). - replace(";", ",")) + options = { + "text": text, + "language": translation_lang + } + request_form['options'] = json.dumps(options) + return request_form def process(self, request_form): from translatepy.translators.google import GoogleTranslate - options = DVMTaskInterface.setOptions(request_form) + options = DVMTaskInterface.set_options(request_form) gtranslate = GoogleTranslate() length = len(options["text"]) @@ -98,7 +103,7 @@ class Translation(DVMTaskInterface): text_part = options["text"][step:step + 5000] step = step + 5000 try: - translated_text_part = str(gtranslate.translate(text_part, options["translation_lang"])) + translated_text_part = str(gtranslate.translate(text_part, options["language"])) print("Translated Text part:\n\n " + translated_text_part) except Exception as e: raise Exception(e) @@ -108,7 +113,7 @@ class Translation(DVMTaskInterface): if step < length: text_part = options["text"][step:length] try: - translated_text_part = str(gtranslate.translate(text_part, options["translation_lang"])) + translated_text_part = str(gtranslate.translate(text_part, options["language"])) print("Translated Text part:\n " + translated_text_part) except Exception as e: raise Exception(e) diff --git a/test_client.py b/test_client.py index 6b5230d..3083c96 100644 --- a/test_client.py +++ b/test_client.py @@ -9,7 +9,7 @@ from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotificatio from utils.dvmconfig import DVMConfig from utils.nostr_utils import send_event -from utils.definitions import EventDefinitions, RELAY_LIST +from utils.definitions import EventDefinitions import utils.env as env @@ -74,7 +74,8 @@ def nostr_client(): pk = keys.public_key() print(f"Nostr Client public key: {pk.to_bech32()}, Hex: {pk.to_hex()} ") client = Client(keys) - for relay in RELAY_LIST: + dvmconfig = DVMConfig() + for relay in dvmconfig.RELAY_LIST: client.add_relay(relay) client.connect() @@ -85,7 +86,7 @@ def nostr_client(): EventDefinitions.KIND_FEEDBACK]).since(Timestamp.now())) # public events client.subscribe([dm_zap_filter, dvm_filter]) - # nostr_client_test_translation("This is the result of the DVM in spanish", "text", "es", 20, 20) + #nostr_client_test_translation("This is the result of the DVM in spanish", "text", "es", 20, 20) #nostr_client_test_translation("note1p8cx2dz5ss5gnk7c59zjydcncx6a754c0hsyakjvnw8xwlm5hymsnc23rs", "event", "es", 20,20) #nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20) diff --git a/utils/database_utils.py b/utils/database_utils.py index 735ad0e..47ba275 100644 --- a/utils/database_utils.py +++ b/utils/database_utils.py @@ -198,7 +198,6 @@ def get_or_add_user(db, npub, client): add_to_sql_table(db, npub, NEW_USER_BALANCE, False, False, nip05, lud16, name, Timestamp.now().as_secs()) user = get_from_sql_table(db, npub) - print(user) else: # update Name, Nip05 and lud16 lnaddress user.name, user.nip05, user.lud16 = fetch_user_metadata(npub, client) diff --git a/utils/dvmconfig.py b/utils/dvmconfig.py index be93749..74fd5a2 100644 --- a/utils/dvmconfig.py +++ b/utils/dvmconfig.py @@ -19,7 +19,7 @@ class DVMConfig: NIP89: NIP89Announcement REQUIRES_NIP05: bool = False - SHOWRESULTBEFOREPAYMENT: bool = True # if this is true show results even when not paid right after autoprocess + SHOW_RESULT_BEFORE_PAYMENT: bool = True # if this is true show results even when not paid right after autoprocess diff --git a/utils/output_utils.py b/utils/output_utils.py index d505ee7..6d1b0cd 100644 --- a/utils/output_utils.py +++ b/utils/output_utils.py @@ -15,12 +15,11 @@ Post process results to either given output format or a Nostr readable plain tex def post_process_result(anno, original_event): - print("post-processing...") + print("Post-processing...") if isinstance(anno, pandas.DataFrame): # if input is an anno we parse it to required output format for tag in original_event.tags(): print(tag.as_vec()[0]) if tag.as_vec()[0] == "output": - print("HAS OUTPUT TAG") output_format = tag.as_vec()[1] print("requested output is " + str(tag.as_vec()[1]) + "...") try: