Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

CinEx

cinex.livemd

CinEx

Mix.install([
  {:kino, "~> 0.12.3"},
  {:instructor, github: "acalejos/instructor_ex"},
  {:erlexec, "~> 2.0"},
  {:exterval, "~> 0.2.0"},
  {:req, "~> 0.4.0"}
])

Upload Struct

defmodule Upload do
  defstruct [:filename, :path]

  @video_types [:mp4, :ogg, :avi, :wmv, :mov]
  @audio_types [:wav, :mp3, :mpeg]
  @image_types [:jpeg, :jpg, :png, :gif, :svg, :pixel]

  defguard is_audio(ext) when ext in @audio_types
  defguard is_video(ext) when ext in @video_types
  defguard is_image(ext) when ext in @image_types
  defguard is_valid_upload(ext) when is_audio(ext) or is_video(ext) or is_image(ext)

  def accepted_types, do: @audio_types ++ @video_types ++ @image_types

  defp to_existing_atom(str) do
    try do
      {:ok, String.to_existing_atom(str)}
    rescue
      _ in ArgumentError ->
        {:error, "#{inspect(str)} is not an existing atom"}

      _e ->
        {:error, "Unknown Error ocurred in `String.to_existing_atom/1`"}
    end
  end

  def ext_type(filename) do
    with <<"."::utf8, rest::binary>> <- Path.extname(filename),
         {:ok, ext} <- to_existing_atom(rest) do
      ext
    end
  end

  def to_kino(upload = %__MODULE__{path: path}) do
    content = File.read!(upload.path)

    case ext_type(path) do
      ext when is_audio(ext) ->
        Kino.Audio.new(content, ext)

      ext when is_video(ext) ->
        Kino.Video.new(content, ext)

      ext when is_image(ext) ->
        Kino.Image.new(content, ext)
    end
  end

  def new(filename, path) do
    %__MODULE__{filename: filename, path: path}
  end

  def generate_temp_filename(extension \\ "mp4") do
    random_string = :crypto.strong_rand_bytes(8) |> Base.encode16()
    temp_dir = System.tmp_dir!()
    Path.join(temp_dir, "temp_#{random_string}.#{extension}")
  end
end

Setup State Management

defmodule Models do
  @oai_models Req.get!("https://api.openai.com/v1/models",
                auth: {:bearer, System.fetch_env!("LB_OPENAI_TOKEN")}
              )
              |> Map.get(:body)
              |> Map.get("data")
              |> Enum.filter(fn entry -> Map.get(entry, "id") |> String.contains?("gpt") end)
              |> Enum.sort_by(&amp;Map.get(&amp;1, "created"), :desc)
              |> Enum.map(fn entry ->
                model = Map.get(entry, "id")
                {model, model}
              end)

  @ollama_models Req.get!("http://localhost:11434/api/tags")
                 |> Map.get(:body)
                 |> Map.get("models")
                 |> Enum.sort_by(fn entry ->
                   {:ok, datetime, _offset} =
                     entry |> Map.get("modified_at") |> DateTime.from_iso8601()

                   datetime
                 end)
                 |> Enum.map(fn entry ->
                   name = Map.get(entry, "name")
                   {name, name}
                 end)
  # There are many different ways to authenticate. This uses API Keys 
  # See https://cloud.google.com/docs/authentication/api-keys
  # We filter for only Gemini 1.5 models since those are the only to support JSON mode in the API
  @gemini_models Req.get!("https://generativelanguage.googleapis.com/v1beta/models",
                   headers: %{"x-goog-api-key" => System.fetch_env!("LB_GOOGLE_GEMINI_API_KEY")}
                 )
                 |> Map.get(:body)
                 |> Map.get("models")
                 |> Enum.filter(fn entry ->
                   Map.get(entry, "name") |> String.contains?("gemini-1.5")
                 end)
                 |> Enum.map(fn entry ->
                   {Map.get(entry, "name"), Map.get(entry, "displayName")}
                 end)
  def oai_models, do: @oai_models
  def ollama_models, do: @ollama_models
  def gemini_models, do: @gemini_models
