fix: issue #2 auto-translate only on manual save now
This commit is contained in:
@@ -503,7 +503,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
||||
active_language = socket.assigns.active_language
|
||||
draft = component_current_draft(socket, post, metadata, active_language)
|
||||
|
||||
case persist(post, draft, active_language, metadata, :save) do
|
||||
action = if keep_draft?, do: :auto_save, else: :save
|
||||
|
||||
case persist(post, draft, active_language, metadata, action) do
|
||||
{:ok, record} ->
|
||||
refreshed_post = Posts.get_post!(post.id)
|
||||
_refreshed_form = persisted_form(refreshed_post, metadata, active_language)
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
canonical_language
|
||||
) do
|
||||
post
|
||||
|> save_canonical_draft(draft)
|
||||
|> save_canonical_draft(draft, action)
|
||||
|> maybe_publish_post(post.id, action)
|
||||
else
|
||||
post.id
|
||||
@@ -65,8 +65,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
else: dgettext("ui", "Delete this unpublished draft")
|
||||
end
|
||||
|
||||
defp save_canonical_draft(%Post{id: post_id}, draft) do
|
||||
Posts.update_post(post_id, %{
|
||||
defp save_canonical_draft(%Post{id: post_id}, draft, action) do
|
||||
Posts.update_post(
|
||||
post_id,
|
||||
%{
|
||||
title: blank_to_nil(Map.get(draft, "title")),
|
||||
excerpt: blank_to_nil(Map.get(draft, "excerpt")),
|
||||
content: blank_to_nil(Map.get(draft, "content")),
|
||||
@@ -76,7 +78,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
||||
language: blank_to_nil(Map.get(draft, "language")),
|
||||
do_not_translate: Map.get(draft, "do_not_translate", false),
|
||||
template_slug: blank_to_nil(Map.get(draft, "template_slug"))
|
||||
})
|
||||
},
|
||||
auto_translate: action != :auto_save
|
||||
)
|
||||
end
|
||||
|
||||
defp save_translation_draft(post_id, language, draft) do
|
||||
|
||||
@@ -106,7 +106,6 @@ defmodule BDS.Posts do
|
||||
{:ok, post} ->
|
||||
:ok = Embeddings.sync_post(post)
|
||||
:ok = Search.sync_post(post)
|
||||
:ok = AutoTranslation.maybe_schedule(post)
|
||||
{:ok, post}
|
||||
|
||||
error ->
|
||||
@@ -114,9 +113,9 @@ defmodule BDS.Posts do
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_post(String.t(), attrs()) ::
|
||||
@spec update_post(String.t(), attrs(), keyword()) ::
|
||||
{:ok, Post.t()} | {:error, :not_found | Ecto.Changeset.t()}
|
||||
def update_post(post_id, attrs) do
|
||||
def update_post(post_id, attrs, opts \\ []) do
|
||||
case Repo.get(Post, post_id) do
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
@@ -145,7 +144,13 @@ defmodule BDS.Posts do
|
||||
:ok = Embeddings.sync_post(updated_post)
|
||||
:ok = PostLinks.sync_post_links(updated_post)
|
||||
:ok = Search.sync_post(updated_post)
|
||||
|
||||
# Auto-translation only runs on an explicit manual save from the
|
||||
# editor, never on post creation or autosave.
|
||||
if Keyword.get(opts, :auto_translate, false) do
|
||||
:ok = AutoTranslation.maybe_schedule(updated_post)
|
||||
end
|
||||
|
||||
{:ok, updated_post}
|
||||
|
||||
error ->
|
||||
|
||||
@@ -231,6 +231,8 @@ rule PostTranslateAction {
|
||||
|
||||
rule PostAutoTranslateOnSave {
|
||||
when: PostSaved(post_id)
|
||||
-- Only an explicit manual save (or publish) triggers this. Auto-saves and
|
||||
-- post creation (sidebar, deeplink/bookmarklet, import, scripting) never do.
|
||||
-- Gate: airplane mode check + auto_translate not disabled (doNotTranslate=false)
|
||||
-- For each configured blog language missing a translation:
|
||||
-- Enqueue background translation task (title model)
|
||||
|
||||
@@ -192,8 +192,9 @@ invariant FtsIncludesTranslations {
|
||||
-- Distilled from: lib/bds/posts/auto_translation.ex
|
||||
--
|
||||
-- Two entry points share one translation primitive:
|
||||
-- 1. ScheduleAutoTranslation - reactive, fired after a post is created or
|
||||
-- updated. One background task per missing language produces a DRAFT
|
||||
-- 1. ScheduleAutoTranslation - reactive, fired only by an explicit manual
|
||||
-- save (or publish) in the post editor — never by post creation or
|
||||
-- auto-save. One background task per missing language produces a DRAFT
|
||||
-- translation, then cascades to the post's linked media.
|
||||
-- 2. FillMissingTranslations - batch maintenance action. Scans every
|
||||
-- published post, AUTO-PUBLISHES the generated translations, fills linked
|
||||
@@ -210,7 +211,9 @@ surface AutoTranslationControlSurface {
|
||||
facing _: TranslationOperator
|
||||
|
||||
provides:
|
||||
-- Reactive trigger: emitted by post create/update side effects.
|
||||
-- Reactive trigger: emitted only when a post is updated with
|
||||
-- auto_translate enabled (the editor's manual save/publish path);
|
||||
-- post creation and auto-save never emit it.
|
||||
PostSavedForAutoTranslation(post)
|
||||
-- Batch trigger: "fill missing translations" maintenance action.
|
||||
FillMissingTranslationsRequested(project)
|
||||
|
||||
@@ -143,7 +143,7 @@ defmodule BDS.PostTranslationsTest do
|
||||
assert {:ok, []} = Posts.list_post_translations(post.id)
|
||||
end
|
||||
|
||||
test "create_post enqueues and completes auto-translation tasks for missing project languages",
|
||||
test "create_post does not enqueue auto-translation tasks",
|
||||
%{project: project} do
|
||||
configure_auto_translation_test_runtime()
|
||||
|
||||
@@ -162,21 +162,79 @@ defmodule BDS.PostTranslationsTest do
|
||||
language: "en"
|
||||
})
|
||||
|
||||
translations = wait_for_post_translations(post.id, ["de", "fr"])
|
||||
|
||||
assert Enum.map(translations, & &1.language) == ["de", "fr"]
|
||||
assert Enum.all?(translations, &(&1.title == "Hallo Welt"))
|
||||
assert Enum.all?(translations, &(&1.excerpt == "Kurze Zusammenfassung"))
|
||||
assert Enum.all?(translations, &(&1.content == "# Hallo Welt\n\nUbersetzter Inhalt"))
|
||||
|
||||
tasks = wait_for_ai_tasks(2)
|
||||
|
||||
assert Enum.any?(tasks, &(&1.name == "Auto-translate Post to de" and &1.status == :completed))
|
||||
assert Enum.any?(tasks, &(&1.name == "Auto-translate Post to fr" and &1.status == :completed))
|
||||
assert Enum.all?(tasks, &(&1.group_name == "AI"))
|
||||
assert ai_tasks() == []
|
||||
assert {:ok, []} = Posts.list_post_translations(post.id)
|
||||
end
|
||||
|
||||
test "update_post auto-translates missing languages and cascades linked media translations",
|
||||
test "update_post without auto_translate does not enqueue auto-translation tasks",
|
||||
%{project: project} do
|
||||
configure_auto_translation_test_runtime()
|
||||
|
||||
assert {:ok, _metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
main_language: "en",
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Autosaved",
|
||||
content: "Source body",
|
||||
language: "en"
|
||||
})
|
||||
|
||||
assert {:ok, _updated_post} = Posts.update_post(post.id, %{content: "Autosaved body"})
|
||||
|
||||
assert ai_tasks() == []
|
||||
assert {:ok, []} = Posts.list_post_translations(post.id)
|
||||
end
|
||||
|
||||
test "editor persist schedules auto-translation on manual save but not on auto-save",
|
||||
%{project: project} do
|
||||
configure_auto_translation_test_runtime()
|
||||
|
||||
assert {:ok, metadata} =
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
main_language: "en",
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, post} =
|
||||
Posts.create_post(%{
|
||||
project_id: project.id,
|
||||
title: "Editor Post",
|
||||
content: "Source body",
|
||||
language: "en"
|
||||
})
|
||||
|
||||
draft = %{
|
||||
"title" => "Editor Post",
|
||||
"excerpt" => "",
|
||||
"content" => "Edited body",
|
||||
"tags" => "",
|
||||
"categories" => "",
|
||||
"author" => "",
|
||||
"language" => "en",
|
||||
"do_not_translate" => false,
|
||||
"template_slug" => ""
|
||||
}
|
||||
|
||||
alias BDS.Desktop.ShellLive.PostEditor.Persistence
|
||||
|
||||
assert {:ok, _post} = Persistence.persist(post, draft, "en", metadata, :auto_save)
|
||||
assert ai_tasks() == []
|
||||
|
||||
assert {:ok, _post} = Persistence.persist(post, draft, "en", metadata, :save)
|
||||
|
||||
[translation] = wait_for_post_translations(post.id, ["de"])
|
||||
assert translation.language == "de"
|
||||
|
||||
tasks = wait_for_ai_tasks(1)
|
||||
assert Enum.any?(tasks, &(&1.name == "Auto-translate Post to de" and &1.status == :completed))
|
||||
end
|
||||
|
||||
test "update_post with auto_translate translates missing languages and cascades linked media translations",
|
||||
%{project: project, temp_dir: temp_dir} do
|
||||
configure_auto_translation_test_runtime()
|
||||
|
||||
@@ -221,10 +279,14 @@ defmodule BDS.PostTranslationsTest do
|
||||
])
|
||||
|
||||
assert {:ok, _updated_post} =
|
||||
Posts.update_post(post.id, %{
|
||||
Posts.update_post(
|
||||
post.id,
|
||||
%{
|
||||
do_not_translate: false,
|
||||
content: "Updated source body"
|
||||
})
|
||||
},
|
||||
auto_translate: true
|
||||
)
|
||||
|
||||
[translation] = wait_for_post_translations(post.id, ["de"])
|
||||
assert translation.language == "de"
|
||||
@@ -427,8 +489,6 @@ defmodule BDS.PostTranslationsTest do
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_ai_tasks(count, attempts \\ 100)
|
||||
|
||||
test "do_not_translate guard prevents translation upsert via the API", %{
|
||||
project: project
|
||||
} do
|
||||
@@ -453,14 +513,19 @@ defmodule BDS.PostTranslationsTest do
|
||||
{"cannot add translations when do_not_translate is true", []}
|
||||
end
|
||||
|
||||
defp ai_tasks do
|
||||
BDS.Tasks.list_tasks()
|
||||
|> Enum.filter(&(&1.group_name == "AI"))
|
||||
end
|
||||
|
||||
defp wait_for_ai_tasks(count, attempts \\ 100)
|
||||
|
||||
defp wait_for_ai_tasks(_count, 0) do
|
||||
flunk("AI tasks did not reach expected state")
|
||||
end
|
||||
|
||||
defp wait_for_ai_tasks(count, attempts) do
|
||||
tasks =
|
||||
BDS.Tasks.list_tasks()
|
||||
|> Enum.filter(&(&1.group_name == "AI"))
|
||||
tasks = ai_tasks()
|
||||
|
||||
if length(tasks) >= count and Enum.all?(tasks, &(&1.status == :completed)) do
|
||||
tasks
|
||||
|
||||
Reference in New Issue
Block a user