diff --git a/config/config.exs b/config/config.exs index 796dc5b..1be349d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,9 +12,10 @@ config :diffuser, # Configures the endpoint config :diffuser, DiffuserWeb.Endpoint, - url: [host: "localhost"], + url: [host: "https://ai.silentsilas.com"], render_errors: [view: DiffuserWeb.ErrorView, accepts: ~w(html json), layout: false], pubsub_server: Diffuser.PubSub, + check_origin: false, live_view: [signing_salt: "mxn2AV/s"] # Configures the mailer @@ -51,7 +52,7 @@ config :phoenix, :json_library, Jason config :waffle, storage: Waffle.Storage.Local, # or {:system, "ASSET_HOST"} - asset_host: "http://localhost:4000" + asset_host: "https://ai.silentsilas.com" # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/config/dev.exs b/config/dev.exs index 9b0fd41..8008638 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -19,7 +19,7 @@ config :diffuser, Diffuser.Repo, config :diffuser, DiffuserWeb.Endpoint, # Binding to loopback ipv4 address prevents access from other machines. # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: 4000], + http: [ip: {0, 0, 0, 0}, port: 4000], check_origin: false, code_reloader: true, debug_errors: true, diff --git a/lib/diffuser/application.ex b/lib/diffuser/application.ex index e034a95..d96bb1f 100644 --- a/lib/diffuser/application.ex +++ b/lib/diffuser/application.ex @@ -18,7 +18,7 @@ defmodule Diffuser.Application do DiffuserWeb.Endpoint, # Start a worker by calling: Diffuser.Worker.start_link(arg) # {Diffuser.Worker, arg} - {Task.Supervisor, name: Diffuser.Generator.PromptRequestSupervisor} + Diffuser.Generator.PromptRequestQueue ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/diffuser/generator.ex b/lib/diffuser/generator.ex index a01d1d0..16d0589 100644 --- a/lib/diffuser/generator.ex +++ b/lib/diffuser/generator.ex @@ -36,7 +36,7 @@ defmodule Diffuser.Generator do """ def get_prompt_request!(id), - do: Repo.one!(from pr in PromptRequest, where: pr.id == ^id, preload: [:images]) + do: Repo.one!(from pr in PromptRequest, where: pr.id == ^id) |> Repo.preload(:images) @doc """ Creates a prompt_request. @@ -122,4 +122,10 @@ defmodule Diffuser.Generator do }) end) end + + def paginate_prompt_requests(params) do + PromptRequest + |> preload(:images) + |> Repo.paginate(params) + end end diff --git a/lib/diffuser/generator/prompt_request.ex b/lib/diffuser/generator/prompt_request.ex index c988002..eb429c7 100644 --- a/lib/diffuser/generator/prompt_request.ex +++ b/lib/diffuser/generator/prompt_request.ex @@ -8,6 +8,8 @@ defmodule Diffuser.Generator.PromptRequest do schema "prompt_requests" do field :prompt, :string field :status, :string, default: "queued" + field :steps, :integer + field :guidance_scale, :float has_many :images, PromptRequestResult, on_delete: :delete_all @@ -17,7 +19,7 @@ defmodule Diffuser.Generator.PromptRequest do @doc false def changeset(prompt_request, attrs) do prompt_request - |> cast(attrs, [:prompt, :status]) + |> cast(attrs, [:prompt, :status, :steps, :guidance_scale]) |> validate_required([:prompt]) end end diff --git a/lib/diffuser/generator/prompt_request_genserver.ex b/lib/diffuser/generator/prompt_request_genserver.ex deleted file mode 100644 index e2bbbdd..0000000 --- a/lib/diffuser/generator/prompt_request_genserver.ex +++ /dev/null @@ -1,98 +0,0 @@ -defmodule Diffuser.Generator.PromptRequestGenserver do - use GenServer - alias Diffuser.Generator - alias Diffuser.Generator.PromptRequest - alias DiffuserWeb.Endpoint - alias Diffuser.PythonHelper, as: Helper - - @path 'lib/diffuser/python' - - def new(%{prompt_request: %PromptRequest{} = prompt_request}) do - GenServer.start_link( - __MODULE__, - %{prompt_request: prompt_request}, - name: name_for(prompt_request) - ) - end - - def name_for(%PromptRequest{id: prompt_request_id}), - do: {:global, "prompt_request:#{prompt_request_id}"} - - def init(%{prompt_request: %PromptRequest{} = prompt_request}) do - send(self(), :start_prompt) - - {:ok, - %{ - prompt_request: prompt_request - }} - end - - def handle_info(:start_prompt, %{prompt_request: prompt_request} = state) do - with {:ok, %{prompt: prompt} = active_prompt} <- - update_and_broadcast_progress(prompt_request, "in_progress"), - :ok <- call_python(:test_script, :test_func, prompt), - %PromptRequest{} = prompt_request_with_results <- write_and_save_images(active_prompt), - {:ok, completed_prompt} <- - update_and_broadcast_progress(prompt_request_with_results, "finished") do - IO.inspect(completed_prompt) - {:noreply, state} - else - nil -> - raise("prompt not found") - - {:error, message} -> - raise(message) - end - end - - defp update_and_broadcast_progress(%PromptRequest{id: id} = prompt_request, new_status) do - {:ok, new_prompt} = Generator.update_prompt_request(prompt_request, %{status: new_status}) - :ok = Endpoint.broadcast("request:#{id}", "request", %{prompt_request: new_prompt}) - - {:ok, new_prompt} - end - - defp call_python(_module, _func, prompt) do - Port.open( - {:spawn, "python #{@path}/stable_diffusion.py --prompt #{prompt}"}, - [:binary, {:packet, 4}] - ) - - # TODO: We will want to flush, and get the image data from the script - # then write it to PromptResult - - # pid = Helper.py_instance(Path.absname(@path)) - # :python.call(pid, module, func, args) - - # pid - # |> :python.stop() - - :ok - end - - defp write_and_save_images(%PromptRequest{id: id, prompt: prompt}) do - height = :rand.uniform(512) - width = :rand.uniform(512) - IO.inspect(height) - - {:ok, resp} = - :httpc.request( - :get, - {'http://placekitten.com/#{height}/#{width}', []}, - [], - body_format: :binary - ) - - {{_, 200, 'OK'}, _headers, body} = resp - - Generator.create_prompt_request_results(id, [ - %{ - file_name: "#{prompt}.jpg", - filename: "#{prompt}.jpg", - binary: body - } - ]) - - Generator.get_prompt_request!(id) - end -end diff --git a/lib/diffuser/generator/prompt_request_queue.ex b/lib/diffuser/generator/prompt_request_queue.ex new file mode 100644 index 0000000..e48a0ae --- /dev/null +++ b/lib/diffuser/generator/prompt_request_queue.ex @@ -0,0 +1,50 @@ +defmodule Diffuser.Generator.PromptRequestQueue do + use GenServer + alias Diffuser.Generator.PromptRequestWorker + + ### GenServer API + + @doc """ + GenServer.init/1 callback + """ + def init(state) do + {:ok, state} + end + + @doc """ + GenServer.handle_call/3 callback + """ + def handle_call(:dequeue, _from, [value | state]) do + {:reply, value, state} + end + + def handle_call(:dequeue, _from, []), do: {:reply, nil, []} + + def handle_call(:queue, _from, state), do: {:reply, state, state} + + @doc """ + GenServer.handle_cast/2 callback + """ + def handle_cast({:enqueue, value}, state) do + {:noreply, state, {:continue, {:enqueue, value}}} + end + + def handle_continue({:enqueue, value}, state) when length(state) > 0 do + {:continue, {:enqueue, value}, state} + end + + def handle_continue({:enqueue, value}, state) do + PromptRequestWorker.start(value) + {:noreply, state} + end + + ### Client API / Helper functions + + def start_link(state \\ []) do + GenServer.start_link(__MODULE__, state, name: __MODULE__) + end + + def queue, do: GenServer.call(__MODULE__, :queue) + def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value}) + def dequeue, do: GenServer.call(__MODULE__, :dequeue) +end diff --git a/lib/diffuser/generator/prompt_request_supervisor.ex b/lib/diffuser/generator/prompt_request_supervisor.ex deleted file mode 100644 index 89684e2..0000000 --- a/lib/diffuser/generator/prompt_request_supervisor.ex +++ /dev/null @@ -1,27 +0,0 @@ -defmodule Diffuser.Generator.PromptRequestSupervisor do - use DynamicSupervisor - alias Diffuser.Generator.PromptRequest - - def start_link(init_arg) do - DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) - end - - @impl true - def init(_init_arg) do - DynamicSupervisor.init(strategy: :one_for_one) - end - - def start_prompt_request(%PromptRequest{} = prompt_request) do - Task.Supervisor.start_child( - __MODULE__, - Diffuser.Generator.PromptRequestGenserver, - :new, - [ - %{ - prompt_request: prompt_request - } - ], - restart: :transient - ) - end -end diff --git a/lib/diffuser/generator/prompt_request_worker.ex b/lib/diffuser/generator/prompt_request_worker.ex new file mode 100644 index 0000000..9e16039 --- /dev/null +++ b/lib/diffuser/generator/prompt_request_worker.ex @@ -0,0 +1,85 @@ +defmodule Diffuser.Generator.PromptRequestWorker do + alias Diffuser.Generator + alias Diffuser.Generator.PromptRequest + alias DiffuserWeb.Endpoint + alias Diffuser.Repo + + @path [:code.priv_dir(:diffuser), "python"] |> Path.join() + @steps 10 + @guidance_scale 7.5 + + def start(%PromptRequest{} = prompt_request) do + with {:ok, active_prompt} <- + update_and_broadcast_progress(prompt_request, "in_progress"), + {:ok, _file_location} <- call_python(:test_script, :test_func, active_prompt), + %PromptRequest{} = prompt_request_with_results <- + write_and_save_images(active_prompt), + {:ok, completed_prompt} <- + update_and_broadcast_progress(prompt_request_with_results, "finished") do + {:ok, completed_prompt |> Repo.preload(:images)} + else + nil -> + raise("prompt not found") + + {:error, message} -> + raise(message) + end + end + + defp update_and_broadcast_progress(%PromptRequest{id: id} = prompt_request, new_status) do + {:ok, new_prompt} = + Generator.update_prompt_request(prompt_request, %{ + status: new_status, + steps: @steps, + guidance_scale: @guidance_scale + }) + + :ok = Endpoint.broadcast("request:#{id}", "request", %{prompt_request: new_prompt}) + + {:ok, new_prompt} + end + + defp call_python(_module, _func, %{id: prompt_id, prompt: prompt}) do + port = + Port.open( + {:spawn, + ~s(python #{@path}/stable_diffusion.py --prompt "#{prompt}" --output "#{@path}/#{prompt_id}.png" --num-inference-steps #{@steps})}, + [:binary] + ) + + python_loop(port, prompt_id) + end + + defp python_loop(port, prompt_id) do + receive do + {^port, {:data, ":finished" <> msg}} -> + {:ok, msg} + + {^port, {:data, ":step" <> step}} -> + Endpoint.broadcast("request:#{prompt_id}", "progress", step) + python_loop(port, prompt_id) + + {^port, result} -> + IO.inspect(result, label: "RESULT") + python_loop(port, prompt_id) + end + end + + defp write_and_save_images(%PromptRequest{id: id}) do + file_path = "#{@path}/#{id}.png" + + with {:ok, body} <- File.read(file_path), + {:ok, _result} <- + Generator.create_prompt_request_result( + id, + %{ + file_name: "#{id}.png", + filename: "#{id}.png", + binary: body + } + ), + :ok <- File.rm(file_path) do + Generator.get_prompt_request!(id) + end + end +end diff --git a/lib/diffuser/repo.ex b/lib/diffuser/repo.ex index 0899a9b..016d1ae 100644 --- a/lib/diffuser/repo.ex +++ b/lib/diffuser/repo.ex @@ -2,4 +2,6 @@ defmodule Diffuser.Repo do use Ecto.Repo, otp_app: :diffuser, adapter: Ecto.Adapters.Postgres + + use Scrivener, page_size: 1 end diff --git a/lib/diffuser_web/live/prompt_request_live/form_component.ex b/lib/diffuser_web/live/prompt_request_live/form_component.ex index b7e5ad8..b022a15 100644 --- a/lib/diffuser_web/live/prompt_request_live/form_component.ex +++ b/lib/diffuser_web/live/prompt_request_live/form_component.ex @@ -42,9 +42,9 @@ defmodule DiffuserWeb.PromptRequestLive.FormComponent do end defp save_prompt_request(socket, :new, prompt_request_params) do - with {:ok, prompt_request} <- Generator.create_prompt_request(prompt_request_params), - {:ok, _pid} <- - Diffuser.Generator.PromptRequestSupervisor.start_prompt_request(prompt_request) do + with {:ok, prompt_request} <- Generator.create_prompt_request(prompt_request_params) do + Diffuser.Generator.PromptRequestQueue.enqueue(prompt_request) + {:noreply, socket |> put_flash(:info, "Prompt request created successfully") diff --git a/lib/diffuser_web/live/prompt_request_live/index.ex b/lib/diffuser_web/live/prompt_request_live/index.ex index 7d7b917..dff7afa 100644 --- a/lib/diffuser_web/live/prompt_request_live/index.ex +++ b/lib/diffuser_web/live/prompt_request_live/index.ex @@ -6,12 +6,14 @@ defmodule DiffuserWeb.PromptRequestLive.Index do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, :prompt_requests, list_prompt_requests())} + {:ok, socket} end @impl true def handle_params(params, _url, socket) do - {:noreply, apply_action(socket, socket.assigns.live_action, params)} + page = list_prompt_requests(params) + socket = socket |> apply_action(socket.assigns.live_action, params) |> assign(:page, page) + {:noreply, socket} end defp apply_action(socket, :edit, %{"id" => id}) do @@ -37,10 +39,10 @@ defmodule DiffuserWeb.PromptRequestLive.Index do prompt_request = Generator.get_prompt_request!(id) {:ok, _} = Generator.delete_prompt_request(prompt_request) - {:noreply, assign(socket, :prompt_requests, list_prompt_requests())} + {:noreply, assign(socket, :page, list_prompt_requests(%{"page" => "2"}))} end - defp list_prompt_requests do - Generator.list_prompt_requests() + defp list_prompt_requests(params) do + Generator.paginate_prompt_requests(params) end end diff --git a/lib/diffuser_web/live/prompt_request_live/index.html.heex b/lib/diffuser_web/live/prompt_request_live/index.html.heex index b8b3d47..2cbc9ba 100644 --- a/lib/diffuser_web/live/prompt_request_live/index.html.heex +++ b/lib/diffuser_web/live/prompt_request_live/index.html.heex @@ -15,14 +15,20 @@
Image | Prompt | |
---|---|---|
+ <%= for result <- prompt_request.images do %>
+ |
<%= prompt_request.prompt %> | @@ -35,4 +41,18 @@ |