end
defmodule FormState do
  use Agent

  @openai_config [
    http_options: [receive_timeout: 10 * 60 * 1000],
    api_key: System.fetch_env!("LB_OPENAI_TOKEN"),
    api_url: "https://api.openai.com",
    adapter: Instructor.Adapters.OpenAI
  ]

  @ollama_config [
    http_options: [receive_timeout: 10 * 60 * 1000],
    api_key: "ollama",
    api_url: "http://localhost:11434",
    adapter: Instructor.Adapters.OpenAI
  ]

  @gemini_config [
    api_version: :v1beta,
    api_url: "https://generativelanguage.googleapis.com/",
    http_options: [receive_timeout: 60_000],
    api_key: System.fetch_env!("LB_GOOGLE_GEMINI_API_KEY"),
    adapter: Instructor.Adapters.Gemini
  ]

  def start_link(_init) do
    Agent.start_link(
      fn ->
        %{
          config: @openai_config,
          provider: :openai,
          prompt: "",
          retries: 2,
          debug: false,
          explain_outputs: true,
          model: Models.oai_models() |> hd() |> elem(0),
          adapter: Application.get_env(:instructor, :adapter, Instructor.Adapters.OpenAI)
        }
      end,
      name: __MODULE__
    )
  end

  def update(:provider, :openai) do
    Agent.update(__MODULE__, fn state ->
      state
      |> Map.put(:config, @openai_config)
      |> Map.put(:provider, :openai)
    end)
  end

  def update(:provider, :ollama) do
    Agent.update(__MODULE__, fn state ->
      state
      |> Map.put(:config, @ollama_config)
      |> Map.put(:provider, :ollama)
    end)
  end

  def update(:provider, :gemini) do
    Agent.update(__MODULE__, fn state ->
      state
      |> Map.put(:config, @gemini_config)
      |> Map.put(:provider, :gemini)
    end)
  end

  def update(key, value) do
    Agent.update(__MODULE__, fn state -> Map.put(state, key, value) end)
  end

  def get(key) do
    Agent.get(__MODULE__, fn state -> Map.get(state, key) end)
  end
end

defmodule EditHistory do
  use Agent

  def start_link(_init) do
    Agent.start_link(fn -> :queue.new() end, name: __MODULE__)
  end

  def push(%Upload{} = upload, prompt \\ nil) do
    Agent.update(__MODULE__, fn history ->
      :queue.snoc(history, {upload, prompt})
    end)
  end

  def undo_edit do
    Agent.get_and_update(__MODULE__, fn history ->
      popped = :queue.liat(history)
      {:queue.last(popped), popped}
    end)
  end

  def current do
    Agent.get(__MODULE__, fn history ->
      :queue.last(history)
    end)
  end

  def original do
    Agent.get(__MODULE__, fn history ->
      :queue.head(history)
    end)
  end

  def previous_edit do
    Agent.get(__MODULE__, fn history ->
      popped = :queue.liat(history)

      unless :queue.is_empty(popped) do
        :queue.last(popped)
      else
        nil
      end
    end)
  end

  def reset do
    Agent.get_and_update(__MODULE__, fn history ->
      original = :queue.head(history)
      {original, :queue.from_list([original])}
    end)
  end
end
Enum.each([EditHistory, FormState], &amp;Kino.start_child!/1)

Setup Boilerplate

defmodule Boilerplate do
  def placeholder,
    do: """
    
    
    
        
        
        Video Preview Placeholder with Spinner
        
            .video-preview-placeholder {
                width: 100%;
                max-width: 640px;
                height: 0;
                padding-bottom: 56.25%; /* 16:9 aspect ratio */
                border: 2px dashed #ccc;
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: #f9f9f9;
                color: #666;
                font-size: 20px;
                text-align: center;
                position: relative;
                box-sizing: border-box;
                margin: auto;
            }
            .spinner-container {
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }
            .spinner {
                border: 4px solid #f3f3f3;
                border-top: 4px solid #3498db;
                border-radius: 50%;
                width: 40px;
                height: 40px;
                animation: spin 2s linear infinite;
                margin-bottom: 10px;
            }
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
            .message {
                font-size: 16px;
                color: #666;
            }
        
    
    
        
            
                <%= if show_spinner do %>
                    
                <% end %>
                <%= message %>
            
        
    
    
    """

  def log_template,
    do: """
    
    
    
        
        
        Log Level Message Box
        
            .message-box {
                width: 100%;
                border: 2px solid;
                padding: 20px;
                box-sizing: border-box;
                margin: 20px 0;
                border-radius: 5px;
                font-size: 18px;
                text-align: left;
            }
            .message-box.error {
                border-color: #f44336;
                background-color: #fdecea;
                color: #f44336;
            }
            .message-box.success {
                border-color: #4caf50;
                background-color: #e8f5e9;
                color: #4caf50;
            }
            .message-box.info {
                border-color: #2196f3;
                background-color: #e3f2fd;
                color: #2196f3;
            }
        
    
    
        
            <%= message %>
        
    
    
    """

  def stdout_template,
    do: """
    
    
    
    
    
    <%= device %>
    
    body {
        background-color: #1e1e1e;
        color: #c5c8c6;
        font-family: "Courier New", Courier, monospace;
        margin: 0;
        padding: 20px 20px 20px 5px;
    }
    .container {
        border: 1px solid #444;
        border-radius: 5px;
        overflow: hidden;
    }
    .header {
        background-color: #444;
        color: #c5c8c6;
        padding: 10px;
        font-weight: bold;
        text-transform: uppercase;
    }
    .output {
        background-color: #1d1f21;
        border-left: 4px solid <%= border_color %>;
        padding: 12px 12px 12px 5px;
        font-size: 16px;
        color: #c5c8c6;
        white-space: pre-wrap;
        word-break: break-all;
    }
    
    
    
    
    
        <%= device %>
    
    
        <%= output %>
    
    
    
    
    """

  def make_stdout(output, device, border_color \\ "gray") do
    Kino.HTML.new(EEx.eval_string(stdout_template(), binding()))
  end

  def make_log(message, level) do
    Kino.HTML.new(EEx.eval_string(log_template(), binding()))
  end
end

Setup Form

original = Kino.Frame.new()
prompt = Kino.Input.textarea("Prompt")
upload = Kino.Input.file("Upload", accept: Upload.accepted_types())
errors = Kino.Frame.new(placeholder: false)
submit_button = Kino.Control.button("Run!")
submit_frame = Kino.Frame.new(placeholder: false)
undo_frame = Kino.Frame.new(placeholder: false)
reset_frame = Kino.Frame.new(placeholder: false)
undo_button = Kino.Control.button("Undo")
reset_button = Kino.Control.button("Reset")
output = Kino.Frame.new(placeholder: false)
logs = Kino.Frame.new(placeholder: false)
debug_checkbox = Kino.Input.checkbox("Verbose Mode")
debug_frame = Kino.Frame.new(placeholder: false)
explain_checkbox = Kino.Input.checkbox("Explain Outputs", default: true)
explain_frame = Kino.Frame.new(placeholder: false)
retries = Kino.Input.number("# Retries", default: 2)

provider =
  Kino.Input.select("Provider", [{:openai, "OpenAI"}, {:gemini, "Gemini"}, {:ollama, "Ollama"}])

models_frame = Kino.Frame.new(placeholder: false)
oai_select = Kino.Input.select("Model", Models.oai_models())
gemini_select = Kino.Input.select("Model", Models.gemini_models())

ollama_select =
  Kino.Input.select(
    "Model",
    if(Models.ollama_models() == [],
      do: [{nil, "Pull Local Model to Use Ollama"}],
      else: Models.ollama_models()
    )
  )

Kino.Frame.render(models_frame, oai_select)

Kino.Frame.render(
  original,
  Kino.HTML.new(
    EEx.eval_string(Boilerplate.placeholder(),
      message: "Upload Media to Get Started",
      show_spinner: false
    )
  )
)

model_info = Kino.Layout.grid([provider, models_frame], columns: 2)
inputs = Kino.Layout.grid([prompt, retries], columns: 2, gap: 10)

buttons =
  Kino.Layout.grid([submit_frame, undo_frame, reset_frame, explain_frame, debug_frame],
    columns: 7,
    gap: 1
  )

Kino.Layout.grid([original, model_info, inputs, upload, buttons, output, logs])

FFMPEG Instructions

defmodule Alfred do
  use Ecto.Schema
  use Instructor.Validator
  import Ecto.Changeset
  import Exterval
  @confidence_interval ~i<[0,10]//0.5>

  @system_prompt """
  You are the companion Agent to another Agent whose job is to product execve-styled arguments
  for programs given a specific prompt. Your job is to interpret and explain the output
  of the command after it has been run. You will be given the prompt / task that originally
  generated the command, then you will be given the command that was run, along with the
  output that was generated. You do not need to re-explain what the task was or regurgitate
  what the command was. You only need to explain what the output means within the context
  of the task. If the task / prompt was a question, you should determine whether the provided
  output directly answers the question and if it does not you should answer it based on the
  output. If the output is not relevant to the prompt this should also be noted.


  You will also provide a confidence score about how confident you are about the above explanation.
  The confidence score is separate from the explanation.
  """

  @primary_key false
  @doc """
  ## Field Descriptions:
  - explanation: Explanation of the output given the context of the task and command that was run
  - confidence: Rating from 0 to 10 in increments of 0.5 of how confident you are in your answer,
    with higher scores being more confident.
  """
  embedded_schema do
    field(:explanation, :string)
    field(:confidence, :float)
  end

  @impl true
  def validate_changeset(changeset) do
    changeset
    |> validate_inclusion(:confidence, @confidence_interval)
  end

  def execute(model, config, prompt, command, retries, outputs \\ [stdout: nil, stderr: nil]) do
    Instructor.chat_completion(
      [
        mode: :json,
        model: model,
        response_model: __MODULE__,
        max_retries: retries,
        messages:
          [
            %{
              role: "system",
              content: @system_prompt
            },
            %{
              role: "user",
              content: """
              Here's the prompt that generated the command: #{inspect(prompt)}
              """
            },
            %{
              role: "user",
              content: """
              Here's command: #{inspect(command)}
              """
            },
            Keyword.get(outputs, :stdout) &amp;&amp;
              %{
                role: "user",
                content: """
                stdout: #{inspect(Keyword.fetch!(outputs, :stdout))}
                """
              },
            Keyword.get(outputs, :stderr) &amp;&amp;
              %{
                role: "user",
                content: """
                stderr: #{inspect(Keyword.fetch!(outputs, :stderr))}
                """
              }
          ]
          |> Enum.filter(&amp; &amp;1)
      ],
      config
    )
  end
