PhoenixKitWeb.Components.AITranslate.FormGlue (phoenix_kit v1.7.130)

Copy Markdown View Source

Shared LiveView glue for the AI-translate modal — the stateful counterpart to the render-only PhoenixKitWeb.Components.AITranslate components.

Owns the whole modal/progress/stall state machine so consumers (catalogue, projects, …) don't each re-implement it:

  • mount state + per-resource PubSub subscription (assign_ai_translation/4)
  • modal open/close, endpoint/prompt/scope selection, generate-default-prompt
  • scope-driven dispatch (missing "*" / overwrite "**" / single tab) via core PhoenixKit.Modules.AI.Translations
  • live progress + a STALL hint ("taking a while…") driven by a per-arm timer that fires only when no language completes for ai_stall_ms/0
  • folding {:ai_translation, _, _} PubSub events back into the form

The only storage-specific behaviour — which langs already have a translation, how to merge a completed translation into the live changeset, and who the actor is — is supplied by a PhoenixKitWeb.Components.AITranslate.FormBinding module, passed once at mount.

Host wiring (thin)

use Gettext, ...
alias PhoenixKitWeb.Components.AITranslate.FormGlue

# mount/3 (on :edit pass the resource, on :new pass nil)
|> FormGlue.assign_ai_translation("project", project_or_nil, MyApp.AITranslateBinding)

# one config call feeds button + modal + progress + hint
<.ai_translate_button ai_translate={FormGlue.ai_translate_config(assigns)} />

# six thin handle_event clauses delegate to:
#   toggle_ai_modal/1, select_ai_endpoint/2, select_ai_prompt/2,
#   select_ai_scope/2, generate_ai_prompt/1, dispatch_ai_translate/2

# one handle_info:
def handle_info({:ai_translation, event, payload}, socket),
  do: {:noreply, FormGlue.handle_ai_translation_event(socket, event, payload, &assign_form/2)}

assign_form/2 (the 4th arg to handle_ai_translation_event/4) is the LV's own (socket, changeset) -> socket helper — the glue uses it to re-assign the patched changeset so the LV's exact form-sync behaviour is preserved.

Summary

Functions

Build the ai_translate config map for the AITranslate components from the form's assigns, or nil when AI translation isn't available. The missing list comes from the binding (live changeset), so it reflects unsaved + just-translated state.

Assign the AI-translate mount state and (on the connected mount of an existing resource) subscribe to its core translation topic.

Dispatch a translation from the modal's "Translate" action. lang is the scope sentinel: "*" (missing only), "**" (all non-primary, overwrite), or a concrete language code (current tab). Closes the modal, grows :ai_in_flight, advances the progress session, and flashes the outcome.

Provision the shared default translation prompt and select it.

Fold a {:ai_translation, event, payload} message into the form socket.

Endpoint dropdown change.

Prompt dropdown change.

Scope radio change (missing | all | current).

Modal open/close toggle.

Functions

ai_translate_config(assigns)

@spec ai_translate_config(map()) :: map() | nil

Build the ai_translate config map for the AITranslate components from the form's assigns, or nil when AI translation isn't available. The missing list comes from the binding (live changeset), so it reflects unsaved + just-translated state.

assign_ai_translation(socket, resource_type, resource, binding)

@spec assign_ai_translation(
  Phoenix.LiveView.Socket.t(),
  String.t(),
  struct() | nil,
  module()
) :: Phoenix.LiveView.Socket.t()

Assign the AI-translate mount state and (on the connected mount of an existing resource) subscribe to its core translation topic.

Pass the resource struct on :edit; pass nil on :new. resource_type is the key registered via ai_translatables/0; binding is the consumer's FormBinding module.

dispatch_ai_translate(socket, lang)

@spec dispatch_ai_translate(Phoenix.LiveView.Socket.t(), String.t()) ::
  Phoenix.LiveView.Socket.t()

Dispatch a translation from the modal's "Translate" action. lang is the scope sentinel: "*" (missing only), "**" (all non-primary, overwrite), or a concrete language code (current tab). Closes the modal, grows :ai_in_flight, advances the progress session, and flashes the outcome.

generate_ai_prompt(socket)

Provision the shared default translation prompt and select it.

handle_ai_translation_event(socket, arg2, payload, assign_cs)

Fold a {:ai_translation, event, payload} message into the form socket.

assign_cs is the LV's own (socket, changeset) -> socket helper — on :translation_completed the binding merges the translated fields into the live changeset and assign_cs re-assigns it (so the result shows without a DB reload and unsaved edits survive). Returns the updated socket.

select_ai_endpoint(socket, uuid)

Endpoint dropdown change.

select_ai_prompt(socket, uuid)

Prompt dropdown change.

select_ai_scope(socket, scope)

Scope radio change (missing | all | current).

toggle_ai_modal(socket)

Modal open/close toggle.