defmodule PyrauiWeb.DocsLive.LiveChatDocs do use PyrauiWeb, :html def render(assigns) do demo_messages = demo_messages() assigns = assigns |> assign(:chat_current_user, Map.get(assigns, :live_chat_current_user, "user-1")) |> assign(:chat_messages, Map.get(assigns, :"docs-chat-1_messages", demo_messages)) |> assign(:chat_typing_users, Map.get(assigns, :"docs-chat-1_typing_users", [])) ~H"""

Live Chat

Real-time messaging with typing indicators, quick replies, and LiveView-powered state.

<:header>
AS

Ava Stone

Senior Product Specialist · Online now

Live
<:toolbar>

The demo keeps conversation state inside the LiveView, streams responses, and simulates an agent reply so you can feel the real interaction cadence.


              <.live_chat
                id="support-chat"
                messages={@chat_messages}
                current_user={@chat_current_user}
                typing_users={@chat_typing_users}
              >
                <:header>...</:header>
                <:toolbar>...</:toolbar>
              </.live_chat>
            

LiveView Wiring

The docs LiveView keeps per-chat state using dynamic assigns. Here’s the distilled version you can copy into your project:


              defmodule MyAppWeb.SupportLive do
                use Phoenix.LiveView

                def mount(_params, _session, socket) do
                  messages = demo_messages()

                  {:ok,
                   socket
                   |> assign(:chat_current_user, "user-1")
                   |> assign(:chat_typing_users, [])
                   |> assign(:chat_reply_index, 0)
                   |> assign(:"support-chat_messages", messages)}
                end

                def handle_event("chat-send", %{"id" => "support-chat", "message" => message}, socket) do
                  trimmed = String.trim(message || "")

                  if trimmed == "" do
                    {:noreply, socket}
                  else
                    socket =
                      socket
                      |> append_message("support-chat", %{text: trimmed, user_id: "user-1", user_name: "You"})
                      |> assign(:chat_typing_users, [])

                    Process.send_after(self(), {:show_typing, "support-chat"}, 400)
                    Process.send_after(self(), {:bot_reply, "support-chat"}, 1400)

                    {:noreply, socket}
                  end
                end

                def handle_event("chat-typing", %{"id" => "support-chat"}, socket) do
                  Process.send_after(self(), {:clear_typing, "support-chat"}, 1200)
                  {:noreply, assign(socket, :chat_typing_users, ["Support Bot"])}
                end

                def handle_event("chat-quick-reply", %{"id" => "support-chat", "text" => text}, socket) do
                  handle_event("chat-send", %{"id" => "support-chat", "message" => text}, socket)
                end

                def handle_info({:show_typing, "support-chat"}, socket) do
                  {:noreply, assign(socket, :chat_typing_users, ["Support Bot"])}
                end

                def handle_info({:clear_typing, "support-chat"}, socket) do
                  {:noreply, assign(socket, :chat_typing_users, [])}
                end

                def handle_info({:bot_reply, "support-chat"}, socket) do
                  replies = demo_replies()
                  idx = rem(socket.assigns[:chat_reply_index], length(replies))

                  socket =
                    socket
                    |> assign(:chat_reply_index, socket.assigns[:chat_reply_index] + 1)
                    |> assign(:chat_typing_users, [])
                    |> append_message("support-chat", %{
                      text: Enum.at(replies, idx),
                      user_id: "bot",
                      user_name: "Support Bot"
                    })

                  {:noreply, socket}
                end

                defp append_message(socket, id, attrs) do
                  messages = Map.get(socket.assigns, :"#{id}_messages", [])
                  assign(socket, :"#{id}_messages", messages ++ [Map.put_new(attrs, :timestamp, DateTime.utc_now())])
                end
              end
            
""" end defp demo_messages do now = DateTime.utc_now() [ %{ user_id: "ava", user_name: "Ava Stone", text: "Welcome to PyraUI live support! 👋", timestamp: DateTime.add(now, -600, :second) }, %{ user_id: "ava", user_name: "Ava Stone", text: "Drop your question—I'll reply in real time with polished patterns.", timestamp: DateTime.add(now, -540, :second) }, %{ user_id: "user-1", user_name: "You", text: "Hey Ava! I'm trying to theme the dashboard chat area.", timestamp: DateTime.add(now, -420, :second) }, %{ user_id: "ava", user_name: "Ava Stone", text: "Love it. Are you targeting a light or glow surface?", timestamp: DateTime.add(now, -360, :second) } ] end end