| defmodule MedicodeWeb.Components.SidebarComponent do |
| @moduledoc """ |
| Main layout header component |
| """ |
| use MedicodeWeb, :live_component |
| use MedicodeWeb, :verified_routes |
|
|
| import MedicodeWeb.CoreComponents |
|
|
| alias Medicode.Transcriptions |
|
|
| @impl Phoenix.LiveComponent |
| def mount(socket) do |
| socket = |
| socket |
| |> assign(:status, nil) |
| |> assign(:upload_progress, 0) |
| |> allow_upload(:audio, |
| accept: ~w(.mp3), |
| max_entries: 1, |
| progress: &handle_upload_progress/3, |
| auto_upload: true |
| ) |
|
|
| {:ok, socket} |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def update(assigns, socket) do |
| if connected?(socket) do |
| Phoenix.PubSub.subscribe(:medicode_pubsub, "medicode:#{assigns.current_user.id}") |
| end |
|
|
| socket = |
| socket |
| |> assign(:current_user, assigns.current_user) |
| |> assign(:transcriptions, list_transcriptions(assigns.current_user)) |
|
|
| {:ok, socket} |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def render(assigns) do |
| ~H""" |
| <header class="hidden w-[335px] min-w-[335px] h-screen sticky top-8 pb-16 border-r border-gray-200 lg:flex flex-col gap-12 overflow-hidden overflow-y-auto"> |
| <div> |
| <.link navigate={~p"/"} class="flex justify-center items-center gap-2"> |
| <img src={~p"/images/logo.svg"} width="48" /> |
| |
| <h1 class="text-lg leading-normal px-2 font-semibold"> |
| MediCode |
| </h1> |
| </.link> |
| </div> |
| |
| <%= if @current_user do %> |
| <div class="flex-1 flex flex-col px-6 gap-4"> |
| <form |
| id="audio-form" |
| phx-submit="save" |
| phx-change="validate" |
| phx-target={@myself} |
| class="relative w-full flex overflow-hidden" |
| > |
| <div |
| phx-drop-target={@uploads.audio.ref} |
| class="cursor-pointer text-[0.8125rem] flex justify-center gap-2 w-full text-white font-semibold hover:text-slate-300 px-3 py-2 bg-emerald-600 rounded-lg" |
| > |
| <.icon name="hero-document-plus" /> |
| <label for={@uploads.audio.ref} class="cursor-pointer"> |
| Upload Audio File (.mp3) |
| </label> |
| <.live_file_input class="hidden" upload={@uploads.audio} /> |
| </div> |
| |
| <button |
| phx-click="save" |
| phx-target={@myself} |
| disabled={@upload_progress < 100} |
| type="submit" |
| data-audio={Enum.count(@uploads.audio.entries) > 0} |
| class="flex justify-center items-center border-box text-[0.8125rem] text-white hover:text-slate-300 px-0 py-2 bg-emerald-600 rounded-lg transition-all relative left-8 w-0 ml-0 data-[audio]:px-3 data-[audio]:left-0 data-[audio]:w-fit data-[audio]:ml-2" |
| > |
| <.icon name="hero-paper-airplane" /> |
| </button> |
| </form> |
| |
| <button |
| id="audio-recorder-button" |
| phx-hook="AudioRecorder" |
| phx-click="record_transcription" |
| phx-target={@myself} |
| navigate={~p"/transcriptions/new"} |
| class="text-[0.8125rem] flex justify-center gap-2 w-full text-white font-semibold hover:text-slate-300 px-3 py-2 bg-emerald-600 rounded-lg" |
| > |
| <.icon :if={@status == :streaming_audio} name="hero-speaker-wave" /> |
| <.icon :if={@status != :streaming_audio} name="hero-microphone" /> |
| <span>Record Patient Notes</span> |
| </button> |
| <div class="flex flex-col gap-4"> |
| <.link |
| :for={transcription <- @transcriptions} |
| navigate={~p"/transcriptions/ |
| class="font-semibold text-lg leading-normal" |
| > |
| <%= transcription.filename %> |
| </.link> |
| </div> |
| </div> |
| <% end %> |
|
|
| <ul class="flex flex-col items-center gap-4 px-4"> |
| <%= if @current_user do %> |
| <li class="text-[0.8125rem] leading-6 text-zinc-900"> |
| <%= @current_user.email %> |
| </li> |
| <li class="w-full"> |
| <.link |
| navigate={~p"/users/settings"} |
| class="text-[0.8125rem] text-left w-full block leading-6 text-zinc-900 font-semibold hover:text-zinc-700" |
| > |
| Settings |
| </.link> |
| </li> |
| <li class="w-full"> |
| <.link |
| href={~p"/users/log_out"} |
| method="delete" |
| class="text-[0.8125rem] text-left w-full block leading-6 text-zinc-900 font-semibold hover:text-zinc-700" |
| > |
| Log out |
| </.link> |
| </li> |
| <% end %> |
| </ul> |
|
|
| <%= if @current_user do %> |
| <div class="px-6 flex flex-col items-center"> |
| <%= if Medicode.Coding.icd9_present?() do %> |
| <div |
| class="w-full px-3 py-2 bg-emerald-600 text-white text-center rounded-lg" |
| title="Precalculated vector embeddings for classification labels were found." |
| > |
| <.icon name="hero-check-circle" /> Vector embeddings found! |
| </div> |
| <% else %> |
| <div |
| class="w-full px-3 py-2 bg-red-300 text-slate-900 text-center rounded-lg" |
| title="Precalculated vector embeddings for classification labels were found." |
| > |
| <.icon name="hero-x-circle" /> Vector embeddings need to be built. Run |
| <code class="p-1 text-sm bg-slate-300 rounded-lg">mix build_code_vectors</code> |
| on the server. |
| </div> |
| <% end %> |
| </div> |
| <% end %> |
| </header> |
| """ |
| end |
| |
| @impl Phoenix.LiveComponent |
| def handle_event("record_transcription", _params, socket) do |
| socket = |
| if socket.assigns.status == :streaming_audio do |
| socket |
| |> push_event("stop_audio_recording", %{}) |
| |> assign(:status, nil) |
| else |
| {:ok, new_transcription} = |
| Transcriptions.create_transcription(%{ |
| user_id: socket.assigns.current_user.id, |
| filename: "Untitled", |
| status: :recording |
| }) |
| |
| Medicode.TranscriptionSupervisor.start_transcription(new_transcription) |
| |
| transcriptions = list_transcriptions(socket.assigns.current_user) |
| |
| socket |
| |> assign(:status, :streaming_audio) |
| |> assign(:transcriptions, transcriptions) |
| |> push_event("start_audio_recording", %{}) |
| |> push_patch(to: ~p"/transcriptions/#{new_transcription.id}") |
| end |
| |
| {:noreply, socket} |
| end |
| |
| def handle_event("save", _params, socket) do |
| if socket.assigns.uploads.audio.entries == [] do |
| {:noreply, put_flash(socket, :error, "Please select an audio file")} |
| else |
| uploaded_files = |
| consume_uploaded_entries(socket, :audio, fn %{path: path}, _entry -> |
| dest = Path.join(System.tmp_dir(), Path.basename(path)) |
| File.cp!(path, dest) |
| {:ok, dest} |
| end) |
| |
| {:ok, transcription} = |
| Transcriptions.create_transcription(%{ |
| user_id: socket.assigns.current_user.id, |
| filename: Enum.at(uploaded_files, 0) |
| }) |
| |
| Phoenix.PubSub.broadcast( |
| :medicode_pubsub, |
| "medicode:#{socket.assigns.current_user.id}", |
| {:transcription_created, transcription.id} |
| ) |
| |
| Transcriptions.transcribe_audio(transcription) |
| |
| {:noreply, push_navigate(socket, to: ~p"/transcriptions/#{transcription.id}")} |
| end |
| end |
| |
| def handle_event("validate", _params, socket) do |
| {:noreply, socket} |
| end |
| |
| defp list_transcriptions(user) do |
| Transcriptions.list_transcriptions(user) |
| end |
| |
| defp handle_upload_progress(:audio, entry, socket) do |
| {:noreply, assign(socket, :upload_progress, entry.progress)} |
| end |
| end |
| |