Created
August 29, 2024 19:38
-
-
Save cigrainger/9c636d94dc2351e72c2b83e2731e0fe2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Mix.install( | |
[ | |
{:phoenix_playground, "~> 0.1.0"}, | |
{:openai, "~> 0.6.1"}, | |
{:makeup, "~> 1.1.2"}, | |
{:makeup_elixir, "~> 0.14"} | |
], | |
config: [ | |
openai: [ | |
api_key: System.get_env("OPENAI_API_KEY"), | |
organization_key: System.get_env("OPENAI_ORGANIZATION_KEY"), | |
http_options: [recv_timeout: 30_000] | |
] | |
] | |
) | |
defmodule ContainerLive do | |
use Phoenix.LiveView | |
def mount(_params, _session, socket) do | |
{:ok, assign(socket, view: nil, tab: :view, code: "", messages: [], version: 0)} | |
end | |
def render(assigns) do | |
assigns = assign(assigns, changeset: %{}) | |
~H""" | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style><%= Makeup.stylesheet() |> Phoenix.HTML.raw() %></style> | |
<div class="flex flex-col"> | |
<.form class="bg-slate-300 justify-between px-4 py-2 flex flex-row items-center" for={@changeset} phx-submit="generate_view"> | |
<div> | |
<h1 class="text-xl">Generate a view</h1> | |
<p>Enter a prompt to generate a view.</p> | |
</div> | |
<div class="flex flex-col space-y-1"> | |
<p><%= if @messages == [], do: "Write a simple Elixir Phoenix LiveView module that...", else: "Now..." %></p> | |
<textarea rows={4} name="prompt" /> | |
</div> | |
<button class="bg-slate-700 rounded-md text-white p-3" type="submit">Generate view</button> | |
</.form> | |
<div class="flex flex-col" :if={not is_nil(@view)}> | |
<div class="flex flex-row justify-between"> | |
<button phx-click="view" class="px-16 py-2">View</button> | |
<button phx-click="code" class="px-16 py-2">Code</button> | |
</div> | |
<div :if={@tab == :code}> | |
<%= @code |> Makeup.highlight() |> Phoenix.HTML.raw() %> | |
</div> | |
<div :if={@tab == :view}> | |
<%= live_render(@socket, @view, id: "#{@view}-#{@version}-view") %> | |
</div> | |
</div> | |
</div> | |
""" | |
end | |
def handle_event("generate_view", %{"prompt" => prompt}, %{assigns: %{messages: []}} = socket) do | |
messages = messages(prompt) | |
%{"content" => code} = new_message = ask_for_view(messages) | |
{:noreply, assign(socket, view: NewView, code: code, messages: messages ++ [new_message])} | |
end | |
def handle_event( | |
"generate_view", | |
%{"prompt" => prompt}, | |
%{assigns: %{version: version, messages: messages}} = socket | |
) do | |
messages = messages ++ [%{"role" => "user", "content" => prompt}] | |
%{"content" => code} = new_message = ask_for_view(messages) | |
{:noreply, | |
assign(socket, | |
view: NewView, | |
code: code, | |
messages: messages ++ [new_message], | |
version: version + 1 | |
)} | |
end | |
def handle_event("view", _, socket) do | |
{:noreply, assign(socket, tab: :view)} | |
end | |
def handle_event("code", _, socket) do | |
{:noreply, assign(socket, tab: :code)} | |
end | |
defp messages(prompt) do | |
[ | |
%{ | |
"role" => "system", | |
"content" => | |
"You are a helpful assistant with an expert knowledge of Elixir, Phoenix, and LiveView. You are a professional web developer. Phoenix.HTML is no longer supported in v4.0, so be sure to use the latest approaches." | |
}, | |
%{ | |
"role" => "user", | |
"content" => | |
"Write a simple but elegant Elixir Phoenix LiveView module that #{prompt}. You can use Tailwind classes. Respond with nothing but the code for the module. Make sure the module is named simply 'NewView' (and nothing else, not 'MyAppWeb.NewView', for example)." | |
} | |
] | |
end | |
defp ask_for_view(messages, retries \\ 3) | |
defp ask_for_view(_messages, 0) do | |
raise "Failed to generate a view after 3 retries. Please try again." | |
end | |
defp ask_for_view(messages, retries) do | |
{:ok, %{choices: [%{"message" => %{"content" => view} = message}]}} = | |
OpenAI.chat_completion( | |
model: "gpt-4o", | |
messages: messages | |
) | |
try do | |
view |> String.trim("```") |> String.trim("elixir") |> Code.compile_string() | |
rescue | |
e -> | |
messages = | |
messages ++ | |
[message] ++ | |
[ | |
%{ | |
"role" => "user", | |
"content" => | |
"I couldn't generate a view from that prompt. Please try again. This was the error: #{inspect(e)}" | |
} | |
] | |
ask_for_view(messages, retries - 1) | |
end | |
message | |
end | |
end | |
PhoenixPlayground.start(live: ContainerLive) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment