Shared AI-translation UI for multilang form LiveViews — the
publishing/projects-style trigger button + modal, on top of core's
PhoenixKit.Modules.AI.Translations pipeline.
Render-only function components driven by a single ai_translate config
map the host LV builds each render; the host owns the state + event
handlers (see PhoenixKit.Modules.AI.Translations for the backend and
any consumer's form LV for the wiring shape). Three surfaces:
<.ai_translate_button>— compact "AI Translate" trigger above the multilang tabs; toggles the modal. Spinner while a job is in flight.<.ai_translate_modal>— daisyUI dialog: endpoint + prompt selectors, a "Generate Default Prompt" button when none exists, a scope picker (missing-only / all-overwrite / current tab), in-flight status, and one scope-driven "Translate" action.<.ai_translate_progress>— slim inline progress bar for the session.
Host contract
Pass ai_translate: %{...} (string OR atom keys accepted):
%{
enabled: true, # gates render
event: "translate_lang", # dispatch (phx-value-lang)
toggle_event: "toggle_ai", # open/close modal
select_endpoint_event: "...", # endpoint dropdown change
select_prompt_event: "...", # prompt dropdown change
select_scope_event: "...", # scope radio change
generate_prompt_event: "...", # generate-default-prompt
missing: ["es", "de"], # langs lacking a translation
all_langs: ["es", "de", "fr"], # every non-primary enabled lang
in_flight: ["es"], # jobs running now
modal_open: false,
endpoints: [{uuid, name}, ...],
prompts: [{uuid, name}, ...],
selected_endpoint_uuid: "...",
selected_prompt_uuid: "...",
scope: :missing, # :missing | :all | :current
default_prompt_exists: true,
current_lang: "es",
primary_lang: "en",
primary_lang_name: "English",
# progress bar (optional):
translation_status: :in_progress, # nil | :in_progress | :completed
translation_progress: 1,
translation_total: 3
}Action contract
The modal's single "Translate" button sends event with a lang value
driven by scope:
:missing→phx-value-lang="*"(bulk, missing only):all→phx-value-lang="**"(bulk, overwrite all non-primary):current→phx-value-lang=<current_lang>(single)
Host's handle_event(event, %{"lang" => lang}, socket) branches on "*",
"**", or a concrete code.
Placement
The modal contains its own <form phx-change> selectors — HTML forbids
nested forms, so render <.ai_translate_modal> outside (after) the
host's outer </.form>. The button can live inside the form. Both take
the same config map; build it once and pass to both.
Summary
Functions
Compact trigger button. Hidden when disabled / no toggle event.
Reassurance line shown when a translation is taking a while (slow: true
in the config while jobs are still in flight). A leading attention icon
flags the line, then the visible message, then a trailing info icon whose
tooltip carries the fuller "you can leave, nothing is lost" detail.
Renders nothing otherwise.
Modal dialog: endpoint/prompt selectors + scope picker + action.
Slim inline session progress bar. Renders once a dispatch has started.
Functions
Compact trigger button. Hidden when disabled / no toggle event.
Attributes
ai_translate(:map) - Config map — see moduledoc. Defaults tonil.
Reassurance line shown when a translation is taking a while (slow: true
in the config while jobs are still in flight). A leading attention icon
flags the line, then the visible message, then a trailing info icon whose
tooltip carries the fuller "you can leave, nothing is lost" detail.
Renders nothing otherwise.
Attributes
ai_translate(:map) - Same config map; reads:slow+:in_flight. Defaults tonil.
Modal dialog: endpoint/prompt selectors + scope picker + action.
Attributes
ai_translate(:map) - Config map — see moduledoc. Defaults tonil.
Slim inline session progress bar. Renders once a dispatch has started.
Attributes
ai_translate(:map) (required) - Same config map; reads translation_status/progress/total.wrapper_class(:string) - Defaults to"flex-1 min-w-0".class(:string) - Defaults to"progress h-2 w-full block".