Commit
•
deecaf3
1
Parent(s):
8cf56d2
wip: feat: Define show screen for transcriptions
Browse files- lib/medical_transcription/transcription.ex +0 -9
- lib/medical_transcription/transcription_server.ex +2 -2
- lib/medical_transcription/transcription_supervisor.ex +2 -2
- lib/medical_transcription/transcriptions.ex +12 -0
- lib/medical_transcription_web/live/home_live/index.ex +18 -19
- lib/medical_transcription_web/live/transcriptions_live/show.ex +221 -0
- lib/medical_transcription_web/router.ex +1 -0
- test/medical_transcription/transcription_server_test.exs +6 -3
- test/medical_transcription_web/live/home_live_test.exs +16 -21
lib/medical_transcription/transcription.ex
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
defmodule MedicalTranscription.Transcription do
|
2 |
-
@moduledoc """
|
3 |
-
Takes a path to an audio file and transcribes it to text.
|
4 |
-
"""
|
5 |
-
|
6 |
-
def stream_transcription_and_search(audio_file_path) do
|
7 |
-
MedicalTranscription.TranscriptionSupervisor.start_transcription(audio_file_path)
|
8 |
-
end
|
9 |
-
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/medical_transcription/transcription_server.ex
CHANGED
@@ -20,8 +20,8 @@ defmodule MedicalTranscription.TranscriptionServer do
|
|
20 |
end
|
21 |
|
22 |
@impl GenServer
|
23 |
-
def handle_continue(:start, [
|
24 |
-
stream_transcription_and_search(
|
25 |
{:noreply, state}
|
26 |
end
|
27 |
|
|
|
20 |
end
|
21 |
|
22 |
@impl GenServer
|
23 |
+
def handle_continue(:start, [transcription: transcription] = state) do
|
24 |
+
stream_transcription_and_search(transcription.filename)
|
25 |
{:noreply, state}
|
26 |
end
|
27 |
|
lib/medical_transcription/transcription_supervisor.ex
CHANGED
@@ -11,8 +11,8 @@ defmodule MedicalTranscription.TranscriptionSupervisor do
|
|
11 |
DynamicSupervisor.init(strategy: :one_for_one)
|
12 |
end
|
13 |
|
14 |
-
def start_transcription(
|
15 |
-
spec = {MedicalTranscription.TranscriptionServer,
|
16 |
DynamicSupervisor.start_child(__MODULE__, spec)
|
17 |
end
|
18 |
end
|
|
|
11 |
DynamicSupervisor.init(strategy: :one_for_one)
|
12 |
end
|
13 |
|
14 |
+
def start_transcription(transcription) do
|
15 |
+
spec = {MedicalTranscription.TranscriptionServer, transcription: transcription}
|
16 |
DynamicSupervisor.start_child(__MODULE__, spec)
|
17 |
end
|
18 |
end
|
lib/medical_transcription/transcriptions.ex
CHANGED
@@ -8,6 +8,18 @@ defmodule MedicalTranscription.Transcriptions do
|
|
8 |
|
9 |
alias MedicalTranscription.Transcriptions.Transcription
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
@doc """
|
12 |
Returns the list of transcriptions.
|
13 |
|
|
|
8 |
|
9 |
alias MedicalTranscription.Transcriptions.Transcription
|
10 |
|
11 |
+
@doc """
|
12 |
+
Create transcription record and begin transcribing
|
13 |
+
|
14 |
+
## Examples
|
15 |
+
|
16 |
+
iex> create_and_transcribe_audio("my-audio.mp3")
|
17 |
+
%Transcription{audio_file: "my-audio.mp3"}
|
18 |
+
"""
|
19 |
+
def transcribe_audio(transcription) do
|
20 |
+
MedicalTranscription.TranscriptionSupervisor.start_transcription(transcription)
|
21 |
+
end
|
22 |
+
|
23 |
@doc """
|
24 |
Returns the list of transcriptions.
|
25 |
|
lib/medical_transcription_web/live/home_live/index.ex
CHANGED
@@ -85,19 +85,24 @@ defmodule MedicalTranscriptionWeb.HomeLive.Index do
|
|
85 |
{:ok, dest}
|
86 |
end)
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
101 |
end
|
102 |
|
103 |
@impl true
|
@@ -208,12 +213,6 @@ defmodule MedicalTranscriptionWeb.HomeLive.Index do
|
|
208 |
{:noreply, assign(socket, :status, :success)}
|
209 |
end
|
210 |
|
211 |
-
defp transcribe_and_tag_audio(audio_file_path) do
|
212 |
-
MedicalTranscription.Transcription.stream_transcription_and_search(
|
213 |
-
audio_file_path
|
214 |
-
)
|
215 |
-
end
|
216 |
-
|
217 |
def error_to_string(:too_large), do: "Too large"
|
218 |
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
|
219 |
|
|
|
85 |
{:ok, dest}
|
86 |
end)
|
87 |
|
88 |
+
{:ok, transcription} = Transcriptions.create_transcription(%{
|
89 |
+
user_id: socket.assigns.current_user.id,
|
90 |
+
filename: Enum.at(uploaded_files, 0)
|
91 |
+
})
|
92 |
+
|
93 |
+
Transcriptions.transcribe_audio(transcription)
|
94 |
+
|
95 |
+
{:noreply, push_navigate(socket, to: ~p"/transcriptions/#{transcription.id}")}
|
96 |
+
#
|
97 |
+
# socket =
|
98 |
+
# socket
|
99 |
+
# |> assign(:status, :loading)
|
100 |
+
# |> assign(:summary_keywords, [])
|
101 |
+
# |> assign(:transcription_rows, [])
|
102 |
+
# |> assign(:uploaded_file_name, filename)
|
103 |
+
# |> assign(:transcriptions, list_transcriptions(socket.assigns.current_user))
|
104 |
+
#
|
105 |
+
# {:noreply, socket}
|
106 |
end
|
107 |
|
108 |
@impl true
|
|
|
213 |
{:noreply, assign(socket, :status, :success)}
|
214 |
end
|
215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
def error_to_string(:too_large), do: "Too large"
|
217 |
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
|
218 |
|
lib/medical_transcription_web/live/transcriptions_live/show.ex
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
defmodule MedicalTranscriptionWeb.TranscriptionsLive.Show do
|
2 |
+
use MedicalTranscriptionWeb, :live_view
|
3 |
+
|
4 |
+
alias MedicalTranscription.Audio.RecordingPipeline
|
5 |
+
alias MedicalTranscription.Transcriptions
|
6 |
+
|
7 |
+
@impl Phoenix.LiveView
|
8 |
+
def mount(params, session, socket) do
|
9 |
+
dbg(params)
|
10 |
+
if connected?(socket), do: Phoenix.PubSub.subscribe(MedicalTranscription.PubSub, "transcriptions")
|
11 |
+
|
12 |
+
# We're storing atoms in `:status` as LiveView's AsyncResult doesn't support modeling a "pending" state, but only
|
13 |
+
# loading / ok / failed.
|
14 |
+
initial_state = %{
|
15 |
+
current_recording_id: 0,
|
16 |
+
uploaded_file_name: nil,
|
17 |
+
status: :pending,
|
18 |
+
audio_pipeline: nil,
|
19 |
+
summary_keywords: [],
|
20 |
+
transcriptions: list_transcriptions(session["current_user"]),
|
21 |
+
finalized_codes: []
|
22 |
+
}
|
23 |
+
|
24 |
+
socket =
|
25 |
+
socket
|
26 |
+
|> assign(initial_state)
|
27 |
+
|> stream_configure(:transcription_rows, dom_id: &"transcription-row-#{&1.id}")
|
28 |
+
|> stream(:transcription_rows, [])
|
29 |
+
|> allow_upload(:audio, accept: ~w(.mp3), max_entries: 1)
|
30 |
+
|
31 |
+
{:ok, socket}
|
32 |
+
end
|
33 |
+
|
34 |
+
@impl Phoenix.LiveView
|
35 |
+
def render(assigns) do
|
36 |
+
~H"""
|
37 |
+
<div class="min-h-[calc(100vh-56px)] flex gap-5">
|
38 |
+
<MedicalTranscriptionWeb.Components.SidebarComponent.sidebar
|
39 |
+
current_user={@current_user}
|
40 |
+
transcriptions={@transcriptions}
|
41 |
+
/>
|
42 |
+
|
43 |
+
<main class="flex-1 pl-16 pr-16 pt-[25px]">
|
44 |
+
<div class="flex flex-col h-full mx-auto max-w-5xl">
|
45 |
+
<div class="flex-1 flex flex-col space-y-6">
|
46 |
+
<%= if @status != :pending do %>
|
47 |
+
<.live_component
|
48 |
+
module={MedicalTranscriptionWeb.Components.ResultListComponent}
|
49 |
+
id="result_list"
|
50 |
+
rows={@streams.transcription_rows}
|
51 |
+
summary_keywords={@summary_keywords}
|
52 |
+
finalized_codes={@finalized_codes}
|
53 |
+
/>
|
54 |
+
<% end %>
|
55 |
+
|
56 |
+
<%= if @status == :pending do %>
|
57 |
+
<div class="flex-1 flex flex-col justify-center items-center gap-4">
|
58 |
+
<img src={~p"/images/logo.svg"} width="106" />
|
59 |
+
<p class="text-2xl leading-normal font-semibold">Medical Code Transcriber</p>
|
60 |
+
</div>
|
61 |
+
<.upload_form audio_upload={@uploads.audio} />
|
62 |
+
<% end %>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
</main>
|
66 |
+
</div>
|
67 |
+
"""
|
68 |
+
end
|
69 |
+
|
70 |
+
@impl true
|
71 |
+
def handle_event("validate", _params, socket) do
|
72 |
+
{:noreply, socket}
|
73 |
+
end
|
74 |
+
|
75 |
+
@impl true
|
76 |
+
def handle_event("save", _params, socket) do
|
77 |
+
filename =
|
78 |
+
socket.assigns.uploads.audio.entries
|
79 |
+
|> Enum.at(0)
|
80 |
+
|> Map.get(:client_name)
|
81 |
+
|
82 |
+
uploaded_files =
|
83 |
+
consume_uploaded_entries(socket, :audio, fn %{path: path}, _entry ->
|
84 |
+
dest = Path.join(System.tmp_dir(), Path.basename(path))
|
85 |
+
File.cp!(path, dest)
|
86 |
+
{:ok, dest}
|
87 |
+
end)
|
88 |
+
|
89 |
+
{:ok, transcription} = Transcriptions.create_transcription(%{
|
90 |
+
user_id: socket.assigns.current_user.id,
|
91 |
+
filename: Enum.at(uploaded_files, 0)
|
92 |
+
})
|
93 |
+
|
94 |
+
Transcriptions.transcribe_audio(transcription)
|
95 |
+
|
96 |
+
socket =
|
97 |
+
socket
|
98 |
+
|> assign(:status, :loading)
|
99 |
+
|> assign(:summary_keywords, [])
|
100 |
+
|> assign(:transcription_rows, [])
|
101 |
+
|> assign(:uploaded_file_name, filename)
|
102 |
+
|> assign(:transcriptions, list_transcriptions(socket.assigns.current_user))
|
103 |
+
|
104 |
+
{:noreply, socket}
|
105 |
+
end
|
106 |
+
|
107 |
+
@impl true
|
108 |
+
def handle_event("add_feedback", params, socket) do
|
109 |
+
text_vector = MedicalTranscription.Coding.compute_vector_as_list(params["text"])
|
110 |
+
|
111 |
+
result =
|
112 |
+
params
|
113 |
+
|> Map.put("text_vector", text_vector)
|
114 |
+
|> MedicalTranscription.Feedback.track_response()
|
115 |
+
|
116 |
+
socket =
|
117 |
+
case result do
|
118 |
+
{:ok, message} -> put_flash(socket, :info, message)
|
119 |
+
{:error, messages} -> put_flash(socket, :error, messages)
|
120 |
+
end
|
121 |
+
|
122 |
+
{:noreply, socket}
|
123 |
+
end
|
124 |
+
|
125 |
+
@impl true
|
126 |
+
def handle_event("reset_to_pending", _params, socket) do
|
127 |
+
{:noreply, assign(socket, :status, :pending)}
|
128 |
+
end
|
129 |
+
|
130 |
+
@impl true
|
131 |
+
def handle_event("toggle_recording", _params, socket) do
|
132 |
+
socket =
|
133 |
+
if is_nil(socket.assigns.audio_pipeline) do
|
134 |
+
pipeline = RecordingPipeline.start_pipeline(self())
|
135 |
+
|
136 |
+
socket
|
137 |
+
|> assign(:status, :streaming_audio)
|
138 |
+
|> assign(:audio_pipeline, pipeline)
|
139 |
+
else
|
140 |
+
RecordingPipeline.stop_pipeline(socket.assigns.audio_pipeline)
|
141 |
+
|
142 |
+
socket
|
143 |
+
|> assign(:status, :success)
|
144 |
+
|> assign(:audio_pipeline, nil)
|
145 |
+
end
|
146 |
+
|
147 |
+
{:noreply, socket}
|
148 |
+
end
|
149 |
+
|
150 |
+
@impl true
|
151 |
+
def handle_info({:chunk, chunk_result}, socket) do
|
152 |
+
# The processing sends a message as each chunk of text is coded. See here for some background and potential
|
153 |
+
# inspiration for this: https://elixirforum.com/t/liveview-asynchronous-task-patterns/44695
|
154 |
+
|
155 |
+
{:noreply, stream_insert(socket, :transcription_rows, chunk_result)}
|
156 |
+
end
|
157 |
+
|
158 |
+
@impl true
|
159 |
+
def handle_info({:new_keywords, new_keywords}, socket) do
|
160 |
+
socket =
|
161 |
+
update(socket, :summary_keywords, fn keywords -> keywords ++ [new_keywords] end)
|
162 |
+
|
163 |
+
{:noreply, socket}
|
164 |
+
end
|
165 |
+
|
166 |
+
@impl true
|
167 |
+
def handle_info({:received_audio_payload, transcribed_text}, socket) do
|
168 |
+
tags = MedicalTranscription.Coding.process_chunk(transcribed_text)
|
169 |
+
|
170 |
+
result = %{
|
171 |
+
id: socket.assigns.current_recording_id + 1,
|
172 |
+
start_mark: nil,
|
173 |
+
end_mark: nil,
|
174 |
+
text: transcribed_text,
|
175 |
+
tags: tags
|
176 |
+
}
|
177 |
+
|
178 |
+
socket =
|
179 |
+
socket
|
180 |
+
|> assign(:status, :streaming_audio)
|
181 |
+
|> stream_insert(:transcription_rows, result)
|
182 |
+
|> assign(:current_recording_id, result.id)
|
183 |
+
|
184 |
+
{:noreply, socket}
|
185 |
+
end
|
186 |
+
|
187 |
+
@impl Phoenix.LiveView
|
188 |
+
def handle_info({"toggle_user_selected_code", {:add, code}}, socket) do
|
189 |
+
new_codes =
|
190 |
+
if Enum.any?(socket.assigns.finalized_codes, code) do
|
191 |
+
socket.assigns.finalized_codes
|
192 |
+
else
|
193 |
+
socket.assigns.finalized_codes ++ [code]
|
194 |
+
end
|
195 |
+
|
196 |
+
{:noreply, assign(socket, :finalized_codes, new_codes)}
|
197 |
+
end
|
198 |
+
|
199 |
+
@impl Phoenix.LiveView
|
200 |
+
def handle_info({"toggle_user_selected_code", {:remove, code}}, socket) do
|
201 |
+
new_codes = Enum.reject(socket.assigns.finalized_codes, &(&1 == code))
|
202 |
+
|
203 |
+
{:noreply, assign(socket, :finalized_codes, new_codes)}
|
204 |
+
end
|
205 |
+
|
206 |
+
@impl true
|
207 |
+
def handle_info({ref, _result}, socket) do
|
208 |
+
# See this Fly article for the usage of Task.async to start `transcribe_and_tag_audio/2` and handle the end of the
|
209 |
+
# task here: https://fly.io/phoenix-files/liveview-async-task/
|
210 |
+
Process.demonitor(ref, [:flush])
|
211 |
+
|
212 |
+
{:noreply, assign(socket, :status, :success)}
|
213 |
+
end
|
214 |
+
|
215 |
+
def error_to_string(:too_large), do: "Too large"
|
216 |
+
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
|
217 |
+
|
218 |
+
defp list_transcriptions(user) do
|
219 |
+
Transcriptions.list_transcriptions(user)
|
220 |
+
end
|
221 |
+
end
|
lib/medical_transcription_web/router.ex
CHANGED
@@ -74,6 +74,7 @@ defmodule MedicalTranscriptionWeb.Router do
|
|
74 |
live "/users/settings", UserSettingsLive, :edit
|
75 |
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
76 |
live "/", HomeLive.Index
|
|
|
77 |
end
|
78 |
end
|
79 |
|
|
|
74 |
live "/users/settings", UserSettingsLive, :edit
|
75 |
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
76 |
live "/", HomeLive.Index
|
77 |
+
live "/transcriptions/:id", TranscriptionsLive.Show
|
78 |
end
|
79 |
end
|
80 |
|
test/medical_transcription/transcription_server_test.exs
CHANGED
@@ -5,6 +5,8 @@ defmodule MedicalTranscription.TranscriptionServerTest do
|
|
5 |
|
6 |
use MedicalTranscription.DataCase
|
7 |
|
|
|
|
|
8 |
alias MedicalTranscription.TranscriptionServer
|
9 |
|
10 |
setup do
|
@@ -13,11 +15,12 @@ defmodule MedicalTranscription.TranscriptionServerTest do
|
|
13 |
|> Path.join("../../medasrdemo-Paul.mp3")
|
14 |
|> Path.expand()
|
15 |
|
16 |
-
%{
|
|
|
17 |
end
|
18 |
|
19 |
-
test "transcribe and tag audio", %{
|
20 |
-
spec = {TranscriptionServer,
|
21 |
|
22 |
{:ok, pid} = start_supervised(spec)
|
23 |
|
|
|
5 |
|
6 |
use MedicalTranscription.DataCase
|
7 |
|
8 |
+
import MedicalTranscription.TranscriptionsFixtures
|
9 |
+
|
10 |
alias MedicalTranscription.TranscriptionServer
|
11 |
|
12 |
setup do
|
|
|
15 |
|> Path.join("../../medasrdemo-Paul.mp3")
|
16 |
|> Path.expand()
|
17 |
|
18 |
+
transcription = transcription_fixture(%{filename: sample_file})
|
19 |
+
%{transcription: transcription}
|
20 |
end
|
21 |
|
22 |
+
test "transcribe and tag audio", %{transcription: transcription} do
|
23 |
+
spec = {TranscriptionServer, transcription: transcription}
|
24 |
|
25 |
{:ok, pid} = start_supervised(spec)
|
26 |
|
test/medical_transcription_web/live/home_live_test.exs
CHANGED
@@ -2,17 +2,20 @@ defmodule MedicalTranscriptionWeb.HomeLiveTest do
|
|
2 |
use MedicalTranscriptionWeb.ConnCase, async: true
|
3 |
|
4 |
import Phoenix.LiveViewTest
|
5 |
-
|
6 |
import MedicalTranscription.AccountsFixtures
|
|
|
|
|
|
|
|
|
7 |
|
8 |
setup %{conn: conn} do
|
9 |
password = valid_user_password()
|
10 |
user = user_fixture(%{password: password})
|
11 |
-
%{conn: log_in_user(conn, user)}
|
12 |
end
|
13 |
|
14 |
describe "/" do
|
15 |
-
test "renders upload screen", %{conn: conn} do
|
16 |
# 1. Find file input
|
17 |
# 2. Upload file
|
18 |
# 3. Click submit
|
@@ -42,28 +45,20 @@ defmodule MedicalTranscriptionWeb.HomeLiveTest do
|
|
42 |
}
|
43 |
])
|
44 |
|
45 |
-
# assert audio
|
46 |
-
# |> render_upload("sample.mp3") =~ "Submitted: sample.mp3"
|
47 |
-
#
|
48 |
-
# refute view
|
49 |
-
# |> form("#audio-form")
|
50 |
-
# |> render_submit() =~ "Submitted: sample.mp3"
|
51 |
-
|
52 |
render_upload(audio, "sample.mp3")
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
62 |
|
63 |
-
|
64 |
-
{:ok, view, html} = live(conn, "/")
|
65 |
-
html = view |> element("result_list")
|
66 |
-
assert html_response(conn, 200)
|
67 |
end
|
68 |
end
|
69 |
end
|
|
|
2 |
use MedicalTranscriptionWeb.ConnCase, async: true
|
3 |
|
4 |
import Phoenix.LiveViewTest
|
|
|
5 |
import MedicalTranscription.AccountsFixtures
|
6 |
+
import Ecto.Query
|
7 |
+
|
8 |
+
alias MedicalTranscription.Transcriptions.Transcription
|
9 |
+
alias MedicalTranscription.Repo
|
10 |
|
11 |
setup %{conn: conn} do
|
12 |
password = valid_user_password()
|
13 |
user = user_fixture(%{password: password})
|
14 |
+
%{conn: log_in_user(conn, user), current_user: user}
|
15 |
end
|
16 |
|
17 |
describe "/" do
|
18 |
+
test "renders upload screen", %{conn: conn, current_user: current_user} do
|
19 |
# 1. Find file input
|
20 |
# 2. Upload file
|
21 |
# 3. Click submit
|
|
|
45 |
}
|
46 |
])
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
render_upload(audio, "sample.mp3")
|
49 |
|
50 |
+
view
|
51 |
+
|> form("#audio-form")
|
52 |
+
|> render_submit()
|
53 |
|
54 |
+
transcription =
|
55 |
+
Transcription
|
56 |
+
|> where([t], t.user_id == ^current_user.id)
|
57 |
+
|> order_by(desc: :inserted_at)
|
58 |
+
|> limit(1)
|
59 |
+
|> Repo.one()
|
60 |
|
61 |
+
assert_redirected view, ~p"/transcriptions/#{transcription.id}"
|
|
|
|
|
|
|
62 |
end
|
63 |
end
|
64 |
end
|