From 43a4610ce7a8184f29692d754b0befc8e85a7c73 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 4 May 2026 06:18:06 +0200 Subject: [PATCH] chore: noise in tests --- config/test.exs | 3 +- lib/bds/ai/chat.ex | 10 ++- lib/bds/ai/chat_message.ex | 4 +- lib/bds/ai/model.ex | 4 +- lib/bds/ai/one_shot.ex | 29 +++++-- lib/bds/ai/openai_compatible_runtime.ex | 2 +- lib/bds/ai/runtime.ex | 3 +- lib/bds/desktop/shell_data.ex | 81 ++++++++++++++----- lib/bds/desktop/shell_live.ex | 71 +++++++++++----- lib/bds/desktop/shell_live/bridges.ex | 70 +++++++++------- lib/bds/desktop/shell_live/chat_editor.ex | 13 ++- .../shell_live/chat_editor/message_build.ex | 3 +- lib/bds/desktop/shell_live/chat_surface.ex | 12 ++- lib/bds/desktop/shell_live/import_editor.ex | 29 +++++-- .../import_editor/progress_tracking.ex | 6 +- .../import_editor/taxonomy_editing.ex | 6 +- lib/bds/desktop/shell_live/media_editor.ex | 29 +++++-- lib/bds/desktop/shell_live/menu_editor.ex | 15 ++-- .../desktop/shell_live/menu_editor/state.ex | 6 +- lib/bds/desktop/shell_live/misc_editor.ex | 28 +++++-- .../desktop/shell_live/overlay_components.ex | 2 - lib/bds/desktop/shell_live/overlay_manager.ex | 33 ++++---- lib/bds/desktop/shell_live/post_editor.ex | 12 ++- lib/bds/desktop/shell_live/script_editor.ex | 11 ++- lib/bds/desktop/shell_live/settings_editor.ex | 29 +++++-- .../shell_live/settings_editor/ai_settings.ex | 47 +++++------ .../desktop/shell_live/sidebar_components.ex | 21 +++-- lib/bds/desktop/shell_live/sidebar_delete.ex | 37 +++++++-- lib/bds/desktop/shell_live/sidebar_events.ex | 4 +- lib/bds/desktop/shell_live/tab_helpers.ex | 18 ++++- lib/bds/desktop/shell_live/tags_editor.ex | 6 +- .../desktop/shell_live/task_localization.ex | 1 - lib/bds/desktop/shell_live/template_editor.ex | 8 +- lib/bds/desktop/shutdown.ex | 4 +- lib/bds/generation/validation.ex | 3 +- lib/bds/media/rebuilder.ex | 4 +- lib/bds/posts/auto_translation.ex | 16 +++- lib/bds/rendering/filters.ex | 3 +- lib/bds/rendering/list_archive.ex | 9 ++- lib/bds/scripts.ex | 5 +- lib/bds/templates.ex | 7 +- lib/bds/ui/registry.ex | 35 ++++++-- test/bds/ai_test.exs | 30 ++++--- test/bds/desktop/import_shell_live_test.exs | 20 +++++ test/bds/desktop/shell_commands_test.exs | 14 +++- test/bds/desktop/shell_live_test.exs | 45 +++++++++-- test/bds/desktop_test.exs | 6 +- test/bds/ui/sidebar_test.exs | 4 +- 48 files changed, 619 insertions(+), 239 deletions(-) diff --git a/config/test.exs b/config/test.exs index 21605f6..a31767d 100644 --- a/config/test.exs +++ b/config/test.exs @@ -3,6 +3,7 @@ import Config config :bds, BDS.Repo, database: Path.expand("../priv/data/bds_test.db", __DIR__), pool: Ecto.Adapters.SQL.Sandbox, - pool_size: 5 + pool_size: 5, + busy_timeout: 15_000 config :logger, level: :warning diff --git a/lib/bds/ai/chat.ex b/lib/bds/ai/chat.ex index 6c7e985..280e8f3 100644 --- a/lib/bds/ai/chat.ex +++ b/lib/bds/ai/chat.ex @@ -69,7 +69,9 @@ defmodule BDS.AI.Chat do {:error, :not_found} %ChatConversation{} = conversation -> - Repo.delete_all(from message in ChatMessage, where: message.conversation_id == ^conversation_id) + Repo.delete_all( + from message in ChatMessage, where: message.conversation_id == ^conversation_id + ) case Repo.delete(conversation) do {:ok, _conversation} -> {:ok, :deleted} @@ -375,7 +377,8 @@ defmodule BDS.AI.Chat do opts, @chat_max_tool_rounds ), - {:ok, reply} <- maybe_generate_chat_title(conversation.id, user_message.content, reply, opts) do + {:ok, reply} <- + maybe_generate_chat_title(conversation.id, user_message.content, reply, opts) do {:ok, reply} end end @@ -425,7 +428,8 @@ defmodule BDS.AI.Chat do with {:ok, endpoint, model, mode} <- Runtime.resolve_target(:chat_title, opts), :ok <- Runtime.validate_target(:chat_title, model, mode), request <- build_chat_title_request(user_content, model), - {:ok, response} <- runtime.generate(Runtime.endpoint_with_model(endpoint, model), request, opts) do + {:ok, response} <- + runtime.generate(Runtime.endpoint_with_model(endpoint, model), request, opts) do title = sanitize_chat_title(Map.get(response, :content)) if title == "" do diff --git a/lib/bds/ai/chat_message.ex b/lib/bds/ai/chat_message.ex index 1f9a3a0..345e7cc 100644 --- a/lib/bds/ai/chat_message.ex +++ b/lib/bds/ai/chat_message.ex @@ -36,7 +36,9 @@ defmodule BDS.AI.ChatMessage do :cache_read_tokens, :cache_write_tokens, :created_at - ], empty_values: [nil]) + ], + empty_values: [nil] + ) |> validate_required([:conversation_id, :role, :created_at]) |> assoc_constraint(:conversation) end diff --git a/lib/bds/ai/model.ex b/lib/bds/ai/model.ex index dad8e02..dd08883 100644 --- a/lib/bds/ai/model.ex +++ b/lib/bds/ai/model.ex @@ -60,7 +60,9 @@ defmodule BDS.AI.Model do :interleaved, :status, :updated_at - ], empty_values: [nil]) + ], + empty_values: [nil] + ) |> validate_required([ :provider, :model_id, diff --git a/lib/bds/ai/one_shot.ex b/lib/bds/ai/one_shot.ex index 584c714..46aa9af 100644 --- a/lib/bds/ai/one_shot.ex +++ b/lib/bds/ai/one_shot.ex @@ -211,8 +211,14 @@ defmodule BDS.AI.OneShot do model: model, max_output_tokens: @default_max_output_tokens, messages: [ - %{"role" => "system", "content" => one_shot_system_prompt(operation, language, source_language)}, - %{"role" => "user", "content" => one_shot_user_content(operation, payload, language, source_language)} + %{ + "role" => "system", + "content" => one_shot_system_prompt(operation, language, source_language) + }, + %{ + "role" => "user", + "content" => one_shot_user_content(operation, payload, language, source_language) + } ] } end @@ -320,7 +326,8 @@ defmodule BDS.AI.OneShot do [ %{ "type" => "text", - "text" => "Analyze this image and return title, alt text, and caption in #{language_name(language)}." + "text" => + "Analyze this image and return title, alt text, and caption in #{language_name(language)}." }, %{"type" => "image_url", "image_url" => %{"url" => media.image_url}} ] @@ -443,7 +450,11 @@ defmodule BDS.AI.OneShot do defp resolve_image_data_url(%{image_url: "file://" <> path, mime_type: mime_type} = media) do with {:ok, binary} <- File.read(path) do data_url = "data:#{mime_type};base64," <> Base.encode64(binary) - Logger.debug("AI analyze_image converted file://#{path} to data URL (#{byte_size(data_url)} chars)") + + Logger.debug( + "AI analyze_image converted file://#{path} to data URL (#{byte_size(data_url)} chars)" + ) + {:ok, %{media | image_url: data_url}} else {:error, reason} -> @@ -452,7 +463,9 @@ defmodule BDS.AI.OneShot do end end - defp resolve_image_data_url(%{file_path: file_path, project_id: project_id, mime_type: mime_type} = media) + defp resolve_image_data_url( + %{file_path: file_path, project_id: project_id, mime_type: mime_type} = media + ) when is_binary(file_path) and is_binary(project_id) do case Projects.get_project(project_id) do nil -> @@ -465,7 +478,11 @@ defmodule BDS.AI.OneShot do case File.read(absolute_path) do {:ok, binary} -> data_url = "data:#{mime_type};base64," <> Base.encode64(binary) - Logger.debug("AI analyze_image converted #{absolute_path} to data URL (#{byte_size(data_url)} chars)") + + Logger.debug( + "AI analyze_image converted #{absolute_path} to data URL (#{byte_size(data_url)} chars)" + ) + {:ok, %{media | image_url: data_url}} {:error, reason} -> diff --git a/lib/bds/ai/openai_compatible_runtime.ex b/lib/bds/ai/openai_compatible_runtime.ex index d84515f..b8308f0 100644 --- a/lib/bds/ai/openai_compatible_runtime.ex +++ b/lib/bds/ai/openai_compatible_runtime.ex @@ -53,7 +53,7 @@ defmodule BDS.AI.OpenAICompatibleRuntime do case result do {:ok, %{json: nil, content: content}} when is_binary(content) -> - Logger.warning( + Logger.debug( "AI OpenAI-compatible response parsed but content is not valid JSON. Content: #{String.slice(content, 0, 500)}" ) diff --git a/lib/bds/ai/runtime.ex b/lib/bds/ai/runtime.ex index 7156367..71ef3c1 100644 --- a/lib/bds/ai/runtime.ex +++ b/lib/bds/ai/runtime.ex @@ -39,7 +39,8 @@ defmodule BDS.AI.Runtime do :ok capabilities.supports_attachment == false -> - {:error, %{kind: :model_capability_missing, capability: :supports_attachment, model: model}} + {:error, + %{kind: :model_capability_missing, capability: :supports_attachment, model: model}} true -> :ok diff --git a/lib/bds/desktop/shell_data.ex b/lib/bds/desktop/shell_data.ex index 6acf9b0..61c0c1b 100644 --- a/lib/bds/desktop/shell_data.ex +++ b/lib/bds/desktop/shell_data.ex @@ -16,17 +16,38 @@ defmodule BDS.Desktop.ShellData do def activity_icon(id) do case to_string(id) do - "posts" -> ~s() - "pages" -> ~s() - "media" -> ~s() - "scripts" -> ~s() - "templates" -> ~s() - "tags" -> ~s() - "chat" -> ~s() - "import" -> ~s() - "git" -> ~s() - "settings" -> ~s() - _other -> activity_icon("posts") + "posts" -> + ~s() + + "pages" -> + ~s() + + "media" -> + ~s() + + "scripts" -> + ~s() + + "templates" -> + ~s() + + "tags" -> + ~s() + + "chat" -> + ~s() + + "import" -> + ~s() + + "git" -> + ~s() + + "settings" -> + ~s() + + _other -> + activity_icon("posts") end end @@ -83,18 +104,28 @@ defmodule BDS.Desktop.ShellData do def assistant_cards do [ - %{label: dgettext("ui", "Offline Gate"), text: dgettext("ui", "Automatic AI actions stay gated by airplane mode.")}, + %{ + label: dgettext("ui", "Offline Gate"), + text: dgettext("ui", "Automatic AI actions stay gated by airplane mode.") + }, %{ label: dgettext("ui", "Filesystem Sync"), - text: dgettext("ui", "Metadata flush, diffing, and rebuild hooks still need editor wiring.") + text: + dgettext("ui", "Metadata flush, diffing, and rebuild hooks still need editor wiring.") }, - %{label: dgettext("ui", "Desktop Runtime"), text: dgettext("ui", "The app window is now served from LiveView state.")} + %{ + label: dgettext("ui", "Desktop Runtime"), + text: dgettext("ui", "The app window is now served from LiveView state.") + } ] end def editor_meta(task_status) do [ - %{label: dgettext("ui", "Status"), value: task_status.running_task_message || dgettext("ui", "Idle")}, + %{ + label: dgettext("ui", "Status"), + value: task_status.running_task_message || dgettext("ui", "Idle") + }, %{label: dgettext("ui", "Mode"), value: dgettext("ui", "Offline")}, %{label: dgettext("ui", "Main Language"), value: ui_language()} ] @@ -120,12 +151,24 @@ defmodule BDS.Desktop.ShellData do def git_badge_count(project_id, opts) when is_binary(project_id) do provider = Keyword.get(opts, :provider, git_remote_state_provider()) + custom_provider? = provider != (&BDS.Git.remote_state/2) try do - case provider.(project_id, []) do - {:ok, %{behind: behind}} when is_integer(behind) and behind > 0 -> behind - {:ok, %{behind: behind}} when is_binary(behind) -> parse_positive_count(behind) - _other -> 0 + has_git = + custom_provider? || + case BDS.Projects.get_project(project_id) do + nil -> false + project -> File.dir?(Path.join(BDS.Projects.project_data_dir(project), ".git")) + end + + if has_git do + case provider.(project_id, []) do + {:ok, %{behind: behind}} when is_integer(behind) and behind > 0 -> behind + {:ok, %{behind: behind}} when is_binary(behind) -> parse_positive_count(behind) + _other -> 0 + end + else + 0 end rescue error in [DBConnection.OwnershipError, Exqlite.Error] -> diff --git a/lib/bds/desktop/shell_live.ex b/lib/bds/desktop/shell_live.ex index b7da0d4..3340a6d 100644 --- a/lib/bds/desktop/shell_live.ex +++ b/lib/bds/desktop/shell_live.ex @@ -60,6 +60,9 @@ defmodule BDS.Desktop.ShellLive do use Gettext, backend: BDS.Gettext @refresh_interval 1_500 + + def refresh_interval, do: @refresh_interval + @output_entry_limit 20 @sidebar_filter_events [ "toggle_sidebar_filters", @@ -126,7 +129,9 @@ defmodule BDS.Desktop.ShellLive do |> MapSet.union(MapSet.new([:open_in_browser, :open_data_folder])) |> MapSet.union(MapSet.new([:preview_post, :rebuild_database, :reindex_text])) |> MapSet.union(MapSet.new([:rebuild_embedding_index, :metadata_diff, :regenerate_calendar])) - |> MapSet.union(MapSet.new([:validate_translations, :fill_missing_translations, :find_duplicates])) + |> MapSet.union( + MapSet.new([:validate_translations, :fill_missing_translations, :find_duplicates]) + ) |> MapSet.union(MapSet.new([:generate_sitemap, :validate_site, :upload_site])) end @@ -138,7 +143,7 @@ defmodule BDS.Desktop.ShellLive do if connected do Phoenix.PubSub.subscribe(BDS.PubSub, Watcher.topic()) - :timer.send_interval(@refresh_interval, :refresh_task_status) + Process.send_after(self(), :refresh_task_status, @refresh_interval) end workbench = Workbench.new() @@ -158,13 +163,13 @@ defmodule BDS.Desktop.ShellLive do |> assign(:titlebar_menu_item_index, nil) |> assign(:tab_meta, %{}) |> assign(:project_menu_open, false) - |> assign(:sidebar_filters_by_view, %{}) - |> assign(:sidebar_filter_panels, %{}) - |> assign(:chat_editor_request_refs, %{}) - |> assign(:shell_overlay, nil) - |> assign(:output_entries, []) - |> reload_shell(workbench) - |> tap(&sync_menu_bar_locale/1)} + |> assign(:sidebar_filters_by_view, %{}) + |> assign(:sidebar_filter_panels, %{}) + |> assign(:chat_editor_request_refs, %{}) + |> assign(:shell_overlay, nil) + |> assign(:output_entries, []) + |> reload_shell(workbench) + |> tap(&sync_menu_bar_locale/1)} end @impl true @@ -262,7 +267,14 @@ defmodule BDS.Desktop.ShellLive do %{"route" => route, "id" => id} = params, socket ) do - {:noreply, SidebarDelete.request_delete(socket, route, id, Map.get(params, "title"), sidebar_delete_callbacks())} + {:noreply, + SidebarDelete.request_delete( + socket, + route, + id, + Map.get(params, "title"), + sidebar_delete_callbacks() + )} end def handle_event("toggle_offline_mode", _params, socket) do @@ -319,7 +331,8 @@ defmodule BDS.Desktop.ShellLive do do: OverlayManager.handle_event("overlay_keydown", params, socket, overlay_callbacks()) def handle_event("overlay_toggle_ai_field", params, socket), - do: OverlayManager.handle_event("overlay_toggle_ai_field", params, socket, overlay_callbacks()) + do: + OverlayManager.handle_event("overlay_toggle_ai_field", params, socket, overlay_callbacks()) def handle_event("overlay_set_search", params, socket), do: OverlayManager.handle_event("overlay_set_search", params, socket, overlay_callbacks()) @@ -334,22 +347,36 @@ defmodule BDS.Desktop.ShellLive do do: OverlayManager.handle_event("overlay_select_result", params, socket, overlay_callbacks()) def handle_event("overlay_insert_external", params, socket), - do: OverlayManager.handle_event("overlay_insert_external", params, socket, overlay_callbacks()) + do: + OverlayManager.handle_event("overlay_insert_external", params, socket, overlay_callbacks()) def handle_event("overlay_select_language", params, socket), - do: OverlayManager.handle_event("overlay_select_language", params, socket, overlay_callbacks()) + do: + OverlayManager.handle_event("overlay_select_language", params, socket, overlay_callbacks()) def handle_event("overlay_confirm", params, socket), do: OverlayManager.handle_event("overlay_confirm", params, socket, overlay_callbacks()) def handle_event("overlay_select_gallery_image", params, socket), - do: OverlayManager.handle_event("overlay_select_gallery_image", params, socket, overlay_callbacks()) + do: + OverlayManager.handle_event( + "overlay_select_gallery_image", + params, + socket, + overlay_callbacks() + ) def handle_event("overlay_close_lightbox", params, socket), do: OverlayManager.handle_event("overlay_close_lightbox", params, socket, overlay_callbacks()) def handle_event("overlay_lightbox_previous", params, socket), - do: OverlayManager.handle_event("overlay_lightbox_previous", params, socket, overlay_callbacks()) + do: + OverlayManager.handle_event( + "overlay_lightbox_previous", + params, + socket, + overlay_callbacks() + ) def handle_event("overlay_lightbox_next", params, socket), do: OverlayManager.handle_event("overlay_lightbox_next", params, socket, overlay_callbacks()) @@ -484,7 +511,8 @@ defmodule BDS.Desktop.ShellLive do next_socket = cond do Map.has_key?(socket.assigns.chat_editor_request_refs, ref) -> - {conversation_id, remaining_refs} = Map.pop(socket.assigns.chat_editor_request_refs, ref) + {conversation_id, remaining_refs} = + Map.pop(socket.assigns.chat_editor_request_refs, ref) if reason == :normal do assign(socket, :chat_editor_request_refs, remaining_refs) @@ -552,7 +580,7 @@ defmodule BDS.Desktop.ShellLive do task_status = localize_task_status(raw_task_status, page_language) socket - |> assign(:tab_meta, tab_meta) + |> assign(:tab_meta, tab_meta) |> assign(:workbench, workbench) |> assign(:projects, projects) |> assign(:current_project, ShellData.current_project(projects)) @@ -737,7 +765,13 @@ defmodule BDS.Desktop.ShellLive do apply_shell_command(socket, Atom.to_string(action)) true -> - append_output_entry(socket, "Menu", "Unsupported shell command", Atom.to_string(action), "error") + append_output_entry( + socket, + "Menu", + "Unsupported shell command", + Atom.to_string(action), + "error" + ) end end @@ -871,5 +905,4 @@ defmodule BDS.Desktop.ShellLive do pid -> send(pid, {:set_ui_locale, locale}) end end - end diff --git a/lib/bds/desktop/shell_live/bridges.ex b/lib/bds/desktop/shell_live/bridges.ex index 5bd3dce..a844d2c 100644 --- a/lib/bds/desktop/shell_live/bridges.ex +++ b/lib/bds/desktop/shell_live/bridges.ex @@ -2,7 +2,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do @moduledoc false import Phoenix.Component, only: [assign: 3] - import Phoenix.LiveView, only: [send_update: 2] + import Phoenix.LiveView, only: [connected?: 1, send_update: 2] alias BDS.Desktop.ShellData alias BDS.Desktop.ShellLive.{ChatEditor, PostEditor} @@ -103,7 +103,8 @@ defmodule BDS.Desktop.ShellLive.Bridges do end def handle_info({:chat_editor_switch_view, view}, socket, callbacks) do - {:noreply, callbacks.reload.(socket, Workbench.click_activity(socket.assigns.workbench, view))} + {:noreply, + callbacks.reload.(socket, Workbench.click_activity(socket.assigns.workbench, view))} end def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do @@ -113,35 +114,40 @@ defmodule BDS.Desktop.ShellLive.Bridges do def handle_info(:refresh_task_status, socket, callbacks) do raw_task_status = BDS.Tasks.status_snapshot() - case SessionUtil.next_completed_task_result(socket, raw_task_status) do - nil -> - task_status = - BDS.Desktop.ShellLive.TaskLocalization.localize_task_status( - raw_task_status, - socket.assigns.page_language + socket = + case SessionUtil.next_completed_task_result(socket, raw_task_status) do + nil -> + task_status = + BDS.Desktop.ShellLive.TaskLocalization.localize_task_status( + raw_task_status, + socket.assigns.page_language + ) + + socket + |> assign(:task_status, task_status) + |> assign(:editor_meta, ShellData.editor_meta(task_status)) + |> assign( + :status, + ShellData.status_bar( + socket.assigns.workbench, + task_status, + socket.assigns.dashboard, + ui_language: socket.assigns.page_language, + offline_mode: socket.assigns.offline_mode + ) ) - {:noreply, - socket - |> assign(:task_status, task_status) - |> assign(:editor_meta, ShellData.editor_meta(task_status)) - |> assign( - :status, - ShellData.status_bar( - socket.assigns.workbench, - task_status, - socket.assigns.dashboard, - ui_language: socket.assigns.page_language, - offline_mode: socket.assigns.offline_mode - ) - )} + task -> + socket + |> SessionUtil.mark_task_result_handled(task.id) + |> callbacks.apply_shell_command_result.(task.result) + end - task -> - {:noreply, - socket - |> SessionUtil.mark_task_result_handled(task.id) - |> callbacks.apply_shell_command_result.(task.result)} + if connected?(socket) do + Process.send_after(self(), :refresh_task_status, BDS.Desktop.ShellLive.refresh_interval()) end + + {:noreply, socket} end def handle_info({:tags_editor_output, title, message, level}, socket, callbacks) do @@ -210,7 +216,12 @@ defmodule BDS.Desktop.ShellLive.Bridges do end def handle_info({:post_editor_insert_content, post_id, content}, socket, _callbacks) do - send_update(PostEditor, id: "post-editor-#{post_id}", action: :insert_content, content: content) + send_update(PostEditor, + id: "post-editor-#{post_id}", + action: :insert_content, + content: content + ) + {:noreply, socket} end @@ -220,7 +231,8 @@ defmodule BDS.Desktop.ShellLive.Bridges do end def handle_info({:post_editor_apply_ai_suggestions, post_id, fields}, socket, _callbacks) do - send_update(PostEditor, id: "post-editor-#{post_id}", + send_update(PostEditor, + id: "post-editor-#{post_id}", action: :apply_ai_suggestions, fields: fields ) diff --git a/lib/bds/desktop/shell_live/chat_editor.ex b/lib/bds/desktop/shell_live/chat_editor.ex index 3862159..3e7bd09 100644 --- a/lib/bds/desktop/shell_live/chat_editor.ex +++ b/lib/bds/desktop/shell_live/chat_editor.ex @@ -61,7 +61,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do def handle_event("toggle_chat_model_selector", _params, socket) do {:noreply, - assign(socket, :model_selector_open?, not socket.assigns.model_selector_open?) |> build_data()} + assign(socket, :model_selector_open?, not socket.assigns.model_selector_open?) + |> build_data()} end def handle_event("select_chat_model", %{"model" => model_id}, socket) do @@ -101,7 +102,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do ) do socket = socket - |> assign(:surface_tabs, Map.put(socket.assigns.surface_tabs, surface_id, parse_integer(index))) + |> assign( + :surface_tabs, + Map.put(socket.assigns.surface_tabs, surface_id, parse_integer(index)) + ) |> build_data() {:noreply, socket} @@ -272,7 +276,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do assign(socket, :request, nil) |> build_data() {:error, reason} -> - notify_parent({:chat_editor_output, dgettext("ui", "Chat"), format_error(reason), "error"}) + notify_parent( + {:chat_editor_output, dgettext("ui", "Chat"), format_error(reason), "error"} + ) + assign(socket, :request, nil) |> build_data() end end diff --git a/lib/bds/desktop/shell_live/chat_editor/message_build.ex b/lib/bds/desktop/shell_live/chat_editor/message_build.ex index c1908ed..458e6bc 100644 --- a/lib/bds/desktop/shell_live/chat_editor/message_build.ex +++ b/lib/bds/desktop/shell_live/chat_editor/message_build.ex @@ -215,7 +215,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do persisted_markers = persisted_tool_markers_for_request(messages, request) {remaining, _persisted_markers} = - Enum.reduce(tool_markers, {[], persisted_markers}, fn marker, {remaining, persisted_markers} -> + Enum.reduce(tool_markers, {[], persisted_markers}, fn marker, + {remaining, persisted_markers} -> case pop_matching_tool_marker(persisted_markers, marker) do {nil, persisted_markers} -> {remaining ++ [marker], persisted_markers} {_matched, persisted_markers} -> {remaining, persisted_markers} diff --git a/lib/bds/desktop/shell_live/chat_surface.ex b/lib/bds/desktop/shell_live/chat_surface.ex index a8977fc..46c8893 100644 --- a/lib/bds/desktop/shell_live/chat_surface.ex +++ b/lib/bds/desktop/shell_live/chat_surface.ex @@ -30,9 +30,17 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do defp assistant_reply(socket) do if socket.assigns.offline_mode do - BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "Automatic AI actions stay gated by airplane mode.") + BDS.Gettext.lgettext( + socket.assigns.page_language, + "ui", + "Automatic AI actions stay gated by airplane mode." + ) else - BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "The assistant sidebar chat surface is ready, but model execution is not connected yet.") + BDS.Gettext.lgettext( + socket.assigns.page_language, + "ui", + "The assistant sidebar chat surface is ready, but model execution is not connected yet." + ) end end end diff --git a/lib/bds/desktop/shell_live/import_editor.ex b/lib/bds/desktop/shell_live/import_editor.ex index 5d48fde..f14f50b 100644 --- a/lib/bds/desktop/shell_live/import_editor.ex +++ b/lib/bds/desktop/shell_live/import_editor.ex @@ -31,6 +31,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do ] use Gettext, backend: BDS.Gettext + import TaxonomyEditing, only: [ existing_taxonomy_terms: 1, @@ -344,7 +345,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do mapped_to <- Map.get(params, "mapped_to"), normalized_value <- TaxonomyEditing.normalize_taxonomy_mapping_value(project_id, type, mapped_to), - updated_report <- TaxonomyEditing.update_taxonomy_mapping(report, type, name, normalized_value), + updated_report <- + TaxonomyEditing.update_taxonomy_mapping(report, type, name, normalized_value), {:ok, _definition} <- ImportDefinitions.update_definition(definition_id, %{ last_analysis_result: updated_report @@ -375,7 +377,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do %{} = report <- ImportDefinitions.decode_analysis_result(definition), normalized_value <- TaxonomyEditing.normalize_taxonomy_mapping_value(project_id, type, ""), - updated_report <- TaxonomyEditing.update_taxonomy_mapping(report, type, name, normalized_value), + updated_report <- + TaxonomyEditing.update_taxonomy_mapping(report, type, name, normalized_value), {:ok, _definition} <- ImportDefinitions.update_definition(definition_id, %{ last_analysis_result: updated_report @@ -409,7 +412,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do def handle_event("toggle_import_ai_model_selector", _params, socket) do {:noreply, - assign(socket, :model_selector_open?, not socket.assigns.model_selector_open?) |> build_data()} + assign(socket, :model_selector_open?, not socket.assigns.model_selector_open?) + |> build_data()} end def handle_event("select_import_ai_model", %{"model" => model_id}, socket) do @@ -432,7 +436,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do if socket.assigns.offline_mode? do notify_output( dgettext("ui", "Import"), - BDS.Gettext.lgettext(socket.assigns[:page_language] || ShellData.ui_language(), "ui", "Automatic AI actions stay gated by airplane mode."), + BDS.Gettext.lgettext( + socket.assigns[:page_language] || ShellData.ui_language(), + "ui", + "Automatic AI actions stay gated by airplane mode." + ), "info" ) @@ -485,7 +493,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do # ── handle_info for async tasks ──────────────────────────────────────────── - @spec handle_info({:import_analysis_progress, atom() | String.t(), String.t()}, Phoenix.LiveView.Socket.t()) :: + @spec handle_info( + {:import_analysis_progress, atom() | String.t(), String.t()}, + Phoenix.LiveView.Socket.t() + ) :: {:noreply, Phoenix.LiveView.Socket.t()} def handle_info({:import_analysis_progress, step, detail}, socket) do socket = @@ -551,7 +562,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do |> assign(:analysis_state, default_analysis_state()) |> notify_output(dgettext("ui", "Import"), message, "error") - match?(%{ref: ^ref}, socket.assigns.execution_state) and reason not in [:normal, :shutdown] -> + match?(%{ref: ^ref}, socket.assigns.execution_state) and + reason not in [:normal, :shutdown] -> message = if is_binary(reason), do: reason, else: inspect(reason) socket @@ -631,7 +643,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do notify_parent( {:import_editor_tab_meta, socket.assigns.definition_id, title, - dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.")} + dgettext( + "ui", + "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported." + )} ) socket diff --git a/lib/bds/desktop/shell_live/import_editor/progress_tracking.ex b/lib/bds/desktop/shell_live/import_editor/progress_tracking.ex index fe0afc2..3c346d0 100644 --- a/lib/bds/desktop/shell_live/import_editor/progress_tracking.ex +++ b/lib/bds/desktop/shell_live/import_editor/progress_tracking.ex @@ -130,7 +130,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do ) |> append_output.( dgettext("ui", "Import"), - dgettext("ui", "Import completed successfully!", count: previous_state.count), + dgettext("ui", "Import completed successfully!", count: previous_state.count), nil, "info" ) @@ -265,9 +265,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do seconds = div(ms, 1000) if seconds < 60 do - dgettext("ui", "ETA: %{value}", - value: dgettext("ui", "%{count}s", count: seconds) - ) + dgettext("ui", "ETA: %{value}", value: dgettext("ui", "%{count}s", count: seconds)) else m = div(seconds, 60) s = rem(seconds, 60) diff --git a/lib/bds/desktop/shell_live/import_editor/taxonomy_editing.ex b/lib/bds/desktop/shell_live/import_editor/taxonomy_editing.ex index 6461140..901e924 100644 --- a/lib/bds/desktop/shell_live/import_editor/taxonomy_editing.ex +++ b/lib/bds/desktop/shell_live/import_editor/taxonomy_editing.ex @@ -86,7 +86,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do socket |> append_output.( dgettext("ui", "Import"), - BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "Automatic AI actions stay gated by airplane mode."), + BDS.Gettext.lgettext( + socket.assigns.page_language, + "ui", + "Automatic AI actions stay gated by airplane mode." + ), nil, "info" ) diff --git a/lib/bds/desktop/shell_live/media_editor.ex b/lib/bds/desktop/shell_live/media_editor.ex index 410dca7..d27d9be 100644 --- a/lib/bds/desktop/shell_live/media_editor.ex +++ b/lib/bds/desktop/shell_live/media_editor.ex @@ -124,7 +124,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do |> build_data() notify_parent({:media_editor_dirty, media.id, false}) - notify_parent({:media_editor_tab_meta, media.id, display_title(updated_media), updated_media.original_name || updated_media.mime_type || ""}) + + notify_parent( + {:media_editor_tab_meta, media.id, display_title(updated_media), + updated_media.original_name || updated_media.mime_type || ""} + ) + {:noreply, socket} {:ok, nil} -> @@ -218,7 +223,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do {:noreply, socket} end - def handle_event("change_media_post_picker", %{"media_post_picker" => %{"query" => query}}, socket) do + def handle_event( + "change_media_post_picker", + %{"media_post_picker" => %{"query" => query}}, + socket + ) do socket = socket |> assign(:post_picker_query, to_string(query || "")) @@ -351,7 +360,13 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do {:noreply, build_data(socket)} {:error, reason} -> - notify_output(socket, dgettext("ui", "Refresh Translation"), inspect(reason), "error") + notify_output( + socket, + dgettext("ui", "Refresh Translation"), + inspect(reason), + "error" + ) + {:noreply, build_data(socket)} end @@ -469,7 +484,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do |> build_data() notify_parent({:media_editor_dirty, media.id, false}) - notify_parent({:media_editor_tab_meta, media.id, display_title(updated_media), updated_media.original_name || updated_media.mime_type || ""}) + + notify_parent( + {:media_editor_tab_meta, media.id, display_title(updated_media), + updated_media.original_name || updated_media.mime_type || ""} + ) + notify_output(socket, dgettext("ui", "Media"), dgettext("ui", "Media saved")) socket @@ -684,7 +704,6 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do end end - @spec media_editor_save_state_label(term()) :: term() def media_editor_save_state_label(:dirty), do: dgettext("ui", "Unsaved") def media_editor_save_state_label(:saved), do: dgettext("ui", "Saved") diff --git a/lib/bds/desktop/shell_live/menu_editor.ex b/lib/bds/desktop/shell_live/menu_editor.ex index e57881c..1d11a8a 100644 --- a/lib/bds/desktop/shell_live/menu_editor.ex +++ b/lib/bds/desktop/shell_live/menu_editor.ex @@ -3,7 +3,6 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do use Phoenix.LiveComponent - use Gettext, backend: BDS.Gettext alias BDS.Desktop.ShellLive.MenuEditor.{ @@ -238,7 +237,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do tab_meta = Map.put(socket.assigns.tab_meta, {:menu_editor, tab_id}, %{ title: dgettext("ui", "Blog Menu"), - subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.") + subtitle: + dgettext( + "ui", + "Manage the central blog navigation outline and save it to meta/menu.opml." + ) }) socket @@ -407,7 +410,6 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do """ end - @spec row_label(term(), term()) :: term() def row_label(item, category_titles) do if item.kind == :category_archive do @@ -430,8 +432,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do def editing_title(_menu_editor), do: dgettext("ui", "Select Page") @spec editing_hint(term()) :: term() - def editing_hint(%{draft: %{type: :category}}), do: dgettext("ui", "Select an existing category or press Enter to create a new archive entry") - def editing_hint(_menu_editor), do: dgettext("ui", "Select a page below or press Enter to create a submenu") + def editing_hint(%{draft: %{type: :category}}), + do: dgettext("ui", "Select an existing category or press Enter to create a new archive entry") + + def editing_hint(_menu_editor), + do: dgettext("ui", "Select a page below or press Enter to create a submenu") @spec editing_placeholder(term()) :: term() def editing_placeholder(%{draft: %{type: :category}}), diff --git a/lib/bds/desktop/shell_live/menu_editor/state.ex b/lib/bds/desktop/shell_live/menu_editor/state.ex index 2c8ba42..b47784c 100644 --- a/lib/bds/desktop/shell_live/menu_editor/state.ex +++ b/lib/bds/desktop/shell_live/menu_editor/state.ex @@ -31,7 +31,11 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do %{ title: dgettext("ui", "Blog Menu Editor"), - description: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml."), + description: + dgettext( + "ui", + "Manage the central blog navigation outline and save it to meta/menu.opml." + ), items: state.items, selected_id: state.selected_id, draft: draft, diff --git a/lib/bds/desktop/shell_live/misc_editor.ex b/lib/bds/desktop/shell_live/misc_editor.ex index 8db2719..6a87f20 100644 --- a/lib/bds/desktop/shell_live/misc_editor.ex +++ b/lib/bds/desktop/shell_live/misc_editor.ex @@ -87,7 +87,12 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do case Generation.apply_validation(project_id, report) do {:ok, result} -> - notify_output(dgettext("ui", "Site Validation"), dgettext("ui", "Validation changes applied"), inspect(result)) + notify_output( + dgettext("ui", "Site Validation"), + dgettext("ui", "Validation changes applied"), + inspect(result) + ) + notify_command("validate_site") {:noreply, socket} end @@ -108,7 +113,9 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do notify_output( dgettext("ui", "Translation Validation"), - dgettext("ui", "Deleted %{dbRows} DB rows and %{files} files, flushed %{flushed} translations to disk", + dgettext( + "ui", + "Deleted %{dbRows} DB rows and %{files} files, flushed %{flushed} translations to disk", dbRows: result.deleted_database_rows, files: result.deleted_files, flushed: result.flushed_translations @@ -193,7 +200,11 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do next_payload = Map.put(payload, :pairs, next_pairs) notify_tab_meta(tab_type, tab_id, %{payload: next_payload}) - notify_output(dgettext("ui", "Find Duplicates"), dgettext("ui", "Selected pairs dismissed")) + + notify_output( + dgettext("ui", "Find Duplicates"), + dgettext("ui", "Selected pairs dismissed") + ) {:noreply, assign(socket, :selected_pairs, MapSet.new()) |> build_data()} @@ -242,13 +253,16 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do end def handle_event("open_duplicate_post", %{"id" => id, "title" => title}, socket) do - notify_open_sidebar_item(%{"route" => "post", "id" => id, "title" => title, "subtitle" => "draft"}, :preview) + notify_open_sidebar_item( + %{"route" => "post", "id" => id, "title" => title, "subtitle" => "draft"}, + :preview + ) + {:noreply, socket} end # ── Public helper functions (used by template) ───────────────────────────── - @spec misc_class(atom()) :: String.t() def misc_class(:site_validation), do: "site-validation-view" def misc_class(:metadata_diff), do: "metadata-diff-view" @@ -430,7 +444,9 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do subtitle: Map.get(meta, :subtitle, ""), summary: %{}, summary_text: - dgettext("ui", "Checked DB rows: %{dbRows} · Checked files: %{files} · Invalid DB rows: %{invalidDb} · Invalid files: %{invalidFiles}", + dgettext( + "ui", + "Checked DB rows: %{dbRows} · Checked files: %{files} · Invalid DB rows: %{invalidDb} · Invalid files: %{invalidFiles}", dbRows: report.checked_database_row_count, files: report.checked_filesystem_file_count, invalidDb: length(report.invalid_database_rows), diff --git a/lib/bds/desktop/shell_live/overlay_components.ex b/lib/bds/desktop/shell_live/overlay_components.ex index cc513b5..95cf689 100644 --- a/lib/bds/desktop/shell_live/overlay_components.ex +++ b/lib/bds/desktop/shell_live/overlay_components.ex @@ -64,8 +64,6 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do def markdown_link(text, url), do: "[#{text}](#{url})" - - def project_metadata(nil), do: %{main_language: "en", blog_languages: []} def project_metadata(project_id) do diff --git a/lib/bds/desktop/shell_live/overlay_manager.ex b/lib/bds/desktop/shell_live/overlay_manager.ex index 0fb534e..757b85e 100644 --- a/lib/bds/desktop/shell_live/overlay_manager.ex +++ b/lib/bds/desktop/shell_live/overlay_manager.ex @@ -148,8 +148,12 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do socket result -> - send(self(), {:post_editor_insert_content, post_id, - ShellOverlayComponents.markdown_link(result.title, result.canonical_url)}) + send( + self(), + {:post_editor_insert_content, post_id, + ShellOverlayComponents.markdown_link(result.title, result.canonical_url)} + ) + socket end @@ -233,13 +237,15 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do socket = case {socket.assigns[:shell_overlay], current_tab} do - {%{kind: :confirm_delete, delete_action: %{source: :sidebar, route: route, id: id}}, - _tab} -> + {%{kind: :confirm_delete, delete_action: %{source: :sidebar, route: route, id: id}}, _tab} -> callbacks.execute_sidebar_delete.(socket, route, id) {%{kind: :ai_suggestions} = overlay, %{type: :post, id: post_id}} -> - send(self(), {:post_editor_apply_ai_suggestions, post_id, - Overlay.selected_ai_fields(overlay)}) + send( + self(), + {:post_editor_apply_ai_suggestions, post_id, Overlay.selected_ai_fields(overlay)} + ) + socket {%{kind: :ai_suggestions} = overlay, %{type: :media, id: media_id}} -> @@ -258,8 +264,10 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do socket |> assign(:shell_overlay, nil) - |> assign(:tab_meta, - Map.delete(socket.assigns.tab_meta, {:media, media_id})) + |> assign( + :tab_meta, + Map.delete(socket.assigns.tab_meta, {:media, media_id}) + ) |> callbacks.reload.(workbench) {:error, reason} -> @@ -331,8 +339,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do } end - assign(socket, :shell_overlay, - Overlay.set_ai_suggestions(overlay, suggestions)) + assign(socket, :shell_overlay, Overlay.set_ai_suggestions(overlay, suggestions)) else socket end @@ -355,13 +362,12 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do if current_tab && current_tab.type == type && current_tab.id == id do message = if is_map(reason) and Map.has_key?(reason, :kind) do - "#{reason.kind}: #{inspect(Map.drop(reason, [:kind]))}" + "#{reason.kind}: #{inspect(Map.drop(reason, [:kind]))}" else inspect(reason) end - assign(socket, :shell_overlay, - Overlay.set_ai_suggestions_error(overlay, message)) + assign(socket, :shell_overlay, Overlay.set_ai_suggestions_error(overlay, message)) else socket end @@ -444,5 +450,4 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do rescue _error -> "en" end - end diff --git a/lib/bds/desktop/shell_live/post_editor.ex b/lib/bds/desktop/shell_live/post_editor.ex index 2e66bad..3ac56c7 100644 --- a/lib/bds/desktop/shell_live/post_editor.ex +++ b/lib/bds/desktop/shell_live/post_editor.ex @@ -47,6 +47,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do ] use Gettext, backend: BDS.Gettext + import PostMetadata, only: [ blank?: 1, @@ -589,7 +590,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do {:ok, %{language_code: language_code}} when is_binary(language_code) and language_code != "" -> socket - |> put_component_draft_field("language", normalize_language(language_code, socket.assigns.canonical_language)) + |> put_component_draft_field( + "language", + normalize_language(language_code, socket.assigns.canonical_language) + ) |> build_data() {:error, reason} -> @@ -685,7 +689,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do socket |> assign(:post, updated_post) |> assign(:project_metadata, metadata) - |> assign(:drafts, Map.put(socket.assigns.drafts, active_language, refreshed_form)) + |> assign( + :drafts, + Map.put(socket.assigns.drafts, active_language, refreshed_form) + ) |> assign(:save_state, :dirty) |> assign(:dirty?, true) |> assign(:shell_overlay, nil) @@ -822,5 +829,4 @@ defmodule BDS.Desktop.ShellLive.PostEditor do @spec post_editor_mode_label(term()) :: term() def post_editor_mode_label(:markdown), do: dgettext("ui", "Markdown") def post_editor_mode_label(:preview), do: dgettext("ui", "Preview") - end diff --git a/lib/bds/desktop/shell_live/script_editor.ex b/lib/bds/desktop/shell_live/script_editor.ex index ce30731..f664134 100644 --- a/lib/bds/desktop/shell_live/script_editor.ex +++ b/lib/bds/desktop/shell_live/script_editor.ex @@ -151,7 +151,10 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do socket |> assign(:draft, nil) |> build_data() - |> notify_output(dgettext("ui", "Scripts"), dgettext("ui", "Script published")) + |> notify_output( + dgettext("ui", "Scripts"), + dgettext("ui", "Script published") + ) |> notify_reload() {:error, reason} -> @@ -273,8 +276,8 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do | Regex.scan(~r/function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/, content || "", capture: :all_but_first ) - |> List.flatten() - |> Enum.reject(&(&1 == "main")) + |> List.flatten() + |> Enum.reject(&(&1 == "main")) ] end @@ -287,4 +290,4 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do send(self(), :reload_shell) socket end -end \ No newline at end of file +end diff --git a/lib/bds/desktop/shell_live/settings_editor.ex b/lib/bds/desktop/shell_live/settings_editor.ex index 0871a90..07315c3 100644 --- a/lib/bds/desktop/shell_live/settings_editor.ex +++ b/lib/bds/desktop/shell_live/settings_editor.ex @@ -118,13 +118,16 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do end def handle_event("save_settings_publishing", _params, socket) do - socket = PublishingSettings.save_publishing(socket, reload_callback(), append_output_callback()) + socket = + PublishingSettings.save_publishing(socket, reload_callback(), append_output_callback()) + notify_parent(:settings_changed) {:noreply, socket} end def handle_event("clear_settings_publishing", _params, socket) do - {:noreply, PublishingSettings.clear_publishing(socket, reload_callback(), append_output_callback())} + {:noreply, + PublishingSettings.clear_publishing(socket, reload_callback(), append_output_callback())} end def handle_event("change_settings_new_category", %{"name" => name}, socket) do @@ -138,25 +141,38 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do end def handle_event("reset_settings_categories", _params, socket) do - socket = ManagedCategories.reset_categories(socket, reload_callback(), append_output_callback()) + socket = + ManagedCategories.reset_categories(socket, reload_callback(), append_output_callback()) + notify_parent(:settings_changed) {:noreply, socket} end def handle_event("save_settings_category", %{"category_settings" => params}, socket) do - socket = ManagedCategories.save_category(socket, params, reload_callback(), append_output_callback()) + socket = + ManagedCategories.save_category(socket, params, reload_callback(), append_output_callback()) + notify_parent(:settings_changed) {:noreply, socket} end def handle_event("remove_settings_category", %{"category" => category}, socket) do - socket = ManagedCategories.remove_category(socket, category, reload_callback(), append_output_callback()) + socket = + ManagedCategories.remove_category( + socket, + category, + reload_callback(), + append_output_callback() + ) + notify_parent(:settings_changed) {:noreply, socket} end def handle_event("toggle_settings_mcp_agent", %{"agent" => agent}, socket) do - socket = MCPConfig.toggle_mcp_agent(socket, agent, reload_callback(), append_output_callback()) + socket = + MCPConfig.toggle_mcp_agent(socket, agent, reload_callback(), append_output_callback()) + notify_parent(:settings_changed) {:noreply, socket} end @@ -385,5 +401,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do defp section_matches?(query, keywords), do: Enum.any?(keywords, &String.contains?(&1, String.downcase(query))) - end diff --git a/lib/bds/desktop/shell_live/settings_editor/ai_settings.ex b/lib/bds/desktop/shell_live/settings_editor/ai_settings.ex index 8e0d535..b842483 100644 --- a/lib/bds/desktop/shell_live/settings_editor/ai_settings.ex +++ b/lib/bds/desktop/shell_live/settings_editor/ai_settings.ex @@ -27,10 +27,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do ), "online_title_model" => get_model_preference(:title), "online_image_analysis_model" => get_model_preference(:image_analysis), - "online_chat_images" => - model_supports_images?( - get_model_preference(:image_analysis) - ), + "online_chat_images" => model_supports_images?(get_model_preference(:image_analysis)), "offline_url" => Map.get(airplane_endpoint || %{}, :url, ""), "offline_api_key" => Map.get(airplane_endpoint || %{}, :api_key, ""), "offline_mode" => Map.get(assigns, :offline_mode, AI.airplane_mode?(true)), @@ -48,9 +45,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do "offline_title_model" => get_model_preference(:airplane_title), "offline_image_analysis_model" => get_model_preference(:airplane_image_analysis), "offline_chat_images" => - model_supports_images?( - get_model_preference(:airplane_image_analysis) - ), + model_supports_images?(get_model_preference(:airplane_image_analysis)), "system_prompt" => EditorSettings.get_global_setting("ai.system_prompt") || "" } end @@ -120,14 +115,14 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do attrs.online_chat_tools, attrs.online_chat_disable_reasoning ), - :ok <- maybe_put_model_preference(:title, attrs.online_title_model), - :ok <- maybe_put_model_preference(:image_analysis, attrs.online_image_analysis_model), - :ok <- - maybe_put_image_model_capabilities( - attrs.online_image_analysis_model, - attrs.online_chat_images - ), - :ok <- maybe_put_model_preference(:airplane_chat, attrs.offline_chat_model), + :ok <- maybe_put_model_preference(:title, attrs.online_title_model), + :ok <- maybe_put_model_preference(:image_analysis, attrs.online_image_analysis_model), + :ok <- + maybe_put_image_model_capabilities( + attrs.online_image_analysis_model, + attrs.online_chat_images + ), + :ok <- maybe_put_model_preference(:airplane_chat, attrs.offline_chat_model), :ok <- maybe_put_chat_model_capabilities( attrs.offline_chat_model, @@ -135,17 +130,17 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do attrs.offline_chat_disable_reasoning ), :ok <- maybe_put_model_preference(:airplane_title, attrs.offline_title_model), - :ok <- - maybe_put_model_preference( - :airplane_image_analysis, - attrs.offline_image_analysis_model - ), - :ok <- - maybe_put_image_model_capabilities( - attrs.offline_image_analysis_model, - attrs.offline_chat_images - ), - :ok <- EditorSettings.put_global_setting("ai.system_prompt", attrs.system_prompt) do + :ok <- + maybe_put_model_preference( + :airplane_image_analysis, + attrs.offline_image_analysis_model + ), + :ok <- + maybe_put_image_model_capabilities( + attrs.offline_image_analysis_model, + attrs.offline_chat_images + ), + :ok <- EditorSettings.put_global_setting("ai.system_prompt", attrs.system_prompt) do socket |> assign(:settings_editor_ai_draft, %{}) |> assign(:offline_mode, attrs.offline_mode) diff --git a/lib/bds/desktop/shell_live/sidebar_components.ex b/lib/bds/desktop/shell_live/sidebar_components.ex index cd3fb3c..aa7e304 100644 --- a/lib/bds/desktop/shell_live/sidebar_components.ex +++ b/lib/bds/desktop/shell_live/sidebar_components.ex @@ -500,8 +500,8 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do """ end - - defp sidebar_deletable?(route), do: route in ["post", "media", "scripts", "templates", "chat", "import"] + defp sidebar_deletable?(route), + do: route in ["post", "media", "scripts", "templates", "chat", "import"] defp sidebar_delete_testid("post"), do: "sidebar-delete-post" defp sidebar_delete_testid("media"), do: "sidebar-delete-media" @@ -513,10 +513,19 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do defp sidebar_delete_title("chat"), do: dgettext("ui", "Delete conversation") defp sidebar_delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post") - defp sidebar_delete_title("media"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media") - defp sidebar_delete_title("scripts"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script") - defp sidebar_delete_title("templates"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template") - defp sidebar_delete_title("import"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import") + + defp sidebar_delete_title("media"), + do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media") + + defp sidebar_delete_title("scripts"), + do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script") + + defp sidebar_delete_title("templates"), + do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template") + + defp sidebar_delete_title("import"), + do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import") + defp sidebar_delete_title(_route), do: dgettext("ui", "Delete") defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates" diff --git a/lib/bds/desktop/shell_live/sidebar_delete.ex b/lib/bds/desktop/shell_live/sidebar_delete.ex index e539876..53861f1 100644 --- a/lib/bds/desktop/shell_live/sidebar_delete.ex +++ b/lib/bds/desktop/shell_live/sidebar_delete.ex @@ -6,7 +6,13 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do alias BDS.{AI, ImportDefinitions, Media, Posts, Scripts, Templates} use Gettext, backend: BDS.Gettext - @spec request_delete(Phoenix.LiveView.Socket.t(), String.t(), String.t(), String.t() | nil, map()) :: + @spec request_delete( + Phoenix.LiveView.Socket.t(), + String.t(), + String.t(), + String.t() | nil, + map() + ) :: Phoenix.LiveView.Socket.t() def request_delete(socket, route, id, fallback_title, callbacks) do case delete_target(socket, route, id, fallback_title) do @@ -43,9 +49,15 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do delete_entity(socket, :scripts, id, &Scripts.delete_script/1, callbacks) "templates" -> - delete_entity(socket, :templates, id, fn tid -> - Templates.delete_template(tid, force: true) - end, callbacks) + delete_entity( + socket, + :templates, + id, + fn tid -> + Templates.delete_template(tid, force: true) + end, + callbacks + ) "chat" -> delete_entity(socket, :chat, id, &AI.delete_chat_conversation/1, callbacks) @@ -56,7 +68,12 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do _other -> socket |> assign(:shell_overlay, nil) - |> callbacks.append_output.(dgettext("ui", "Delete"), inspect(:unsupported_route), nil, "error") + |> callbacks.append_output.( + dgettext("ui", "Delete"), + inspect(:unsupported_route), + nil, + "error" + ) |> callbacks.reload.(socket.assigns.workbench) end end @@ -95,7 +112,9 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do "post" -> case Posts.get_post(id) do %{project_id: ^active_project_id} = post -> - {:ok, present_title(fallback_title) || present_title(post.title) || present_title(post.slug) || id} + {:ok, + present_title(fallback_title) || present_title(post.title) || + present_title(post.slug) || id} _other -> {:error, :not_found} @@ -155,7 +174,10 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do defp delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post") defp delete_title("media"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media") defp delete_title("scripts"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script") - defp delete_title("templates"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template") + + defp delete_title("templates"), + do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template") + defp delete_title("import"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import") defp delete_title(_route), do: dgettext("ui", "Delete") @@ -168,5 +190,4 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do end defp present_title(_value), do: nil - end diff --git a/lib/bds/desktop/shell_live/sidebar_events.ex b/lib/bds/desktop/shell_live/sidebar_events.ex index 4076f72..c94fadd 100644 --- a/lib/bds/desktop/shell_live/sidebar_events.ex +++ b/lib/bds/desktop/shell_live/sidebar_events.ex @@ -3,7 +3,9 @@ defmodule BDS.Desktop.ShellLive.SidebarEvents do alias BDS.Desktop.ShellLive.SidebarState, as: ShellSidebarState - @spec handle(Phoenix.LiveView.Socket.t(), String.t(), map(), (Phoenix.LiveView.Socket.t(), term() -> Phoenix.LiveView.Socket.t())) :: + @spec handle(Phoenix.LiveView.Socket.t(), String.t(), map(), (Phoenix.LiveView.Socket.t(), + term() -> + Phoenix.LiveView.Socket.t())) :: {:noreply, Phoenix.LiveView.Socket.t()} def handle(socket, event, params, reload) diff --git a/lib/bds/desktop/shell_live/tab_helpers.ex b/lib/bds/desktop/shell_live/tab_helpers.ex index a419da0..4c7d97c 100644 --- a/lib/bds/desktop/shell_live/tab_helpers.ex +++ b/lib/bds/desktop/shell_live/tab_helpers.ex @@ -49,7 +49,8 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do end end - defp default_tab_subtitle(_tab), do: "Desktop workbench content routed through the Elixir shell." + defp default_tab_subtitle(_tab), + do: "Desktop workbench content routed through the Elixir shell." def tab_route_label(nil), do: dgettext("ui", "Dashboard") def tab_route_label(%{type: type}), do: ShellData.route_label(type) @@ -168,7 +169,11 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do %{name: name} -> %{ title: blank_to_nil(name) || dgettext("ui", "Untitled Import"), - subtitle: dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.") + subtitle: + dgettext( + "ui", + "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported." + ) } _other -> @@ -183,7 +188,11 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do defp derived_tab_meta(%{type: :menu_editor}) do %{ title: dgettext("ui", "Blog Menu"), - subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.") + subtitle: + dgettext( + "ui", + "Manage the central blog navigation outline and save it to meta/menu.opml." + ) } end @@ -212,7 +221,8 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do if is_binary(value), do: String.trim(value) != "", else: false end - defp post_record_title(%Post{} = post), do: blank_to_nil(post.title) || blank_to_nil(post.slug) || post.id + defp post_record_title(%Post{} = post), + do: blank_to_nil(post.title) || blank_to_nil(post.slug) || post.id defp post_record_subtitle(%Post{} = post), do: Atom.to_string(post.status) diff --git a/lib/bds/desktop/shell_live/tags_editor.ex b/lib/bds/desktop/shell_live/tags_editor.ex index b72e967..848c13a 100644 --- a/lib/bds/desktop/shell_live/tags_editor.ex +++ b/lib/bds/desktop/shell_live/tags_editor.ex @@ -272,11 +272,13 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do end), selected: selected, new_tag: - Map.get(socket.assigns, :tags_editor, %{}) |> Map.get(:new_tag, %{"name" => "", "color" => ""}), + Map.get(socket.assigns, :tags_editor, %{}) + |> Map.get(:new_tag, %{"name" => "", "color" => ""}), edit_draft: edit_draft, templates: templates, merge_target: - Map.get(socket.assigns, :tags_editor, %{}) |> Map.get(:merge_target, List.first(selected) || ""), + Map.get(socket.assigns, :tags_editor, %{}) + |> Map.get(:merge_target, List.first(selected) || ""), selected_section: selected_section } diff --git a/lib/bds/desktop/shell_live/task_localization.ex b/lib/bds/desktop/shell_live/task_localization.ex index 9ebe745..c627439 100644 --- a/lib/bds/desktop/shell_live/task_localization.ex +++ b/lib/bds/desktop/shell_live/task_localization.ex @@ -1,7 +1,6 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do @moduledoc false - def localize_task_status(task_status, locale) do tasks = Enum.map(Map.get(task_status, :tasks, []), &localize_task(&1, locale)) active = Enum.filter(tasks, &(&1.status in [:running, :pending])) diff --git a/lib/bds/desktop/shell_live/template_editor.ex b/lib/bds/desktop/shell_live/template_editor.ex index 7cc970c..fd4f91f 100644 --- a/lib/bds/desktop/shell_live/template_editor.ex +++ b/lib/bds/desktop/shell_live/template_editor.ex @@ -165,7 +165,11 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do %Template{} = template -> case MCP.validate_template(current_draft(socket.assigns, template)["content"] || "") do {:ok, %{valid: true}} -> - notify_output(socket, dgettext("ui", "Templates"), dgettext("ui", "Template syntax is valid")) + notify_output( + socket, + dgettext("ui", "Templates"), + dgettext("ui", "Template syntax is valid") + ) {:ok, %{valid: false, errors: errors}} -> notify_output(socket, dgettext("ui", "Templates"), Enum.join(errors, "; "), "error") @@ -235,4 +239,4 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do send(self(), :reload_shell) socket end -end \ No newline at end of file +end diff --git a/lib/bds/desktop/shutdown.ex b/lib/bds/desktop/shutdown.ex index f04fe06..cb47d37 100644 --- a/lib/bds/desktop/shutdown.ex +++ b/lib/bds/desktop/shutdown.ex @@ -21,9 +21,7 @@ defmodule BDS.Desktop.Shutdown do userData: self() ) - :wxFrame.connect(frame, :command_menu_selected, - callback: &__MODULE__.command_menu_selected/2 - ) + :wxFrame.connect(frame, :command_menu_selected, callback: &__MODULE__.command_menu_selected/2) :ok rescue diff --git a/lib/bds/generation/validation.ex b/lib/bds/generation/validation.ex index 9c28632..837ab9b 100644 --- a/lib/bds/generation/validation.ex +++ b/lib/bds/generation/validation.ex @@ -431,7 +431,8 @@ defmodule BDS.Generation.Validation do _relative_path, %{requires_fallback_section_render: true}, _main? - ), do: true + ), + do: true defp targeted_output_for_plan?(relative_path, plan, _main?) do cond do diff --git a/lib/bds/media/rebuilder.ex b/lib/bds/media/rebuilder.ex index ae2fa41..d45025d 100644 --- a/lib/bds/media/rebuilder.ex +++ b/lib/bds/media/rebuilder.ex @@ -65,7 +65,9 @@ defmodule BDS.Media.Rebuilder do Sidecars.upsert_translation_from_sidecar( project, canonical_media_by_binary_path, - sidecar, sync_search: false) + sidecar, + sync_search: false + ) :ok = report_rebuild_progress(on_progress, index, total_files, "media files") end) diff --git a/lib/bds/posts/auto_translation.ex b/lib/bds/posts/auto_translation.ex index 8b0ff00..b83aa3d 100644 --- a/lib/bds/posts/auto_translation.ex +++ b/lib/bds/posts/auto_translation.ex @@ -113,7 +113,10 @@ defmodule BDS.Posts.AutoTranslation do ) {summary, completed} = - Enum.reduce(post_items, {empty_fill_summary(), 0}, fn %{post: post, language: language}, + Enum.reduce(post_items, {empty_fill_summary(), 0}, fn %{ + post: post, + language: language + }, {summary, completed} -> report_fill_item_progress( on_progress, @@ -132,7 +135,10 @@ defmodule BDS.Posts.AutoTranslation do end) {summary, _completed} = - Enum.reduce(media_items, {summary, completed}, fn %{media_id: media_id, language: language}, + Enum.reduce(media_items, {summary, completed}, fn %{ + media_id: media_id, + language: language + }, {summary, completed} -> report_fill_item_progress( on_progress, @@ -392,7 +398,11 @@ defmodule BDS.Posts.AutoTranslation do end with {:ok, translation} <- - AI.translate_media(media_id, language, Keyword.put(ai_opts(), :source_language, source_language)), + AI.translate_media( + media_id, + language, + Keyword.put(ai_opts(), :source_language, source_language) + ), {:ok, saved_translation} <- Media.upsert_media_translation(media_id, language, %{ title: translation.title, diff --git a/lib/bds/rendering/filters.ex b/lib/bds/rendering/filters.ex index b1c2d60..05a6363 100644 --- a/lib/bds/rendering/filters.ex +++ b/lib/bds/rendering/filters.ex @@ -69,8 +69,7 @@ defmodule BDS.Rendering.Filters do "macros/vimeo", %{ "id" => Map.get(params, "id", ""), - "title" => - default_macro_title(Map.get(params, "title"), language, "Vimeo video") + "title" => default_macro_title(Map.get(params, "title"), language, "Vimeo video") }, context ) diff --git a/lib/bds/rendering/list_archive.ex b/lib/bds/rendering/list_archive.ex index ea28a6c..2697e84 100644 --- a/lib/bds/rendering/list_archive.ex +++ b/lib/bds/rendering/list_archive.ex @@ -97,7 +97,8 @@ defmodule BDS.Rendering.ListArchive do post_data_json_by_id: Enum.into(posts, %{}, fn post -> {post.id, PostRendering.post_data_json_value(post)} end), day_blocks: day_blocks, - archive_month_name: Labels.month_name(Map.get(normalized_archive_context, :month), language), + archive_month_name: + Labels.month_name(Map.get(normalized_archive_context, :month), language), labels: Labels.for_language(language) } end @@ -148,7 +149,11 @@ defmodule BDS.Rendering.ListArchive do Map.get( assigns, "not_found_message", - BDS.Gettext.lgettext(language, "render", "The requested preview page could not be found.") + BDS.Gettext.lgettext( + language, + "render", + "The requested preview page could not be found." + ) ) ), not_found_back_label: diff --git a/lib/bds/scripts.ex b/lib/bds/scripts.ex index d420261..ecc918a 100644 --- a/lib/bds/scripts.ex +++ b/lib/bds/scripts.ex @@ -98,6 +98,7 @@ defmodule BDS.Scripts do content_changed? = has_attr?(attrs, :content) and attr(attrs, :content) != effective_script_content(script) + now = Persistence.now_ms() updates = @@ -309,7 +310,9 @@ defmodule BDS.Scripts do defp hydrate_script_content(%Script{} = script) do case script do - %Script{content: content} when is_binary(content) -> script + %Script{content: content} when is_binary(content) -> + script + %Script{status: :published, file_path: file_path} when file_path not in [nil, ""] -> %{script | content: published_script_body(script)} diff --git a/lib/bds/templates.ex b/lib/bds/templates.ex index 65a341d..012472d 100644 --- a/lib/bds/templates.ex +++ b/lib/bds/templates.ex @@ -102,7 +102,8 @@ defmodule BDS.Templates do end content_changed? = - has_attr?(attrs, :content) and attr(attrs, :content) != effective_template_content(template) + has_attr?(attrs, :content) and + attr(attrs, :content) != effective_template_content(template) slug_changed? = next_slug != template.slug now = Persistence.now_ms() @@ -472,7 +473,9 @@ defmodule BDS.Templates do defp hydrate_template_content(%Template{} = template) do case template do - %Template{content: content} when is_binary(content) -> template + %Template{content: content} when is_binary(content) -> + template + %Template{status: :published, file_path: file_path} when file_path not in [nil, ""] -> %{template | content: published_template_body(template)} diff --git a/lib/bds/ui/registry.ex b/lib/bds/ui/registry.ex index 3af34db..bc8373f 100644 --- a/lib/bds/ui/registry.ex +++ b/lib/bds/ui/registry.ex @@ -101,15 +101,40 @@ defmodule BDS.UI.Registry do %{id: :chat, singleton: false, entity_tab: true, title: dgettext("ui", "Chat")}, %{id: :import, singleton: false, entity_tab: true, title: dgettext("ui", "Import")}, %{id: :menu_editor, singleton: true, entity_tab: false, title: dgettext("ui", "Menu")}, - %{id: :metadata_diff, singleton: true, entity_tab: false, title: dgettext("ui", "Metadata Diff")}, + %{ + id: :metadata_diff, + singleton: true, + entity_tab: false, + title: dgettext("ui", "Metadata Diff") + }, %{id: :git_diff, singleton: false, entity_tab: true, title: dgettext("ui", "Git Diff")}, - %{id: :documentation, singleton: true, entity_tab: false, title: dgettext("ui", "Documentation")}, + %{ + id: :documentation, + singleton: true, + entity_tab: false, + title: dgettext("ui", "Documentation") + }, %{id: :api_documentation, singleton: true, entity_tab: false, title: dgettext("ui", "API")}, - %{id: :site_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Site Validation")}, - %{id: :translation_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Translations")}, + %{ + id: :site_validation, + singleton: true, + entity_tab: false, + title: dgettext("ui", "Site Validation") + }, + %{ + id: :translation_validation, + singleton: true, + entity_tab: false, + title: dgettext("ui", "Translations") + }, %{id: :scripts, singleton: false, entity_tab: true, title: dgettext("ui", "Script")}, %{id: :templates, singleton: false, entity_tab: true, title: dgettext("ui", "Template")}, - %{id: :find_duplicates, singleton: true, entity_tab: false, title: dgettext("ui", "Find Duplicates")} + %{ + id: :find_duplicates, + singleton: true, + entity_tab: false, + title: dgettext("ui", "Find Duplicates") + } ] end diff --git a/test/bds/ai_test.exs b/test/bds/ai_test.exs index c6a621e..9127cd4 100644 --- a/test/bds/ai_test.exs +++ b/test/bds/ai_test.exs @@ -206,7 +206,8 @@ defmodule BDS.AITest do {:ok, %{ json: %{ - "title" => "Analyzed " <> (get_in(request.messages, [Access.at(1), "content"]) || ""), + "title" => + "Analyzed " <> (get_in(request.messages, [Access.at(1), "content"]) || ""), "excerpt" => "Analyzed excerpt", "slug" => "analyzed-slug" }, @@ -386,17 +387,22 @@ defmodule BDS.AITest do {:ok, {_address, port}} = ThousandIsland.listener_info(server) - assert {:error, %{kind: :invalid_json_response, reason: %Jason.DecodeError{}}} = - BDS.AI.OpenAICompatibleRuntime.generate( - %{url: "http://127.0.0.1:#{port}/v1", api_key: nil}, - %{ - model: "gpt-test", - messages: [%{"role" => "user", "content" => "Hello"}], - max_output_tokens: 128, - tools: [] - }, - [] - ) + log = + capture_log(fn -> + assert {:error, %{kind: :invalid_json_response, reason: %Jason.DecodeError{}}} = + BDS.AI.OpenAICompatibleRuntime.generate( + %{url: "http://127.0.0.1:#{port}/v1", api_key: nil}, + %{ + model: "gpt-test", + messages: [%{"role" => "user", "content" => "Hello"}], + max_output_tokens: 128, + tools: [] + }, + [] + ) + end) + + assert log =~ "AI OpenAI-compatible response normalization failed" end test "openai-compatible generation accepts title requests without tools" do diff --git a/test/bds/desktop/import_shell_live_test.exs b/test/bds/desktop/import_shell_live_test.exs index 4d0a60b..71c5209 100644 --- a/test/bds/desktop/import_shell_live_test.exs +++ b/test/bds/desktop/import_shell_live_test.exs @@ -13,6 +13,26 @@ defmodule BDS.Desktop.ImportShellLiveTest do :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo) Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()}) + Enum.each(BDS.Tasks.list_running_tasks(), fn task -> + BDS.Tasks.cancel_task(task.id) + end) + + if :ets.whereis(:bds_ai_in_flight) != :undefined do + Enum.each(:ets.tab2list(:bds_ai_in_flight), fn {_conversation_id, pid} -> + Process.exit(pid, :kill) + end) + end + + for {_, pid, _, _} <- DynamicSupervisor.which_children(BDS.TCP.TaskSupervisor) do + DynamicSupervisor.terminate_child(BDS.TCP.TaskSupervisor, pid) + end + + for {_, pid, _, _} <- DynamicSupervisor.which_children(BDS.Tasks.TaskSupervisor) do + DynamicSupervisor.terminate_child(BDS.Tasks.TaskSupervisor, pid) + end + + Process.sleep(100) + temp_dir = Path.join(System.tmp_dir!(), "bds-import-shell-live-#{System.unique_integer([:positive])}") diff --git a/test/bds/desktop/shell_commands_test.exs b/test/bds/desktop/shell_commands_test.exs index 1e98370..f5776dc 100644 --- a/test/bds/desktop/shell_commands_test.exs +++ b/test/bds/desktop/shell_commands_test.exs @@ -241,7 +241,8 @@ defmodule BDS.Desktop.ShellCommandsTest do assert result.action == "fill_missing_translations" assert is_binary(result.task_id) - completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000) + completed = + wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000) assert completed.group_name == "AI" assert completed.result.project_id == project.id @@ -319,7 +320,9 @@ defmodule BDS.Desktop.ShellCommandsTest do }) assert {:ok, result} = ShellCommands.execute("fill_missing_translations") - completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000) + + completed = + wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000) assert completed.result.translated_posts == 1 assert completed.result.translated_media == 1 @@ -530,8 +533,10 @@ defmodule BDS.Desktop.ShellCommandsTest do wait_for_task( result.task_id, fn task -> - task.status in [:running, :completed] and is_number(task.progress) and task.progress > 0.2 and - Repo.get_by(BDS.Posts.Post, project_id: project.id, file_path: post_orphan_path) != nil and + task.status in [:running, :completed] and is_number(task.progress) and + task.progress > 0.2 and + Repo.get_by(BDS.Posts.Post, project_id: project.id, file_path: post_orphan_path) != + nil and Repo.get_by(BDS.Posts.Translation, project_id: project.id, file_path: post_translation_orphan_path @@ -545,6 +550,7 @@ defmodule BDS.Desktop.ShellCommandsTest do assert progressed.progress > 0.2 assert Repo.get_by(BDS.Posts.Post, project_id: project.id, file_path: post_orphan_path) + assert Repo.get_by(BDS.Posts.Translation, project_id: project.id, file_path: post_translation_orphan_path diff --git a/test/bds/desktop/shell_live_test.exs b/test/bds/desktop/shell_live_test.exs index ba8a0c5..2b63dc2 100644 --- a/test/bds/desktop/shell_live_test.exs +++ b/test/bds/desktop/shell_live_test.exs @@ -1,6 +1,7 @@ defmodule BDS.Desktop.ShellLiveTest do use ExUnit.Case, async: false + import ExUnit.CaptureLog import Phoenix.ConnTest import Phoenix.LiveViewTest @@ -175,6 +176,26 @@ defmodule BDS.Desktop.ShellLiveTest do :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo) Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()}) + Enum.each(BDS.Tasks.list_running_tasks(), fn task -> + BDS.Tasks.cancel_task(task.id) + end) + + if :ets.whereis(:bds_ai_in_flight) != :undefined do + Enum.each(:ets.tab2list(:bds_ai_in_flight), fn {_conversation_id, pid} -> + Process.exit(pid, :kill) + end) + end + + for {_, pid, _, _} <- DynamicSupervisor.which_children(BDS.TCP.TaskSupervisor) do + DynamicSupervisor.terminate_child(BDS.TCP.TaskSupervisor, pid) + end + + for {_, pid, _, _} <- DynamicSupervisor.which_children(BDS.Tasks.TaskSupervisor) do + DynamicSupervisor.terminate_child(BDS.Tasks.TaskSupervisor, pid) + end + + Process.sleep(100) + temp_dir = Path.join(System.tmp_dir!(), "bds-shell-live-#{System.unique_integer([:positive])}") @@ -422,12 +443,16 @@ defmodule BDS.Desktop.ShellLiveTest do _html = view - |> element("#tags-editor-shell button[phx-click='toggle_tag_selection'][phx-value-name='Alpha']") + |> element( + "#tags-editor-shell button[phx-click='toggle_tag_selection'][phx-value-name='Alpha']" + ) |> render_click() _html = view - |> element("#tags-editor-shell button[phx-click='toggle_tag_selection'][phx-value-name='Beta']") + |> element( + "#tags-editor-shell button[phx-click='toggle_tag_selection'][phx-value-name='Beta']" + ) |> render_click() _html = @@ -1328,7 +1353,9 @@ defmodule BDS.Desktop.ShellLiveTest do html = view - |> element("#settings-editor-shell button[phx-click='refresh_settings_ai_models'][phx-value-endpoint='online']") + |> element( + "#settings-editor-shell button[phx-click='refresh_settings_ai_models'][phx-value-endpoint='online']" + ) |> render_click() assert html =~ ~s() @@ -1336,7 +1363,9 @@ defmodule BDS.Desktop.ShellLiveTest do html = view - |> element("#settings-editor-shell button[phx-click='refresh_settings_ai_models'][phx-value-endpoint='airplane']") + |> element( + "#settings-editor-shell button[phx-click='refresh_settings_ai_models'][phx-value-endpoint='airplane']" + ) |> render_click() assert html =~ ~s() @@ -2717,7 +2746,12 @@ defmodule BDS.Desktop.ShellLiveTest do assert html =~ "ai-suggestions-modal" - send(view.pid, {:ai_suggestions_error, :post, post.id, :test_error}) + _ = + capture_log(fn -> + send(view.pid, {:ai_suggestions_error, :post, post.id, :test_error}) + render(view) + end) + html = render(view) assert html =~ "ai-suggestions-modal" @@ -3236,6 +3270,7 @@ defmodule BDS.Desktop.ShellLiveTest do view |> element("[data-testid='chat-model-selector-button']") |> render_click() + assert selector_html =~ ~s(class="chat-model-selector-menu") assert selector_html =~ ~s(data-testid="chat-model-selector-option") assert selector_html =~ "llama-current" diff --git a/test/bds/desktop_test.exs b/test/bds/desktop_test.exs index 39d6369..c17111f 100644 --- a/test/bds/desktop_test.exs +++ b/test/bds/desktop_test.exs @@ -25,7 +25,11 @@ defmodule BDS.DesktopTest do defmodule FakeWindowLifecycle do def hide(window_id) do - send(Application.fetch_env!(:bds, :desktop_shutdown_test_pid), {:window_hide_requested, window_id}) + send( + Application.fetch_env!(:bds, :desktop_shutdown_test_pid), + {:window_hide_requested, window_id} + ) + :ok end diff --git a/test/bds/ui/sidebar_test.exs b/test/bds/ui/sidebar_test.exs index ea6cd41..eeb231f 100644 --- a/test/bds/ui/sidebar_test.exs +++ b/test/bds/ui/sidebar_test.exs @@ -213,6 +213,8 @@ defmodule BDS.UI.SidebarTest do Repo.update_all( from(definition in BDS.ImportDefinitions.ImportDefinition, where: definition.id == ^definition_id - ), set: [updated_at: updated_at]) + ), + set: [updated_at: updated_at] + ) end end