Live Chat

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

AS

Ava Stone

Senior Product Specialist · Online now

Live
Ava Stone
Welcome to PyraUI live support! 👋
05:15
Ava Stone
Drop your question—I'll reply in real time with polished patterns.
05:16
Hey Ava! I'm trying to theme the dashboard chat area.
05:18
Ava Stone
Love it. Are you targeting a light or glow surface?
05:19

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