feat: issue #25 implemented - cli
This commit is contained in:
@@ -120,7 +120,7 @@ defmodule BDS.Desktop.ShellCommands do
|
||||
"rebuild_embedding_index",
|
||||
"Rebuild Embedding Index",
|
||||
"Embeddings",
|
||||
fn report -> rebuild_embedding_index_work(project, report) end
|
||||
fn report -> Maintenance.rebuild_embedding_index(project.id, on_progress: report) end
|
||||
)
|
||||
end
|
||||
|
||||
@@ -221,7 +221,7 @@ defmodule BDS.Desktop.ShellCommands do
|
||||
defp dispatch("rebuild_database", project, _params) do
|
||||
group_id = task_group_id("rebuild_database")
|
||||
attrs = %{group_id: group_id, group_name: "Maintenance"}
|
||||
[first_step | remaining_steps] = rebuild_database_steps(project)
|
||||
[first_step | remaining_steps] = Maintenance.full_rebuild_steps(project.id)
|
||||
|
||||
{:ok, posts_task} =
|
||||
Tasks.submit_task(first_step.name, first_step.work, attrs)
|
||||
@@ -692,102 +692,6 @@ defmodule BDS.Desktop.ShellCommands do
|
||||
|> length() > 1
|
||||
end
|
||||
|
||||
defp rebuild_database_steps(project) do
|
||||
[
|
||||
%{
|
||||
name: "Rebuild Posts From Files",
|
||||
work: fn report ->
|
||||
{:ok, posts} =
|
||||
Maintenance.rebuild_from_filesystem(project.id, "post",
|
||||
on_progress: report,
|
||||
rebuild_embeddings: false
|
||||
)
|
||||
|
||||
report.(1.0, "Post rebuild complete")
|
||||
%{project_id: project.id, counts: %{posts: length(posts)}}
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Rebuild Media From Files",
|
||||
work: fn report ->
|
||||
{:ok, media} =
|
||||
Maintenance.rebuild_from_filesystem(project.id, "media", on_progress: report)
|
||||
|
||||
report.(1.0, "Media rebuild complete")
|
||||
%{project_id: project.id, counts: %{media: length(media)}}
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Rebuild Scripts From Files",
|
||||
work: fn report ->
|
||||
{:ok, scripts} =
|
||||
Maintenance.rebuild_from_filesystem(project.id, "script", on_progress: report)
|
||||
|
||||
report.(1.0, "Script rebuild complete")
|
||||
%{project_id: project.id, counts: %{scripts: length(scripts)}}
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Rebuild Templates From Files",
|
||||
work: fn report ->
|
||||
{:ok, templates} =
|
||||
Maintenance.rebuild_from_filesystem(project.id, "template", on_progress: report)
|
||||
|
||||
report.(1.0, "Template rebuild complete")
|
||||
%{project_id: project.id, counts: %{templates: length(templates)}}
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Rebuild Post Links",
|
||||
work: fn report ->
|
||||
:ok = Posts.rebuild_post_links(project.id, on_progress: report)
|
||||
report.(1.0, "Post links rebuilt")
|
||||
%{project_id: project.id}
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Regenerate Missing Thumbnails",
|
||||
work: fn report ->
|
||||
result = BDS.Media.regenerate_missing_thumbnails(project.id, on_progress: report)
|
||||
report.(1.0, "Missing thumbnails regenerated")
|
||||
Map.put(result, :project_id, project.id)
|
||||
end
|
||||
},
|
||||
%{
|
||||
name: "Rebuild Embedding Index",
|
||||
work: fn report -> rebuild_embedding_index_work(project, report) end
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp rebuild_embedding_index_work(project, report) do
|
||||
case Embeddings.rebuild_project(project.id, on_progress: report) do
|
||||
{:ok, rebuilt_post_ids} ->
|
||||
report.(1.0, "Embedding index rebuilt")
|
||||
|
||||
%{
|
||||
project_id: project.id,
|
||||
rebuilt_post_ids: rebuilt_post_ids,
|
||||
rebuilt_count: length(rebuilt_post_ids)
|
||||
}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, embedding_error_message(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
defp embedding_error_message(reason) do
|
||||
detail =
|
||||
case reason do
|
||||
message when is_binary(message) -> message
|
||||
{:embedding_backend_unavailable, _inner} -> "the embedding service did not start"
|
||||
other -> inspect(other)
|
||||
end
|
||||
|
||||
"Could not build the embedding index: #{detail}. The model is downloaded on first use, " <>
|
||||
"so check your internet connection — or turn off semantic similarity in Settings."
|
||||
end
|
||||
|
||||
defp run_rebuild_sequence(_group_id, _attrs, []), do: :ok
|
||||
|
||||
defp run_rebuild_sequence(group_id, attrs, [step | remaining_steps]) do
|
||||
|
||||
@@ -15,14 +15,7 @@ defmodule BDS.Desktop.ShellLive.GalleryImport do
|
||||
@spec start(list(String.t()), String.t(), String.t(), String.t(), integer(), pid()) :: :ok
|
||||
def start(paths, project_id, post_id, language, concurrency_limit, parent) do
|
||||
{:ok, metadata} = Metadata.get_project_metadata(project_id)
|
||||
main_language = metadata.main_language || language
|
||||
blog_languages = metadata.blog_languages || []
|
||||
|
||||
translate_targets =
|
||||
[main_language | blog_languages]
|
||||
|> Enum.reject(&(&1 == language or is_nil(&1)))
|
||||
|> Enum.uniq()
|
||||
|
||||
translate_targets = translate_targets(metadata, language)
|
||||
{in_flight, remaining} = Enum.split(paths, concurrency_limit)
|
||||
|
||||
tasks =
|
||||
@@ -48,6 +41,36 @@ defmodule BDS.Desktop.ShellLive.GalleryImport do
|
||||
send(parent, {:add_images_complete, length(paths)})
|
||||
end
|
||||
|
||||
@doc """
|
||||
The translation targets for AI-generated media texts: the project main
|
||||
language plus all blog languages, minus the source `language`.
|
||||
"""
|
||||
def translate_targets(metadata, language) do
|
||||
main_language = metadata.main_language || language
|
||||
blog_languages = metadata.blog_languages || []
|
||||
|
||||
[main_language | blog_languages]
|
||||
|> Enum.reject(&(&1 == language or is_nil(&1)))
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Imports a single file and runs the same best-effort AI enrichment
|
||||
(title/alt/caption plus translations) as the gallery pipeline, without
|
||||
linking the media to a post. Used by the CLI `media` command (issue #25).
|
||||
|
||||
Returns `{:ok, media, display_title}` or `{:error, reason}`.
|
||||
"""
|
||||
def import_and_enrich(path, project_id, language, translate_targets) do
|
||||
case Media.import_media(%{project_id: project_id, source_path: path}) do
|
||||
{:ok, media} ->
|
||||
{:ok, media, enrich_media(media, language, translate_targets)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp drain_tasks(
|
||||
[],
|
||||
tasks,
|
||||
|
||||
@@ -69,6 +69,15 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("install_cli_tool", _params, socket) do
|
||||
case BDS.UI.SettingsForm.run_action("install_cli") do
|
||||
{:ok, message} -> LiveToast.send_toast(:info, message)
|
||||
{:error, message} -> LiveToast.send_toast(:error, message)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("change_settings_search", %{"query" => query}, socket) do
|
||||
socket =
|
||||
socket
|
||||
@@ -302,7 +311,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
||||
query,
|
||||
~w(mcp claude copilot gemini opencode mistral codex agent server)
|
||||
),
|
||||
data_visible?: section_matches?(query, ~w(data rebuild maintenance folder filesystem)),
|
||||
data_visible?: section_matches?(query, ~w(data rebuild maintenance folder filesystem cli)),
|
||||
supported_languages: @supported_languages,
|
||||
protected_categories: ManagedCategories.protected_categories()
|
||||
}
|
||||
|
||||
@@ -381,6 +381,15 @@
|
||||
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button>
|
||||
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label class="setting-label"><%= dgettext("ui", "Command Line Tool") %></label>
|
||||
<p class="setting-description"><%= dgettext("ui", "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app") %></p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button class="secondary ui-button ui-button-secondary" type="button" phx-click="install_cli_tool" phx-target={@myself}><%= dgettext("ui", "Install CLI Tool") %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user