end
defmodule AutoFfmpeg do
  import Ecto.Changeset
  use Ecto.Schema
  use Instructor.Validator

  @system_prompt """
  You are a multimedia editor and your job is to receive tasks for multimedia editing and use
  the programs available to you (and only those) to complete the tasks. You will return arguments
  to be passed to the program assuming that the input file(s) has already been passed. You do not need to
  call the binary itself, you are only in charge of generating all subsequent
  arugments after inputs have been passed. Assume the output file path will be appended
  after the arguments you provide.

  You have access to the following programs: ffmpeg and ffprobe

  So assume the command already is composed of something like
  `ffmpeg -i input_file_path [..., args, ...] output_file_path` and you then pass arugments
  to complete the given task. You will also be provided the input file for context, but you
  should not include inputs in your arguments. Use the given file extension to determine how
  to form your arugments. You will also provide the output file
  extension / file type, since depending on the task it could differ from the input type. If the
  given task does not result in an operation that writes to a file, (eg. asking for timestamps
  where it is silent would result in writing to stdout), the extension would be `null`.

  If the command is such that it will output to stdout, you should output as JSON when
  possible.
  """

  @doc """
  ## Field Descriptions:
  - program: the executable program to call
  - arguments: execve-formatted arguments for the  command
  - output_ext: The extension (filetype) of the outputted file
  """
  @primary_key false
  embedded_schema do
    field(:program, Ecto.Enum, values: [:ffmpeg, :ffprobe])
    field(:arguments, {:array, :string})

    field(:output_ext, Ecto.Enum,
      values: [
        :mp4,
        :ogg,
        :avi,
        :wmv,
        :mov,
        :wav,
        :mp3,
        :mpeg,
        :jpeg,
        :jpg,
        :png,
        :gif,
        :svg,
        :pixel,
        :null
      ]
    )

    field(:output_path, :string, virtual: true)
  end

  @impl true
  def validate_changeset(
        changeset,
        context
      ) do
    changeset
    |> validate_required([:program, :arguments, :output_ext])
    |> validate_exclusion(:arguments, ~w(-i))
    |> validate_command(context)
  end

  defp validate_command(%Ecto.Changeset{valid?: false} = changeset, _context), do: changeset

  defp validate_command(
         changeset,
         %{
           upload_path: upload_path,
           debug: debug,
           debug_frame: debug_frame,
           output_frame: output_frame,
           prompt: prompt,
           explain: explain,
           retries: retries,
           model: model,
           config: config
         }
       ) do
    program = get_field(changeset, :program)
    program_args = get_field(changeset, :arguments)
    input_args = ["-i", upload_path]
    output_ext = get_field(changeset, :output_ext)

    # Only perform command if arguments pass validation
    # Otherwise skip running command and return arg errors
    output_args =
      cond do
        program == :ffprobe ->
          []

        output_ext == :null ->
          ["-f", "null", "-"]

        true ->
          [Upload.generate_temp_filename(Atom.to_string(output_ext))]
      end

    command =
      Enum.join([Atom.to_string(program) | input_args ++ program_args ++ output_args], " ")

    if debug do
      message = """
      Command: #{command}
      """

      Kino.Frame.append(
        debug_frame,
        Boilerplate.make_log(
          message,
          :info
        )
      )
    end

    case :exec.run(command, [
           :sync,
           :stdout,
           :stderr
         ]) do
      {:ok, result} when is_list(result) ->
        outputs =
          [:stdout, :stderr]
          |> Enum.map(fn device ->
            if Keyword.has_key?(result, device) do
              output = Enum.join(Keyword.fetch!(result, device), "")
              Kino.Frame.append(output_frame, Boilerplate.make_stdout(output, device))
              {device, output}
            else
              {device, nil}
            end
          end)

        if explain do
          case Alfred.execute(model, config, prompt, command, retries, outputs) do
            {:ok, %Alfred{explanation: explanation, confidence: confidence}} ->
              Kino.Frame.append(
                output_frame,
                Boilerplate.make_stdout(
                  "Explanation: #{explanation}\n\nConfidence: #{confidence}",
                  :alfred,
                  "green"
                )
              )

            {:error,
             %Ecto.Changeset{
               errors: [
                 explanation: {error, _extras}
               ],
               valid?: false
             }} ->
              Kino.Frame.append(
                debug_frame,
                Boilerplate.make_log("Trouble providing explanation: #{inspect(error)}", :error)
              )
          end
        end

        if program == :ffmpeg &amp;&amp; output_ext != :null do
          [output_path] = output_args
          put_change(changeset, :output_path, output_path)
        else
          changeset
        end

      {:error, result} when is_list(result) ->
        debug &amp;&amp;
          Kino.Frame.append(
            debug_frame,
            Boilerplate.make_log("Something Went Wrong! Retrying...", :error)
          )

        error =
          cond do
            Keyword.has_key?(result, :stderr) ->
              Keyword.fetch!(result, :stderr) |> Enum.join("")

            Keyword.has_key?(result, :stdout) ->
              Keyword.fetch!(result, :stdout) |> Enum.join("")

            Keyword.has_key?(result, :exit_signal) ->
              "Error resulted in exit code #{Keyword.fetch!(result, :exit_signal)}"

            true ->
              "Unexpected error occurred!"
          end

        add_error(
          changeset,
          :arguments,
          error,
          status: Keyword.get(result, :exit_status)
        )
    end
  end

  def execute(model, config, prompt, %{upload_path: upload_path} = context, retries) do
    Instructor.chat_completion(
      [
        mode: :json,
        model: model,
        validation_context:
          Map.merge(context, %{
            prompt: prompt,
            retries: retries,
            model: model,
            config: config
          }),
        response_model: __MODULE__,
        max_retries: retries,
        messages: [
          %{
            role: "system",
            content: @system_prompt
          },
          %{
            role: "user",
            content: """
            Here's the editing task: #{inspect(prompt)}
            """
          },
          %{
            role: "user",
            content: """
            Here's input file type: #{inspect(Upload.ext_type(upload_path))}
            """
          }
        ]
      ],
      config
    )
  end
