defmodule LivebookWeb.Output do use LivebookWeb, :html alias LivebookWeb.Output @doc """ Renders a single cell output. """ attr :id, :string, required: true attr :output, :map, required: true attr :session_id, :string, required: true attr :session_pid, :any, required: true attr :input_views, :map, required: true attr :client_id, :string, required: true attr :cell_id, :string, required: true def output(assigns) do ~H"""
<%= render_output(@output, %{ id: "#{@id}-output", session_id: @session_id, session_pid: @session_pid, input_views: @input_views, client_id: @client_id, cell_id: @cell_id }) %>
""" end defp border?(%{type: :terminal_text}), do: true defp border?(%{type: :error, context: {:interrupt, _, _}}), do: false defp border?(%{type: :error}), do: true defp border?(%{type: :grid, boxed: boxed}), do: boxed defp border?(_output), do: false defp render_output(%{type: :terminal_text, text: text}, %{id: id}) do text = if(text == :__pruned__, do: nil, else: text) assigns = %{id: id, text: text} ~H""" <.live_component module={Output.TerminalTextComponent} id={@id} text={@text} /> """ end defp render_output(%{type: :plain_text, text: text}, %{id: id}) do text = if(text == :__pruned__, do: nil, else: text) assigns = %{id: id, text: text} ~H""" <.live_component module={Output.PlainTextComponent} id={@id} text={@text} /> """ end defp render_output(%{type: :markdown, text: text}, %{id: id, session_id: session_id}) do text = if(text == :__pruned__, do: nil, else: text) assigns = %{id: id, session_id: session_id, text: text} ~H""" <.live_component module={Output.MarkdownComponent} id={@id} session_id={@session_id} text={@text} /> """ end defp render_output(%{type: :image} = output, %{id: id}) do assigns = %{id: id, content: output.content, mime_type: output.mime_type} ~H""" """ end defp render_output(%{type: :js} = output, %{ id: id, session_id: session_id, client_id: client_id }) do assigns = %{ id: id, js_view: output.js_view, session_id: session_id, client_id: client_id } ~H""" <.live_component module={LivebookWeb.JSViewComponent} id={@id} js_view={@js_view} session_id={@session_id} client_id={@client_id} timeout_message="Output data no longer available, please reevaluate this cell" /> """ end defp render_output(%{type: :frame} = output, %{ id: id, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id }) do assigns = %{ id: id, outputs: output.outputs, placeholder: output.placeholder, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id } ~H""" <.live_component module={Output.FrameComponent} id={@id} outputs={@outputs} placeholder={@placeholder} session_id={@session_id} session_pid={@session_pid} input_views={@input_views} client_id={@client_id} cell_id={@cell_id} /> """ end defp render_output(%{type: :tabs, outputs: outputs, labels: labels}, %{ id: id, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id }) do assigns = %{ id: id, labels: labels, outputs: outputs, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id } ~H""" <.live_component module={Output.TabsComponent} id={@id} outputs={@outputs} labels={@labels} session_id={@session_id} session_pid={@session_pid} input_views={@input_views} client_id={@client_id} cell_id={@cell_id} /> """ end defp render_output(%{type: :grid} = grid, %{ id: id, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id }) do assigns = %{ id: id, columns: grid.columns, gap: grid.gap, outputs: grid.outputs, session_id: session_id, session_pid: session_pid, input_views: input_views, client_id: client_id, cell_id: cell_id } ~H""" <.live_component module={Output.GridComponent} id={@id} outputs={@outputs} columns={@columns} gap={@gap} session_id={@session_id} session_pid={@session_pid} input_views={@input_views} client_id={@client_id} cell_id={@cell_id} /> """ end defp render_output(%{type: :input} = input, %{ id: id, input_views: input_views, session_pid: session_pid, client_id: client_id }) do assigns = %{ id: id, input: input, input_views: input_views, session_pid: session_pid, client_id: client_id } ~H""" <.live_component module={Output.InputComponent} id={@id} input={@input} input_views={@input_views} session_pid={@session_pid} client_id={@client_id} /> """ end defp render_output(%{type: :control} = control, %{ id: id, input_views: input_views, session_pid: session_pid, client_id: client_id, cell_id: cell_id }) do assigns = %{ id: id, control: control, input_views: input_views, session_pid: session_pid, client_id: client_id, cell_id: cell_id } ~H""" <.live_component module={Output.ControlComponent} id={@id} control={@control} input_views={@input_views} session_pid={@session_pid} client_id={@client_id} cell_id={@cell_id} /> """ end defp render_output( %{type: :error, context: {:missing_secret, secret_name}} = output, %{session_id: session_id, id: id} ) do assigns = %{message: output.message, secret_name: secret_name, session_id: session_id, id: id} ~H"""
<.remix_icon icon="close-circle-line" /> Missing secret <%= inspect(@secret_name) %>
<.button color="gray" patch={~p"/sessions/#{@session_id}/secrets?secret_name=#{@secret_name}"}> Add secret
<%= render_formatted_error_message(@id, @message) %>
""" end defp render_output( %{type: :error, context: {:file_entry_forbidden, file_entry_name}} = output, %{session_id: session_id, id: id} ) do assigns = %{ message: output.message, file_entry_name: file_entry_name, session_id: session_id, id: id } ~H"""
<.remix_icon icon="close-circle-line" /> Forbidden access to file <%= inspect(@file_entry_name) %>
<.button color="gray" phx-click={JS.push("review_file_entry_access", value: %{name: @file_entry_name})} > Review access
<%= render_formatted_error_message(@id, @message) %>
""" end defp render_output( %{type: :error, context: {:interrupt, variant, message}}, %{cell_id: cell_id} ) do assigns = %{variant: variant, message: message, cell_id: cell_id} ~H"""
"text-red-400 border-red-400" :normal -> "text-gray-500 border-gray-300" end ]}>
<%= @message %>
""" end defp render_output(%{type: :error, context: :dependencies} = output, %{id: id, cell_id: cell_id}) do assigns = %{message: output.message, id: id, cell_id: cell_id} if cell_id == Livebook.Notebook.Cell.setup_cell_id() do ~H"""
<.remix_icon icon="close-circle-line" /> Trouble installing dependencies? You may want to retry without cache.
<.button color="gray" phx-click={ JS.push("queue_cell_evaluation", value: %{cell_id: @cell_id, disable_dependencies_cache: true} ) } > Setup without cache
<%= render_formatted_error_message(@id, @message) %>
""" else render_formatted_error_message(id, output.message) end end defp render_output(%{type: :error, message: message}, %{id: id}) do render_formatted_error_message(id, message) end defp render_output(output, %{}) do req = Livebook.Runtime.Definitions.kino_requirement() render_error_message(""" Unknown output format: #{inspect(output)}. You may want to explicitly \ add {:kino, "#{req}"} as a notebook dependency or update to the latest \ Livebook. """) end defp render_error_message(message) do assigns = %{message: message} ~H""" """ end defp render_formatted_error_message(id, message) do assigns = %{id: id, message: message} ~H"""
""" end end