feat: issue #25 implemented - cli

This commit is contained in:
2026-07-17 14:26:14 +02:00
parent e4fa61ae07
commit 0f3f1efa08
24 changed files with 2899 additions and 873 deletions

View File

@@ -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,

View File

@@ -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()
}

View File

@@ -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>