end

Listeners

import Upload

[
  upload: upload,
  submit: submit_button,
  reset: reset_button,
  undo: undo_button,
  prompt: prompt,
  debug: debug_checkbox,
  retries: retries,
  explain: explain_checkbox,
  provider: provider,
  oai_select: oai_select,
  ollama_select: ollama_select,
  gemini_select: gemini_select
]
|> Kino.Control.tagged_stream()
|> Kino.listen(fn
  {model_select, %{type: :change, value: value}}
  when model_select in [:oai_select, :ollama_select, :gemini_select] ->
    FormState.update(:model, value)

  {:provider, %{type: :change, value: value}} ->
    Kino.Frame.clear(logs)
    current_provider = FormState.get(:provider)

    unless value == current_provider do
      case value do
        :ollama ->
          if Models.ollama_models() == [] do
            Kino.Frame.append(
              logs,
              Boilerplate.make_log(
                "You must have local models to use Ollama. Pull a model then rerun this notebook.",
                :error
              )
            )
          end

          Kino.Frame.render(models_frame, ollama_select)
          FormState.update(:provider, value)
          FormState.update(:model, Models.ollama_models() |> hd() |> elem(0))

        :openai ->
          Kino.Frame.render(models_frame, oai_select)
          FormState.update(:provider, value)
          FormState.update(:model, Models.oai_models() |> hd() |> elem(0))

        :gemini ->
          Kino.Frame.render(models_frame, gemini_select)
          FormState.update(:provider, value)
          FormState.update(:model, Models.gemini_models() |> hd() |> elem(0))

        _ ->
          Kino.Frame.append(
            logs,
            Boilerplate.make_log(
              "Bad provider selected. Only OpenAI and Ollama are currently supported.",
              :error
            )
          )
      end
    end

  {:explain, %{type: :change, value: value}} ->
    FormState.update(:explain_outputs, value)

  {:retries, %{type: :change, value: value}} ->
    FormState.update(:retries, value)

  {:debug, %{type: :change, value: value}} ->
    FormState.update(:debug, value)

  {:prompt, %{type: :change, value: prompt}} ->
    FormState.update(:prompt, prompt)

  {:upload,
   %{
     type: :change,
     value: %{
       file_ref: file_ref,
       client_name: filename
     }
   }} ->
    Kino.Frame.clear(logs)
    Kino.Frame.clear(output)
    ext_type = Upload.ext_type(filename)

    unless is_valid_upload(ext_type) do
      Kino.Frame.render(
        logs,
        Boilerplate.make_log(
          "File must be of one of the following types: #{inspect(Upload.accepted_types())}",
          :error
        )
      )
    else
      file_path =
        file_ref
        |> Kino.Input.file_path()

      tmp_path = Upload.generate_temp_filename(ext_type)
      _bytes_copied = File.copy!(file_path, tmp_path)
      upload = Upload.new(filename, tmp_path)
      Upload.to_kino(upload) |> then(&amp;Kino.Frame.render(original, &amp;1))
      EditHistory.push(upload)
      Kino.Frame.render(debug_frame, debug_checkbox)
      Kino.Frame.render(explain_frame, explain_checkbox)
      Kino.Frame.render(submit_frame, submit_button)
      Kino.Frame.clear(undo_frame)
      Kino.Frame.clear(reset_frame)
    end

  {:submit, %{type: :click}} ->
    Kino.Frame.clear(logs)
    Kino.Frame.clear(output)

    prompt = FormState.get(:prompt) |> String.trim()

    if prompt == "" do
      Kino.Frame.append(logs, Boilerplate.make_log("Prompt cannot be empty!", :error))
    else
      Kino.Frame.render(
        original,
        Kino.HTML.new(
          EEx.eval_string(Boilerplate.placeholder(), message: "Working...", show_spinner: true)
        )
      )

      {%Upload{} =
         current_upload, _old_prompt} = EditHistory.current()

      num_retries = FormState.get(:retries)
      model = FormState.get(:model)
      debug = FormState.get(:debug)

      if debug do
        message = """
        Provider: #{FormState.get(:provider)}

Adapter: #{FormState.get(:config) |> Keyword.fetch!(:adapter)}

Model: #{model}

Prompt: #{prompt} """ Kino.Frame.append( logs, Boilerplate.make_log( message, :info ) ) end case AutoFfmpeg.execute( model, FormState.get(:config), prompt, %{ upload_path: current_upload.path, debug: debug, debug_frame: logs, output_frame: output, explain: FormState.get(:explain_outputs) }, num_retries ) do {:ok, %AutoFfmpeg{output_path: output_path}} -> FormState.get(:debug) &amp;&amp; Kino.Frame.append(logs, Boilerplate.make_log("Success!", :success)) unless is_nil(output_path) do new_upload = Upload.new(current_upload.filename, output_path) EditHistory.push(new_upload, prompt) Upload.to_kino(new_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) else Upload.to_kino(current_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) end Kino.Frame.render(undo_frame, undo_button) Kino.Frame.render(reset_frame, reset_button) {:error, %Ecto.Changeset{ errors: errors, valid?: false }} -> Upload.to_kino(current_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) Kino.Frame.append( logs, Boilerplate.make_log("Failed after #{num_retries} attempts!", :error) ) Enum.each(errors, fn {field, error} -> Kino.Frame.append( logs, Boilerplate.make_log("Error on field #{inspect(field)}: #{inspect(error)}", :error) ) end) {:error, <<"LLM Adapter Error: ", error::binary>>} -> Upload.to_kino(current_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) {error, _binding} = error |> Code.eval_string() Kino.Frame.append( logs, Boilerplate.make_log("Error! Reference the error below for details", :error) ) Kino.Frame.append(logs, Kino.Tree.new(error)) {:error, <<"Invalid JSON returned from LLM: ", error::binary>>} -> Upload.to_kino(current_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) Kino.Frame.append(logs, Boilerplate.make_log(error, :error)) end end {:reset, %{type: :click}} -> {%Upload{} = original_upload, nil} = EditHistory.reset() Upload.to_kino(original_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) Kino.Frame.clear(logs) Kino.Frame.clear(output) Kino.Frame.clear(reset_frame) Kino.Frame.clear(undo_frame) {:undo, %{type: :click}} -> Kino.Frame.clear(logs) Kino.Frame.clear(output) case EditHistory.undo_edit() do nil -> Kino.Frame.append(logs, Kino.Text.new("Error! Cannot `Undo`. No previous edit.")) {%Upload{} = previous_upload, _previous_prompt} -> Upload.to_kino(previous_upload) |> then(&amp;Kino.Frame.render(original, &amp;1)) Kino.Frame.clear(logs) if EditHistory.previous_edit() == nil do Kino.Frame.clear(reset_frame) Kino.Frame.clear(undo_frame) end end end)