noahsettersten's picture
refactor: Clean up state in HomeLive.Index mount/3 call
8f4c6f0
raw
history blame
4.93 kB
defmodule MedicalTranscriptionWeb.HomeLive.Index do
use MedicalTranscriptionWeb, :live_view
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
# We're storing atoms in `:status` as LiveView's AsyncResult doesn't support modeling a "pending" state, but only
# loading / ok / failed.
initial_state = %{
current_recording_id: 0,
uploaded_file_name: nil,
status: :pending,
summary_keywords: []
}
socket =
socket
|> assign(initial_state)
|> stream(:transcription_rows, [])
|> allow_upload(:audio, accept: ~w(.mp3), max_entries: 1)
{:ok, socket}
end
@impl Phoenix.LiveView
def render(assigns) do
~H"""
<div class="flex-1 flex flex-col space-y-6">
<.result_heading
status={@status}
filename={@uploaded_file_name}
summary_keywords={@summary_keywords}
/>
<%= if @status != :pending do %>
<.result_list rows={@streams.transcription_rows} />
<% end %>
<%= if @status == :pending do %>
<div class="flex-1 flex flex-col justify-center items-center gap-4">
<img src={~p"/images/logo.svg"} width="106" />
<p class="text-2xl leading-normal font-semibold">Medical Code Transcriber</p>
</div>
<.upload_form audio_upload={@uploads.audio} />
<% end %>
</div>
"""
end
@impl true
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
@impl true
def handle_event("save", _params, socket) do
filename =
socket.assigns.uploads.audio.entries
|> Enum.at(0)
|> Map.get(:client_name)
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)
uploaded_file = Enum.at(uploaded_files, 0)
live_view_pid = self()
Task.async(fn -> transcribe_and_tag_audio(live_view_pid, uploaded_file) end)
socket =
socket
|> assign(:status, :loading)
|> assign(:summary_keywords, [])
|> assign(:transcription_rows, [])
|> assign(:uploaded_file_name, filename)
{:noreply, socket}
end
@impl true
def handle_event("add_feedback", params, socket) do
text_vector = MedicalTranscription.Coding.compute_vector_as_list(params["text"])
result =
params
|> Map.put("text_vector", text_vector)
|> MedicalTranscription.Feedback.track_response()
socket =
case result do
{:ok, message} -> put_flash(socket, :info, message)
{:error, messages} -> put_flash(socket, :error, messages)
end
{:noreply, socket}
end
@impl true
def handle_event("reset_to_pending", _params, socket) do
{:noreply, assign(socket, :status, :pending)}
end
@impl true
def handle_event("toggle_recording", _params, socket) do
recording = GenServer.call(RecordingPipeline, :toggle_recording)
new_status =
if recording do
:streaming_audio
else
:success
end
{:noreply, assign(socket, :status, new_status)}
end
@impl true
def handle_info({:transcription_row, chunk_result}, socket) do
# The processing sends a message as each chunk of text is coded. See here for some background and potential
# inspiration for this: https://elixirforum.com/t/liveview-asynchronous-task-patterns/44695
{:noreply, stream_insert(socket, :transcription_rows, chunk_result)}
end
@impl true
def handle_info({:new_keywords, new_keywords}, socket) do
socket =
update(socket, :summary_keywords, fn keywords -> keywords ++ [new_keywords] end)
{:noreply, socket}
end
@impl true
def handle_info({:received_audio_payload, transcribed_text}, socket) do
tags = MedicalTranscription.Coding.process_chunk(transcribed_text)
result = %{
id: socket.assigns.current_recording_id + 1,
start_mark: nil,
end_mark: nil,
text: transcribed_text,
tags: tags
}
socket =
socket
|> assign(:status, :streaming_audio)
|> stream_insert(:transcription_rows, result)
|> assign(:current_recording_id, result.id)
{:noreply, socket}
end
@impl true
def handle_info({ref, _result}, socket) do
# See this Fly article for the usage of Task.async to start `transcribe_and_tag_audio/2` and handle the end of the
# task here: https://fly.io/phoenix-files/liveview-async-task/
Process.demonitor(ref, [:flush])
{:noreply, assign(socket, :status, :success)}
end
defp transcribe_and_tag_audio(live_view_pid, audio_file_path) do
MedicalTranscription.Transcription.stream_transcription_and_search(
live_view_pid,
audio_file_path
)
end
def error_to_string(:too_large), do: "Too large"
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
end