defmodule ElixirAiWeb.ChatMessage do use Phoenix.Component alias ElixirAiWeb.Markdown alias Phoenix.LiveView.JS attr :content, :string, required: true attr :tool_call_id, :string, required: true def tool_result_message(assigns) do ~H"""
tool result {@tool_call_id}
{@content}
""" end attr :content, :string, required: true def user_message(assigns) do ~H"""
{@content}
""" end attr :content, :string, required: true attr :reasoning_content, :string, default: nil attr :tool_calls, :list, default: [] def assistant_message(assigns) do assigns = assigns |> assign(:_reasoning_id, "reasoning-#{:erlang.phash2({assigns.content, assigns.reasoning_content, assigns.tool_calls})}") |> assign(:_expanded, false) ~H""" <.message_bubble reasoning_id={@_reasoning_id} content={@content} reasoning_content={@reasoning_content} tool_calls={@tool_calls} expanded={@_expanded} /> """ end attr :content, :string, required: true attr :reasoning_content, :string, default: nil attr :tool_calls, :list, default: [] def streaming_assistant_message(assigns) do assigns = assigns |> assign(:_reasoning_id, "reasoning-stream") |> assign(:_expanded, true) ~H""" <.message_bubble reasoning_id={@_reasoning_id} content={@content} reasoning_content={@reasoning_content} tool_calls={@tool_calls} expanded={@_expanded} /> """ end attr :content, :string, required: true attr :reasoning_content, :string, default: nil attr :tool_calls, :list, default: [] attr :reasoning_id, :string, required: true attr :expanded, :boolean, default: false defp message_bubble(assigns) do ~H"""
<%= if @reasoning_content && @reasoning_content != "" do %>
{Markdown.render(@reasoning_content)}
<% end %> <%= for tool_call <- @tool_calls do %> <.tool_call_item tool_call={tool_call} /> <% end %> <%= if @content && @content != "" do %>
{Markdown.render(@content)}
<% end %>
""" end # Dispatches to the appropriate tool call component based on result state attr :tool_call, :map, required: true defp tool_call_item(%{tool_call: tool_call} = assigns) do cond do Map.has_key?(tool_call, :error) -> assigns = assigns |> assign(:name, tool_call.name) |> assign(:arguments, tool_call[:arguments] || "") |> assign(:error, tool_call.error) ~H"<.error_tool_call name={@name} arguments={@arguments} error={@error} />" Map.has_key?(tool_call, :result) -> assigns = assigns |> assign(:name, tool_call.name) |> assign(:arguments, tool_call[:arguments] || "") |> assign(:result, tool_call.result) ~H"<.success_tool_call name={@name} arguments={@arguments} result={@result} />" true -> assigns = assigns |> assign(:name, tool_call.name) |> assign(:arguments, tool_call[:arguments] || "") ~H"<.pending_tool_call name={@name} arguments={@arguments} />" end end attr :name, :string, required: true attr :arguments, :string, default: "" defp pending_tool_call(assigns) do ~H"""
<.tool_call_icon /> {@name} running
<.tool_call_args arguments={@arguments} />
""" end attr :name, :string, required: true attr :arguments, :string, default: "" attr :result, :any, required: true defp success_tool_call(assigns) do assigns = assign(assigns, :result_str, case assigns.result do s when is_binary(s) -> s other -> inspect(other, pretty: true, limit: :infinity) end) ~H"""
<.tool_call_icon /> {@name} done
<.tool_call_args arguments={@arguments} />
result
{@result_str}
""" end attr :name, :string, required: true attr :arguments, :string, default: "" attr :error, :string, required: true defp error_tool_call(assigns) do ~H"""
<.tool_call_icon /> {@name} error
<.tool_call_args arguments={@arguments} />
error
{@error}
""" end attr :arguments, :string, default: "" defp tool_call_args(%{arguments: args} = assigns) when args != "" do assigns = assign(assigns, :pretty_args, case Jason.decode(args) do {:ok, decoded} -> Jason.encode!(decoded, pretty: true) _ -> args end) ~H"""
arguments
{@pretty_args}
""" end defp tool_call_args(assigns), do: ~H"" defp tool_call_icon(assigns) do ~H""" """ end end