diff --git a/.gitignore b/.gitignore index 00228771..001d306a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ GolemLib/bin/ GolemLib/obj/ Mock/bin/ Mock/obj/ -/FacadeApp/bin/ -/FacadeApp/obj/ +/FacadeHeadlessApp/bin/ +/FacadeHeadlessApp/obj/ /Golem/bin/ /Golem/obj/ /Golem.Tests/bin/ @@ -34,8 +34,8 @@ Mock/obj/ /Golem.Package/tests/ /Golem.Package/package/ -/ExampleRunner/bin/ -/ExampleRunner/obj/ +/example/ExampleRunner/bin/ +/example/ExampleRunner/obj/ /MockGUI/bin/ /MockGUI/obj/ @@ -48,6 +48,7 @@ Mock/obj/ /example/ai-requestor/.venv/ /example/ai-requestor/dist +example/ai-requestor/outputs/ output.png /example/DummyAiHttpServer/__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..af87f962 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build + +# Install python, because Golem.Tools need it to build App and it is dependency +# of `FacadeHeadlessApp` so we have no choice. +RUN apt-get update && apt-get install -y python3 python3-venv python3-pip +RUN ln -s /usr/bin/python3 /usr/bin/python +RUN pip3 install pyinstaller + +RUN git clone https://github.com/golemfactory/gamerhash-facade.git +WORKDIR /gamerhash-facade +RUN git checkout headless-facade + +# Build necessary application +RUN dotnet build FacadeHeadlessApp +RUN dotnet build Golem.Package + +RUN dotnet publish FacadeHeadlessApp --no-restore -o /apps +RUN dotnet publish Golem.Package --no-restore -o /apps + + +FROM mcr.microsoft.com/dotnet/runtime:7.0-jammy + +WORKDIR /apps +COPY --from=build /apps . + +RUN ./Golem.Package download --target modules --version v5.1.0 +ENTRYPOINT ["./FacadeHeadlessApp", "--golem", "modules"] +CMD ["--wallet", "0x82a630d2447ffd282657978f9f76c02da8be9819"] diff --git a/FacadeApp/Program.cs b/FacadeApp/Program.cs deleted file mode 100644 index d43bd917..00000000 --- a/FacadeApp/Program.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.ComponentModel; -using System.Diagnostics; - -using CommandLine; - -using Golem; - -using GolemLib; - -using Microsoft.Extensions.Logging; - -namespace FacadeApp -{ - public class FacadeAppArguments - { - [Option('g', "golem", Required = true, HelpText = "Path to a folder with golem executables")] - public string? GolemPath { get; set; } - [Option('d', "data_dir", Required = false, HelpText = "Path to the provider's data directory")] - public string? DataDir { get; set; } - [Option('m', "mainnet", Default = false, Required = false, HelpText = "Enables usage of mainnet")] - public required bool Mainnet { get; set; } - } - - - internal class Program - { - static async Task Main(string[] args) - { - ILoggerFactory loggerFactory = LoggerFactory.Create(builder => - builder.AddSimpleConsole() - ); - - var logger = loggerFactory.CreateLogger(); - - string golemPath = ""; - string? dataDir = null; - bool mainnet = false; - - Parser.Default.ParseArguments(args) - .WithParsed(o => - { - golemPath = o.GolemPath ?? ""; - dataDir = o.DataDir; - mainnet = o.Mainnet; - }); - - logger.LogInformation("Path: " + golemPath); - logger.LogInformation("DataDir: " + (dataDir ?? "")); - - var binaries = Path.Combine(golemPath, "golem"); - dataDir = Path.Combine(golemPath, "golem-data"); - - await using (var golem = (Golem.Golem)await new Factory().Create(golemPath, loggerFactory, mainnet)) - { - golem.PropertyChanged += new PropertyChangedHandler(logger).For(nameof(IGolem.Status)); - - bool end = false; - - do - { - Console.WriteLine("Start/Stop/End?"); - var line = Console.ReadLine(); - - switch (line) - { - case "Start": - await golem.Start(); - break; - case "Stop": - await golem.Stop(); - break; - case "End": - end = true; - break; - - case "Wallet": - var walletAddress = golem.WalletAddress; - golem.WalletAddress = walletAddress; - Console.WriteLine($"Wallet: {walletAddress}"); - break; - - default: Console.WriteLine($"Didn't understand: {line}"); break; - } - } while (!end); - } - - Console.WriteLine("Done"); - } - } - - public class PropertyChangedHandler - { - - public PropertyChangedHandler(ILogger logger) - { - this.logger = logger; - } - - readonly ILogger logger; - public PropertyChangedEventHandler For(string name) - { - switch (name) - { - case "Status": return Status_PropertyChangedHandler; - case "Activities": return Activities_PropertyChangedHandler; - default: return Empty_PropertyChangedHandler; - } - } - - private void Status_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) - { - if (sender is Golem.Golem golem && e.PropertyName != "Status") - logger.LogInformation($"Status property has changed: {e.PropertyName} to {golem.Status}"); - } - - private void Activities_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) - { - if (sender is Golem.Golem golem && e.PropertyName != "Activities") - logger.LogInformation($"Activities property has changed: {e.PropertyName}. Current job: {golem.CurrentJob}"); - } - - private void Empty_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) - { - logger.LogInformation($"Property {e} is not supported in this context"); - } - } -} diff --git a/FacadeHeadlessApp/Facade.cs b/FacadeHeadlessApp/Facade.cs new file mode 100644 index 00000000..d1ee7663 --- /dev/null +++ b/FacadeHeadlessApp/Facade.cs @@ -0,0 +1,136 @@ +using System.ComponentModel; +using System.Diagnostics; + +using CommandLine; + +using Golem; +using GolemLib; +using Golem.Tools.ViewModels; + +using Microsoft.Extensions.Logging; +using Golem.Tools; + +namespace FacadeHeadlessApp; + +public class FacadeAppArguments +{ + [Option('g', "golem", Required = true, HelpText = "Path to a folder with golem executables (modules)")] + public string? GolemPath { get; set; } + [Option('d', "use-dll", Required = false, HelpText = "Load Golem object from dll found in binaries directory. (Simulates how GamerHash will use it. Otherwise project dependency will be used.)")] + public bool UseDll { get; set; } + [Option('r', "relay", Default = RelayType.Central, Required = false, HelpText = "Change relay to devnet yacn2a or setup local")] + public required RelayType Relay { get; set; } + [Option('m', "mainnet", Default = false, Required = false, HelpText = "Enables usage of mainnet")] + public bool Mainnet { get; set; } + [Option('w', "wallet", Required = false, HelpText = "Wallet address to receive funds")] + public string? Wallet { get; set; } + [Option('i', "interactive", Default = false, HelpText = "Enable interactive console mode")] + public bool Interactive { get; set; } +} + + +internal class Facade +{ + static async Task Main(string[] argsArray) + { + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + builder.AddSimpleConsole() + ); + + var logger = loggerFactory.CreateLogger(); + + var args = Parser.Default.ParseArguments(argsArray).Value; + if (args == null) + return; + + string golemPath = args.GolemPath ?? ""; + + logger.LogInformation("Path: " + golemPath); + + await using GolemViewModel view = args.UseDll ? + await GolemViewModel.Load(golemPath, args.Relay, args.Mainnet) : + await GolemViewModel.CreateStatic(golemPath, args.Relay, args.Mainnet); + + var golem = view.Golem; + if (args.Wallet != null) + golem.WalletAddress = args.Wallet; + golem.PropertyChanged += new PropertyChangedHandler(logger).For(nameof(IGolem.Status)); + + + if (args.Interactive) + { + bool end = false; + + do + { + Console.WriteLine("Start/Stop/End?"); + var line = Console.ReadLine(); + + switch (line) + { + case "Start": + await golem.Start(); + break; + case "Stop": + await golem.Stop(); + break; + case "End": + end = true; + break; + case "Wallet": + var walletAddress = golem.WalletAddress; + golem.WalletAddress = walletAddress; + Console.WriteLine($"Wallet: {walletAddress}"); + break; + + default: Console.WriteLine($"Didn't understand: {line}"); break; + } + } while (!end); + + + Console.WriteLine("Done"); + } + else + { + await golem.Start(); + await ConsoleHelper.WaitForCancellation(); + } + } +} + +public class PropertyChangedHandler +{ + + public PropertyChangedHandler(ILogger logger) + { + this._logger = logger; + } + + readonly ILogger _logger; + public PropertyChangedEventHandler For(string name) + { + return name switch + { + "Status" => Status_PropertyChangedHandler, + "Activities" => Activities_PropertyChangedHandler, + _ => Empty_PropertyChangedHandler, + }; + } + + private void Status_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) + { + if (sender is Golem.Golem golem && e.PropertyName == "Status") + _logger.LogInformation($"Status property has changed: {e.PropertyName} to {golem.Status}"); + } + + private void Activities_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) + { + if (sender is Golem.Golem golem && e.PropertyName != "Activities") + _logger.LogInformation($"Activities property has changed: {e.PropertyName}. Current job: {golem.CurrentJob}"); + } + + private void Empty_PropertyChangedHandler(object? sender, PropertyChangedEventArgs e) + { + _logger.LogInformation($"Property {e} is not supported in this context"); + } +} diff --git a/FacadeApp/FacadeApp.csproj b/FacadeHeadlessApp/FacadeHeadlessApp.csproj similarity index 88% rename from FacadeApp/FacadeApp.csproj rename to FacadeHeadlessApp/FacadeHeadlessApp.csproj index 60c2b473..54302a85 100644 --- a/FacadeApp/FacadeApp.csproj +++ b/FacadeHeadlessApp/FacadeHeadlessApp.csproj @@ -14,6 +14,7 @@ + diff --git a/FacadeApp/Properties/launchSettings.json b/FacadeHeadlessApp/Properties/launchSettings.json similarity index 100% rename from FacadeApp/Properties/launchSettings.json rename to FacadeHeadlessApp/Properties/launchSettings.json diff --git a/Golem.Tools/Console.cs b/Golem.Tools/Console.cs new file mode 100644 index 00000000..90d02ef6 --- /dev/null +++ b/Golem.Tools/Console.cs @@ -0,0 +1,48 @@ + +namespace Golem.Tools; + + +public class ConsoleHelper +{ + public static void WaitForCtrlC() + { + Console.TreatControlCAsInput = true; + + ConsoleKeyInfo cki; + do + { + cki = Console.ReadKey(); + } while (!(((cki.Modifiers & ConsoleModifiers.Control) != 0) && (cki.Key == ConsoleKey.C))); + } + + // Based on: https://www.meziantou.net/handling-cancelkeypress-using-a-cancellationtoken.htm + public static async Task WaitForCancellation() + { + using var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (sender, e) => + { + // We'll stop the process manually by using the CancellationToken + e.Cancel = true; + + // Change the state of the CancellationToken to "Canceled" + // - Set the IsCancellationRequested property to true + // - Call the registered callbacks + cts.Cancel(); + }; + + + while (true) + { + try + { + // We can't pass TimeSpan.MaxValue because it will throw an exception. + await Task.Delay(TimeSpan.FromHours(1), cts.Token); + } + catch (Exception e) when (e.IsCancelled()) + { + Console.WriteLine("Application received cancellation signal. Exiting..."); + return; + } + } + } +} diff --git a/MockGUI/GolemModel.cs b/Golem.Tools/GolemViewModel.cs similarity index 96% rename from MockGUI/GolemModel.cs rename to Golem.Tools/GolemViewModel.cs index 45c8cf19..885df9dc 100644 --- a/MockGUI/GolemModel.cs +++ b/Golem.Tools/GolemViewModel.cs @@ -1,23 +1,15 @@ -using System; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using GolemLib; using App; using System.Reflection; -using Microsoft.Extensions.Logging.Abstractions; -using Golem.Tools; -using Golem; -using System.Collections.Generic; -using Golem.Yagna.Types; using GolemLib.Types; -namespace MockGUI.ViewModels +namespace Golem.Tools.ViewModels { public class GolemViewModel : INotifyPropertyChanged, IAsyncDisposable { @@ -100,7 +92,7 @@ static async Task Create(string modulesDir, Func(); diff --git a/GolemLib.sln b/GolemLib.sln index 52f3de7f..74ff6165 100644 --- a/GolemLib.sln +++ b/GolemLib.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GolemLib", "GolemLib\GolemLib.csproj", "{40CD8A07-4E06-49EA-8B3E-A7D087596015}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FacadeApp", "FacadeApp\FacadeApp.csproj", "{8B1A5AC1-BC71-4565-BB56-A6DD22D967AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FacadeHeadlessApp", "FacadeHeadlessApp\FacadeHeadlessApp.csproj", "{8B1A5AC1-BC71-4565-BB56-A6DD22D967AF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Golem", "Golem\Golem.csproj", "{7C980FF1-986D-4059-BFB7-8B52AFC6D9F2}" EndProject @@ -17,7 +17,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Golem.Tools", "Golem.Tools\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Golem.Package", "Golem.Package\Golem.Package.csproj", "{299443D2-91EB-4C12-B654-3B9852E14120}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleRunner", "ExampleRunner\ExampleRunner.csproj", "{35C7BEC1-FA91-4B29-9C6C-48ABA08388D2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleRunner", "example\ExampleRunner\ExampleRunner.csproj", "{35C7BEC1-FA91-4B29-9C6C-48ABA08388D2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GolemLib.Tests", "GolemLib.Tests\GolemLib.Tests.csproj", "{840D9798-C88E-4120-B19C-FFE1B62CCBF3}" EndProject diff --git a/MockGUI/App.axaml.cs b/MockGUI/App.axaml.cs index 9b3b0a37..be4ef21a 100644 --- a/MockGUI/App.axaml.cs +++ b/MockGUI/App.axaml.cs @@ -1,12 +1,12 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using MockGUI.ViewModels; using CommandLine; using System.Threading.Tasks; using Avalonia.Threading; using Golem; +using Golem.Tools.ViewModels; namespace MockGUI; diff --git a/ExampleRunner/ExampleRunner.csproj b/example/ExampleRunner/ExampleRunner.csproj similarity index 82% rename from ExampleRunner/ExampleRunner.csproj rename to example/ExampleRunner/ExampleRunner.csproj index d7362193..fb557aba 100644 --- a/ExampleRunner/ExampleRunner.csproj +++ b/example/ExampleRunner/ExampleRunner.csproj @@ -1,7 +1,7 @@ - + diff --git a/ExampleRunner/Program.cs b/example/ExampleRunner/Program.cs similarity index 88% rename from ExampleRunner/Program.cs rename to example/ExampleRunner/Program.cs index 13287a80..5e552246 100644 --- a/ExampleRunner/Program.cs +++ b/example/ExampleRunner/Program.cs @@ -4,7 +4,7 @@ using Golem; -using GolemLib; +using Golem.Tools; using Microsoft.Extensions.Logging; @@ -63,7 +63,7 @@ static void Main(string[] args) logger.LogInformation("Press Ctrl+C To Terminate"); - waitForCtrlC(); + ConsoleHelper.WaitForCtrlC(); Task[] tasks = new Task[2]; tasks[0] = Task.Run(() => @@ -75,7 +75,7 @@ static void Main(string[] args) tasks[1] = Task.Run(() => { - waitForCtrlC(); + ConsoleHelper.WaitForCtrlC(); logger.LogInformation("Captured second Ctrl-C. Killing..."); App.Kill().Wait(100); @@ -84,15 +84,4 @@ static void Main(string[] args) Task.WaitAny(tasks); } - - static void waitForCtrlC() - { - Console.TreatControlCAsInput = true; - - ConsoleKeyInfo cki; - do - { - cki = Console.ReadKey(); - } while (!(((cki.Modifiers & ConsoleModifiers.Control) != 0) && (cki.Key == ConsoleKey.C))); - } } diff --git a/example/ai-requestor/run-inference.sh b/example/ai-requestor/run-inference.sh new file mode 100755 index 00000000..e95b3d16 --- /dev/null +++ b/example/ai-requestor/run-inference.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +BASE_URL="https://f48cc2fe53a0.app.modelserve.dev-test.golem.network" +SEQ_NUM=1 +#PROMPT="Beatifull girl riding an unicorn flying above a rainbow." +PROMPT="close up portrait, Amidst the interplay of light and shadows in a photography studio,a soft spotlight traces the contours of a face,highlighting a figure clad in a sleek black turtleneck. The garment,hugging the skin with subtle luxury,complements the Caucasian model's understated makeup,embodying minimalist elegance. Behind,a pale gray backdrop extends,its fine texture shimmering subtly in the dim light,artfully balancing the composition and focusing attention on the subject. In a palette of black,gray,and skin tones,simplicity intertwines with profundity,as every detail whispers untold stories." + +mkdir -p outputs + +curl -X POST -H "Content-Type: application/json" \ + -d '{"prompt": "Beatifull girl riding an unicorn flying above a rainbow.", "num_inference_steps": 24, "guidance_scale": 3.5}' \ + ${BASE_URL}/sdapi/v1/txt2img \ + | jq -r ".images[0]" \ + | base64 --decode > outputs/output-${SEQ_NUM}.png \ + && xdg-open outputs/output-${SEQ_NUM}.png +