diff --git a/lib/bds/desktop/shell_commands.ex b/lib/bds/desktop/shell_commands.ex index 9718cc0..80bf887 100644 --- a/lib/bds/desktop/shell_commands.ex +++ b/lib/bds/desktop/shell_commands.ex @@ -262,6 +262,30 @@ defmodule BDS.Desktop.ShellCommands do end) end + defp dispatch("apply_site_validation", project, params) do + report = normalize_apply_validation_report(BDS.MapUtils.attr(params, :report, %{})) + + queue_task( + project, + "apply_site_validation", + "Apply Site Validation", + "Generation", + fn report_fn -> + {:ok, _apply} = + Generation.apply_validation(project.id, report, + on_progress: scaled_progress_reporter(report_fn, 0.0, 0.85) + ) + + {:ok, validation} = + Generation.validate_site(project.id, @site_sections, + on_progress: scaled_progress_reporter(report_fn, 0.85, 1.0) + ) + + site_validation_result(project.id, validation) + end + ) + end + defp dispatch("metadata_diff", project, _params) do queue_task(project, "metadata_diff", "Metadata Diff", "Maintenance", fn report -> {:ok, metadata_diff} = Maintenance.metadata_diff(project.id, on_progress: report) @@ -640,6 +664,20 @@ defmodule BDS.Desktop.ShellCommands do "http://#{server.host}:#{server.port}/" end + defp normalize_apply_validation_report(report) when is_map(report) do + %{ + sitemap_path: BDS.MapUtils.attr(report, :sitemap_path), + sitemap_changed: BDS.MapUtils.attr(report, :sitemap_changed, false), + missing_url_paths: BDS.MapUtils.attr(report, :missing_url_paths, []), + extra_url_paths: BDS.MapUtils.attr(report, :extra_url_paths, []), + updated_post_url_paths: BDS.MapUtils.attr(report, :updated_post_url_paths, []), + expected_url_count: BDS.MapUtils.attr(report, :expected_url_count, 0), + existing_html_url_count: BDS.MapUtils.attr(report, :existing_html_url_count, 0) + } + end + + defp normalize_apply_validation_report(_report), do: %{} + defp normalize_site_validation(report) do %{ sitemap_path: report.sitemap_path, diff --git a/lib/bds/desktop/shell_live/bridges.ex b/lib/bds/desktop/shell_live/bridges.ex index f307b06..0d309f2 100644 --- a/lib/bds/desktop/shell_live/bridges.ex +++ b/lib/bds/desktop/shell_live/bridges.ex @@ -5,7 +5,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do import Phoenix.LiveView, only: [connected?: 1, send_update: 2] alias BDS.Desktop.ShellData - alias BDS.Desktop.ShellLive.{ChatEditor, PostEditor} + alias BDS.Desktop.ShellLive.{ChatEditor, MediaEditor, PostEditor} alias BDS.Desktop.ShellLive.{CliSync, SessionUtil} alias BDS.UI.Workbench @@ -216,6 +216,26 @@ defmodule BDS.Desktop.ShellLive.Bridges do {:noreply, socket} end + def handle_info({:editor_translation_completed, :post, post_id, language}, socket, _callbacks) do + send_update(PostEditor, + id: "post-editor-#{post_id}", + action: :translation_completed, + language: language + ) + + {:noreply, socket} + end + + def handle_info({:editor_translation_completed, :media, media_id, language}, socket, _callbacks) do + send_update(MediaEditor, + id: "media-editor-#{media_id}", + action: :translation_completed, + language: language + ) + + {:noreply, socket} + end + def handle_info({:post_editor_apply_ai_suggestions, post_id, fields}, socket, _callbacks) do send_update(PostEditor, id: "post-editor-#{post_id}", diff --git a/lib/bds/desktop/shell_live/media_editor.ex b/lib/bds/desktop/shell_live/media_editor.ex index 2155fa9..f00dc18 100644 --- a/lib/bds/desktop/shell_live/media_editor.ex +++ b/lib/bds/desktop/shell_live/media_editor.ex @@ -7,7 +7,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do alias BDS.Desktop.{FilePicker} alias BDS.Desktop.ShellLive.Notify - alias BDS.{AI, I18n, Media} + alias BDS.{AI, I18n, Media, Tasks} alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Translation alias BDS.Posts.Post @@ -57,6 +57,17 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do {:ok, socket} end + def update(%{action: :translation_completed} = assigns, socket) do + socket = + socket + |> assign(Map.drop(assigns, [:action, :language])) + |> assign(:quick_actions_open?, false) + |> assign(:editing_translation, nil) + |> build_data() + + {:ok, socket} + end + def update(assigns, socket) do socket = socket @@ -552,25 +563,34 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do media = socket.assigns.media normalized_language = normalize_language(language) source_language = normalize_language(media.language) + parent = self() - case AI.translate_media(media.id, normalized_language, source_language: source_language) do - {:ok, translation} -> - case Media.upsert_media_translation(media.id, normalized_language, translation) do - {:ok, _saved_translation} -> - socket - |> assign(:quick_actions_open?, false) - |> assign(:editing_translation, nil) - |> build_data() + {:ok, _task} = + Tasks.submit_task( + "Translate Media to #{normalized_language}", + fn report -> + report.(0.1, "Translating media to #{normalized_language}") - {:error, reason} -> - notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error") - |> build_data() - end + with {:ok, translation} <- + AI.translate_media(media.id, normalized_language, + source_language: source_language + ), + {:ok, _saved_translation} <- + Media.upsert_media_translation(media.id, normalized_language, translation) do + report.(1.0, "Media translation complete") + send(parent, {:editor_translation_completed, :media, media.id, normalized_language}) + %{media_id: media.id, language: normalized_language} + else + {:error, reason} -> {:error, reason} + end + end, + %{group_id: media.project_id, group_name: "AI"} + ) - {:error, reason} -> - notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error") - |> build_data() - end + socket + |> assign(:quick_actions_open?, false) + |> assign(:editing_translation, nil) + |> build_data() end end diff --git a/lib/bds/desktop/shell_live/misc_editor.ex b/lib/bds/desktop/shell_live/misc_editor.ex index 5b8eaf2..a079ccf 100644 --- a/lib/bds/desktop/shell_live/misc_editor.ex +++ b/lib/bds/desktop/shell_live/misc_editor.ex @@ -6,7 +6,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do import Phoenix.HTML, only: [raw: 1] import Ecto.Query - alias BDS.{Embeddings, Generation, Git, HelpDocs, Posts, Repo} + alias BDS.{Embeddings, Git, HelpDocs, Posts, Repo} alias BDS.Desktop.ShellLive.Notify alias BDS.MapUtils alias BDS.Settings.Setting @@ -77,7 +77,6 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do def handle_event("apply_site_validation", _params, socket) do meta = meta(socket.assigns) payload = Map.get(meta, :payload, %{}) - project_id = Map.get(meta, :project_id, socket.assigns.project_id) report = %{ sitemap_path: Map.get(payload, :sitemap_path), @@ -89,21 +88,8 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do existing_html_url_count: Map.get(payload, :existing_html_url_count, 0) } - case Generation.apply_validation(project_id, report) do - {:ok, result} -> - notify_output( - dgettext("ui", "Site Validation"), - dgettext("ui", "Validation changes applied"), - inspect(result) - ) - - notify_command("validate_site") - {:noreply, socket} - end - rescue - error -> - notify_output(dgettext("ui", "Site Validation"), inspect(error), nil, "error") - {:noreply, socket} + notify_command("apply_site_validation", %{report: report}) + {:noreply, socket} end def handle_event("fix_translation_validation", _params, socket) do diff --git a/lib/bds/desktop/shell_live/post_editor.ex b/lib/bds/desktop/shell_live/post_editor.ex index 234089c..fd153bf 100644 --- a/lib/bds/desktop/shell_live/post_editor.ex +++ b/lib/bds/desktop/shell_live/post_editor.ex @@ -3,7 +3,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do use Phoenix.LiveComponent - alias BDS.{AI, Embeddings, Metadata, Posts, Preview} + alias BDS.{AI, Embeddings, Metadata, Posts, Preview, Tasks} alias BDS.Desktop.ShellData alias BDS.Desktop.ShellLive.{EditorImageDrop, Notify} alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata} @@ -131,6 +131,15 @@ defmodule BDS.Desktop.ShellLive.PostEditor do {:ok, socket} end + def update(%{action: :translation_completed, language: language} = assigns, socket) do + socket = + socket + |> assign(Map.drop(assigns, [:action, :language])) + |> apply_translation_completed(language) + + {:ok, socket} + end + def update(%{action: :apply_ai_suggestions, fields: fields} = assigns, socket) do socket = socket @@ -808,34 +817,56 @@ defmodule BDS.Desktop.ShellLive.PostEditor do post_id = socket.assigns.post_id normalized_language = normalize_language(language, "") source_language = socket.assigns.canonical_language + project_id = socket.assigns.post.project_id + parent = self() - case AI.translate_post(post_id, normalized_language, source_language: source_language) do - {:ok, translation} -> - with {:ok, _saved_translation} <- - Posts.upsert_post_translation(post_id, normalized_language, %{ - title: translation.title, - excerpt: translation.excerpt, - content: translation.content - }) do - socket = - socket - |> assign(:active_language, normalized_language) - |> assign(:drafts, Map.delete(socket.assigns.drafts, normalized_language)) - |> assign(:quick_actions_open?, false) - |> build_data() + {:ok, _task} = + Tasks.submit_task( + "Translate Post to #{normalized_language}", + fn report -> + report.(0.1, "Translating post to #{normalized_language}") - Notify.dirty(:post, post_id, false) - socket - else - {:error, reason} -> - notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error") - |> build_data() - end + with {:ok, translation} <- + AI.translate_post(post_id, normalized_language, + source_language: source_language + ), + {:ok, _saved_translation} <- + Posts.upsert_post_translation(post_id, normalized_language, %{ + title: translation.title, + excerpt: translation.excerpt, + content: translation.content + }) do + report.(1.0, "Post translation complete") + send(parent, {:editor_translation_completed, :post, post_id, normalized_language}) + %{post_id: post_id, language: normalized_language} + else + {:error, reason} -> {:error, reason} + end + end, + %{group_id: project_id, group_name: "AI"} + ) - {:error, reason} -> - notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error") + socket + |> assign(:quick_actions_open?, false) + |> build_data() + end + end + + defp apply_translation_completed(socket, language) do + case Posts.get_post(socket.assigns.post_id) do + nil -> + build_data(socket) + + %Post{} = post -> + socket = + socket + |> assign(:post, post) + |> assign(:active_language, language) + |> assign(:drafts, Map.delete(socket.assigns.drafts, language)) |> build_data() - end + + Notify.dirty(:post, post.id, false) + socket end end diff --git a/lib/bds/generation.ex b/lib/bds/generation.ex index 1b006a1..fb8a6c1 100644 --- a/lib/bds/generation.ex +++ b/lib/bds/generation.ex @@ -224,6 +224,14 @@ defmodule BDS.Generation do end def apply_validation(project_id, report) when is_binary(project_id) and is_map(report) do + apply_validation(project_id, report, []) + end + + @spec apply_validation(String.t(), map(), generation_opts()) :: {:ok, map()} | {:error, term()} + def apply_validation(project_id, report, opts) + when is_binary(project_id) and is_map(report) and is_list(opts) do + on_progress = callback(opts) + with {:ok, plan} <- plan_generation(project_id, @core_sections) do expected_outputs = build_outputs(plan) expected_output_map = Map.new(expected_outputs) @@ -247,11 +255,19 @@ defmodule BDS.Generation do ) end) - Enum.each(outputs_to_render, fn {relative_path, content} -> + total_to_render = length(outputs_to_render) + :ok = report_generation_started(on_progress, total_to_render, "validation routes") + + outputs_to_render + |> Enum.with_index(1) + |> Enum.each(fn {{relative_path, content}, index} -> _ = write_generated_file(project_id, relative_path, content, refresh_timestamp_on_unchanged: route_html_path?(relative_path) ) + + :ok = + report_generation_progress(on_progress, index, total_to_render, "validation routes") end) {deleted_url_count, removed_empty_dir_count} =