From 15901e7cd9617a3573c8040369fc037d7ec29aee Mon Sep 17 00:00:00 2001 From: Alex Lion Date: Tue, 4 Aug 2026 14:10:12 +0700 Subject: [PATCH] Refactor code --- .../transcriptions/mistral_realtime_client.ex | 95 +++++----- .../transcriptions/transcription_worker.ex | 8 +- lib/claper_web/live/admin_live/event_live.ex | 4 +- lib/claper_web/live/event_live/manage.ex | 168 +++++++++--------- .../mistral_realtime_client_test.exs | 2 +- 5 files changed, 146 insertions(+), 131 deletions(-) diff --git a/lib/claper/transcriptions/mistral_realtime_client.ex b/lib/claper/transcriptions/mistral_realtime_client.ex index 43fe141..1b46bdd 100644 --- a/lib/claper/transcriptions/mistral_realtime_client.ex +++ b/lib/claper/transcriptions/mistral_realtime_client.ex @@ -60,7 +60,9 @@ defmodule Claper.Transcriptions.MistralRealtimeClient do @impl true def handle_info(:send_session_update, state) do - session_config = %{"audio_format" => %{"encoding" => "pcm_s16le", "sample_rate" => 16000}} + session_config = %{ + "audio_format" => %{"encoding" => "pcm_s16le", "sample_rate" => 16_000} + } message = Jason.encode!(%{"type" => "session.update", "session" => session_config}) {:reply, {:text, message}, state} @@ -74,47 +76,8 @@ defmodule Claper.Transcriptions.MistralRealtimeClient do @impl true def handle_frame({:text, msg}, state) do case Jason.decode(msg) do - {:ok, %{"type" => "session.created"} = event} -> - Logger.info("MistralRealtimeClient: session created") - send(state.callback_pid, {:mistral_event, :session_created, event}) - {:ok, %{state | session_ready: true}} - - {:ok, %{"type" => "session.updated"}} -> - Logger.info("MistralRealtimeClient: session updated, ready for audio") - {:ok, state} - - {:ok, %{"type" => "transcription.text.delta", "text" => text}} -> - send(state.callback_pid, {:mistral_event, :text_delta, text}) - {:ok, state} - - {:ok, %{"type" => "transcription.segment", "text" => text}} -> - send(state.callback_pid, {:mistral_event, :segment, text}) - {:ok, state} - - {:ok, %{"type" => "transcription.done"} = event} -> - text = Map.get(event, "text", "") - send(state.callback_pid, {:mistral_event, :done, text}) - {:ok, state} - - {:ok, %{"type" => "transcription.language"} = event} -> - lang = Map.get(event, "audioLanguage") || Map.get(event, "language") - - Logger.info("MistralRealtimeClient: detected language #{lang}") - send(state.callback_pid, {:mistral_event, :language, lang}) - {:ok, state} - - {:ok, %{"type" => "error"} = event} -> - error_msg = get_in(event, ["error", "message"]) || "unknown error" - Logger.error("MistralRealtimeClient: error - #{error_msg}") - send(state.callback_pid, {:mistral_event, :error, error_msg}) - {:ok, state} - {:ok, event} -> - Logger.info( - "MistralRealtimeClient: unhandled event type #{inspect(event["type"])} payload=#{inspect(event)}" - ) - - {:ok, state} + handle_event(event, state) {:error, reason} -> Logger.error("MistralRealtimeClient: failed to parse: #{inspect(reason)}") @@ -134,6 +97,56 @@ defmodule Claper.Transcriptions.MistralRealtimeClient do {:reconnect, state} end + defp handle_event(%{"type" => "session.created"} = event, state) do + Logger.info("MistralRealtimeClient: session created") + send(state.callback_pid, {:mistral_event, :session_created, event}) + {:ok, %{state | session_ready: true}} + end + + defp handle_event(%{"type" => "session.updated"}, state) do + Logger.info("MistralRealtimeClient: session updated, ready for audio") + {:ok, state} + end + + defp handle_event(%{"type" => "transcription.text.delta", "text" => text}, state) do + send(state.callback_pid, {:mistral_event, :text_delta, text}) + {:ok, state} + end + + defp handle_event(%{"type" => "transcription.segment", "text" => text}, state) do + send(state.callback_pid, {:mistral_event, :segment, text}) + {:ok, state} + end + + defp handle_event(%{"type" => "transcription.done"} = event, state) do + text = Map.get(event, "text", "") + send(state.callback_pid, {:mistral_event, :done, text}) + {:ok, state} + end + + defp handle_event(%{"type" => "transcription.language"} = event, state) do + lang = Map.get(event, "audioLanguage") || Map.get(event, "language") + + Logger.info("MistralRealtimeClient: detected language #{lang}") + send(state.callback_pid, {:mistral_event, :language, lang}) + {:ok, state} + end + + defp handle_event(%{"type" => "error"} = event, state) do + error_msg = get_in(event, ["error", "message"]) || "unknown error" + Logger.error("MistralRealtimeClient: error - #{error_msg}") + send(state.callback_pid, {:mistral_event, :error, error_msg}) + {:ok, state} + end + + defp handle_event(event, state) do + Logger.info( + "MistralRealtimeClient: unhandled event type #{inspect(event["type"])} payload=#{inspect(event)}" + ) + + {:ok, state} + end + defp get_api_key do Claper.Settings.get_transcription_api_key() end diff --git a/lib/claper/transcriptions/transcription_worker.ex b/lib/claper/transcriptions/transcription_worker.ex index 8169478..9b600ad 100644 --- a/lib/claper/transcriptions/transcription_worker.ex +++ b/lib/claper/transcriptions/transcription_worker.ex @@ -40,10 +40,7 @@ defmodule Claper.Transcriptions.TranscriptionWorker do @impl true def init({event_uuid, presentation_file_id}) do - unless Claper.Settings.transcription_globally_enabled?() do - Logger.info("TranscriptionWorker: transcription globally disabled, not starting") - {:stop, :transcription_disabled} - else + if Claper.Settings.transcription_globally_enabled?() do Logger.info("TranscriptionWorker started for event #{event_uuid}") config_language = @@ -72,6 +69,9 @@ defmodule Claper.Transcriptions.TranscriptionWorker do Logger.error("Failed to connect to Mistral realtime API: #{inspect(reason)}") {:stop, reason} end + else + Logger.info("TranscriptionWorker: transcription globally disabled, not starting") + {:stop, :transcription_disabled} end end diff --git a/lib/claper_web/live/admin_live/event_live.ex b/lib/claper_web/live/admin_live/event_live.ex index b3d6647..63d53ae 100644 --- a/lib/claper_web/live/admin_live/event_live.ex +++ b/lib/claper_web/live/admin_live/event_live.ex @@ -106,11 +106,9 @@ defmodule ClaperWeb.AdminLive.EventLive do transcriptions = socket.assigns.transcriptions content = - transcriptions - |> Enum.map(fn t -> + Enum.map_join(transcriptions, "\n", fn t -> "[#{Calendar.strftime(t.inserted_at, "%Y-%m-%d %H:%M:%S")}] #{t.text}" end) - |> Enum.join("\n") {:noreply, push_event(socket, "download_csv", %{ diff --git a/lib/claper_web/live/event_live/manage.ex b/lib/claper_web/live/event_live/manage.ex index ea47e96..a9d872e 100644 --- a/lib/claper_web/live/event_live/manage.ex +++ b/lib/claper_web/live/event_live/manage.ex @@ -26,91 +26,95 @@ defmodule ClaperWeb.EventLive.Manage do |> put_flash(:error, gettext("Event doesn't exist")) |> redirect(to: "/")} else - if connected?(socket) do - Claper.Events.Event.subscribe(event.uuid) - Presentations.subscribe(event.presentation_file.id) - Events.subscribe_user_events(socket.assigns.current_user.id) - end - - posts = list_all_posts(socket, event.uuid) - pinned_posts = list_pinned_posts(socket, event.uuid) - questions = list_all_questions(socket, event.uuid) - form_submits = list_form_submits(socket, event.presentation_file.id) - - audio_token = - Phoenix.Token.sign(ClaperWeb.Endpoint, "audio_token", %{ - user_id: socket.assigns.current_user.id, - event_uuid: event.uuid - }) - - transcription_config = - Transcriptions.get_transcription_config(event.presentation_file.id) - - transcription_globally_enabled = Claper.Settings.transcription_globally_enabled?() - - # Auto-start transcription worker if config says enabled and globally enabled - if connected?(socket) && transcription_globally_enabled && transcription_config && - transcription_config.enabled do - unless Claper.Transcriptions.TranscriptionWorker.running?(event.uuid) do - DynamicSupervisor.start_child( - Claper.TranscriptionSupervisor, - {Claper.Transcriptions.TranscriptionWorker, {event.uuid, event.presentation_file.id}} - ) - end - end - - socket = - socket - |> assign(:interaction_modal, false) - |> assign(:settings_modal, false) - |> assign(:attendees_nb, 0) - |> assign(:event, event) - |> assign(:sort_questions_by, "date") - |> assign(:state, event.presentation_file.presentation_state) - |> assign(:audio_token, audio_token) - |> assign(:transcription_config, transcription_config) - |> assign(:transcription_globally_enabled, transcription_globally_enabled) - |> stream(:posts, posts) - |> stream(:questions, questions) - |> stream(:pinned_posts, pinned_posts) - |> stream(:form_submits, form_submits) - |> assign(:pinned_post_count, length(pinned_posts)) - |> assign(:question_count, length(questions)) - |> assign(:post_count, length(posts)) - |> assign( - :total_interactions, - Claper.Interactions.get_number_total_interactions(event.presentation_file.id) - ) - |> assign( - :form_submit_count, - length(form_submits) - ) - |> assign(:create, nil) - |> assign(:list_tab, :posts) - |> assign(:create_action, :new) - |> assign( - :missing_slide_thumbnails, - Presentations.missing_slide_thumbnails?(event.presentation_file) - ) - |> assign(:thumbnail_cache_bust, thumbnail_cache_bust()) - |> assign(:thumbnail_regeneration_in_progress, false) - |> push_event("page-manage", %{ - current_page: event.presentation_file.presentation_state.position, - timeout: 500 - }) - |> then(fn s -> - if transcription_config && transcription_config.enabled do - push_event(s, "transcription-state", %{enabled: true}) - else - s - end - end) - |> interactions_at_position(event.presentation_file.presentation_state.position) - - {:ok, socket} + mount_event(socket, event) end end + defp mount_event(socket, event) do + if connected?(socket) do + Claper.Events.Event.subscribe(event.uuid) + Presentations.subscribe(event.presentation_file.id) + Events.subscribe_user_events(socket.assigns.current_user.id) + end + + posts = list_all_posts(socket, event.uuid) + pinned_posts = list_pinned_posts(socket, event.uuid) + questions = list_all_questions(socket, event.uuid) + form_submits = list_form_submits(socket, event.presentation_file.id) + + audio_token = + Phoenix.Token.sign(ClaperWeb.Endpoint, "audio_token", %{ + user_id: socket.assigns.current_user.id, + event_uuid: event.uuid + }) + + transcription_config = + Transcriptions.get_transcription_config(event.presentation_file.id) + + transcription_globally_enabled = Claper.Settings.transcription_globally_enabled?() + + # Auto-start transcription worker if config says enabled and globally enabled + if connected?(socket) && transcription_globally_enabled && transcription_config && + transcription_config.enabled do + unless Claper.Transcriptions.TranscriptionWorker.running?(event.uuid) do + DynamicSupervisor.start_child( + Claper.TranscriptionSupervisor, + {Claper.Transcriptions.TranscriptionWorker, {event.uuid, event.presentation_file.id}} + ) + end + end + + socket = + socket + |> assign(:interaction_modal, false) + |> assign(:settings_modal, false) + |> assign(:attendees_nb, 0) + |> assign(:event, event) + |> assign(:sort_questions_by, "date") + |> assign(:state, event.presentation_file.presentation_state) + |> assign(:audio_token, audio_token) + |> assign(:transcription_config, transcription_config) + |> assign(:transcription_globally_enabled, transcription_globally_enabled) + |> stream(:posts, posts) + |> stream(:questions, questions) + |> stream(:pinned_posts, pinned_posts) + |> stream(:form_submits, form_submits) + |> assign(:pinned_post_count, length(pinned_posts)) + |> assign(:question_count, length(questions)) + |> assign(:post_count, length(posts)) + |> assign( + :total_interactions, + Claper.Interactions.get_number_total_interactions(event.presentation_file.id) + ) + |> assign( + :form_submit_count, + length(form_submits) + ) + |> assign(:create, nil) + |> assign(:list_tab, :posts) + |> assign(:create_action, :new) + |> assign( + :missing_slide_thumbnails, + Presentations.missing_slide_thumbnails?(event.presentation_file) + ) + |> assign(:thumbnail_cache_bust, thumbnail_cache_bust()) + |> assign(:thumbnail_regeneration_in_progress, false) + |> push_event("page-manage", %{ + current_page: event.presentation_file.presentation_state.position, + timeout: 500 + }) + |> then(fn s -> + if transcription_config && transcription_config.enabled do + push_event(s, "transcription-state", %{enabled: true}) + else + s + end + end) + |> interactions_at_position(event.presentation_file.presentation_state.position) + + {:ok, socket} + end + defp leader?(%{assigns: %{current_user: current_user}} = _socket, event) do Claper.Events.led_by?(current_user.email, event) || event.user.id == current_user.id end diff --git a/test/claper/transcriptions/mistral_realtime_client_test.exs b/test/claper/transcriptions/mistral_realtime_client_test.exs index a8246a7..808d820 100644 --- a/test/claper/transcriptions/mistral_realtime_client_test.exs +++ b/test/claper/transcriptions/mistral_realtime_client_test.exs @@ -12,7 +12,7 @@ defmodule Claper.Transcriptions.MistralRealtimeClientTest do assert %{ "type" => "session.update", "session" => %{ - "audio_format" => %{"encoding" => "pcm_s16le", "sample_rate" => 16000} + "audio_format" => %{"encoding" => "pcm_s16le", "sample_rate" => 16_000} } } = Jason.decode!(message)