Skip to content

Instantly share code, notes, and snippets.

@cigrainger
Created August 29, 2024 19:38
Show Gist options
  • Save cigrainger/9c636d94dc2351e72c2b83e2731e0fe2 to your computer and use it in GitHub Desktop.
Save cigrainger/9c636d94dc2351e72c2b83e2731e0fe2 to your computer and use it in GitHub Desktop.
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