Initial commit: Replacing repo with local files

This commit is contained in:
2026-02-13 09:22:48 -06:00
commit 4970133b83
62 changed files with 4934 additions and 0 deletions
@@ -0,0 +1,140 @@
defmodule RiverConnectWeb.AudioLive do
use RiverConnectWeb, :live_view
alias RiverConnect.Audio
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
RiverConnectWeb.Endpoint.subscribe("audio:lobby")
end
# Fetch initial messages
messages = Audio.list_messages()
{:ok, assign(socket, messages: messages, recording: false, page_title: "Voxer Audio")}
end
@impl true
def handle_event("recording_started", %{"id" => _id}, socket) do
{:noreply, assign(socket, recording: true)}
end
@impl true
def handle_event("recording_stopped", _params, socket) do
{:noreply, assign(socket, recording: false)}
end
@impl true
def handle_info(%{event: "audio_message_created", payload: %{message: message}}, socket) do
# Prepend new message
messages = [message | socket.assigns.messages]
{:noreply, assign(socket, messages: messages)}
end
@impl true
def handle_info(%{event: "audio_message_ready", payload: %{id: _id}}, socket) do
# Reload message to get updated status/path
# In a real app we'd update just the one message, here we refresh all for simplicity
messages = Audio.list_messages()
{:noreply, assign(socket, messages: messages)}
end
# Forward other broadcasts (like chunks) which are handled by JS, but if they come here we ignore
@impl true
def handle_info(_, socket), do: {:noreply, socket}
@impl true
def render(assigns) do
~H"""
<div class="p-4 max-w-md mx-auto h-screen flex flex-col">
<h1 class="text-2xl font-bold mb-4 text-center">Voxer Clone</h1>
<div class="flex-1 overflow-y-auto mb-4 space-y-4 pr-2">
<h2 class="text-xl font-semibold sticky top-0 bg-white p-2 border-b">Recent Messages</h2>
<%= if Enum.empty?(@messages) do %>
<p class="text-gray-500 text-center py-8">No messages yet. Start talking!</p>
<% end %>
<%= for message <- @messages do %>
<div class="bg-gray-50 p-3 rounded-lg shadow-sm border border-gray-200">
<div class="flex justify-between text-xs text-gray-500 mb-2">
<span class="font-mono truncate w-1/2" title={message.user_id}>{message.user_id}</span>
<span>{Calendar.strftime(message.inserted_at, "%H:%M:%S")}</span>
</div>
<%= if message.status == "ready" do %>
<audio controls src={message.file_path} class="w-full h-8"></audio>
<div class="text-right text-xs text-gray-400 mt-1">{message.duration_ms}ms</div>
<% else %>
<div class={[
"flex items-center justify-center p-2 text-xs rounded animate-pulse",
message.status == "recording" && "bg-red-50 text-red-700",
message.status != "recording" && "bg-yellow-50 text-yellow-700"
]}>
<%= if message.status == "recording" do %>
<span class="flex h-2 w-2 mr-2">
<span class="animate-ping absolute inline-flex h-2 w-2 rounded-full bg-red-400 opacity-75">
</span> <span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
</span>
🔴 LIVE
<button
type="button"
phx-click={Phoenix.LiveView.JS.dispatch("play_live", to: "#audio-recorder")}
class="ml-3 bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-[10px] font-bold shadow-sm active:scale-95 transition-all"
>
▶ PLAY
</button>
<% else %>
<svg class="animate-spin h-4 w-4 mr-2" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
>
</circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
>
</path>
</svg>
Processing...
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<div
id="audio-recorder"
phx-hook="AudioRecorder"
class="pb-8 pt-4 border-t bg-gray-50 -mx-4 px-4 flex flex-col items-center justify-center"
>
<button
id="record-btn"
class={"w-24 h-24 rounded-full flex items-center justify-center text-white text-4xl shadow-lg transition-all transform active:scale-95 " <>
if(@recording, do: "bg-red-600 animate-pulse ring-4 ring-red-200", else: "bg-blue-600 hover:bg-blue-700 hover:shadow-xl")}
>
<span class={
if @recording,
do:
"animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75",
else: "hidden"
}>
</span> {if @recording, do: "🎤", else: "🎙️"}
</button>
<p class={"mt-3 font-medium transition-colors " <> if(@recording, do: "text-red-600", else: "text-gray-600")}>
{if @recording, do: "Click to finish", else: "Click to Talk"}
</p>
</div>
</div>
"""
end
end