From 631ceb0521ff8420fc1744ce073d04f836f5e636 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sat, 2 May 2026 08:40:36 +0200 Subject: [PATCH] fix: still fighting crashes on close and weird AI chat behaviou --- lib/bds/ai/chat.ex | 7 +++++ lib/bds/ai/openai_compatible_runtime.ex | 6 +++++ lib/bds/desktop/shutdown.ex | 20 ++++++++++++++ test/bds/ai_test.exs | 35 ++++++++++++++++-------- test/bds/desktop_test.exs | 36 +++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 11 deletions(-) diff --git a/lib/bds/ai/chat.ex b/lib/bds/ai/chat.ex index 531b510..6c7e985 100644 --- a/lib/bds/ai/chat.ex +++ b/lib/bds/ai/chat.ex @@ -385,12 +385,19 @@ defmodule BDS.AI.Chat do cond do chat_user_message_count(conversation_id) < 1 -> + Logger.debug("Chat title generation skipped reason=:no_user_messages") {:ok, reply} not generated_chat_title?(conversation.title, conversation.model) -> + Logger.debug( + "Chat title generation skipped reason=:conversation_already_titled title=#{inspect(conversation.title)}" + ) + {:ok, reply} true -> + Logger.debug("Chat title generation requested conversation_id=#{conversation_id}") + case generate_chat_title(user_content, opts) do {:ok, title} when is_binary(title) and title != "" -> now = Persistence.now_ms() diff --git a/lib/bds/ai/openai_compatible_runtime.ex b/lib/bds/ai/openai_compatible_runtime.ex index d4ac24c..10ca8a0 100644 --- a/lib/bds/ai/openai_compatible_runtime.ex +++ b/lib/bds/ai/openai_compatible_runtime.ex @@ -1,6 +1,8 @@ defmodule BDS.AI.OpenAICompatibleRuntime do @moduledoc false + require Logger + alias BDS.AI.HttpClient def list_models(endpoint, opts \\ []) when is_map(endpoint) and is_list(opts) do @@ -39,6 +41,10 @@ defmodule BDS.AI.OpenAICompatibleRuntime do |> maybe_disable_thinking(request.model) |> maybe_put_tools(Map.get(request, :tools, [])) + Logger.debug( + "AI OpenAI-compatible request operation=#{inspect(Map.get(request, :operation))} model=#{inspect(request.model)} url=#{url} tools=#{payload |> Map.get("tools", []) |> length()}" + ) + with {:ok, response} <- HttpClient.post(url, headers, Jason.encode!(payload)), 200 <- response.status do normalize_response(response.body) diff --git a/lib/bds/desktop/shutdown.ex b/lib/bds/desktop/shutdown.ex index a22fc9f..f04fe06 100644 --- a/lib/bds/desktop/shutdown.ex +++ b/lib/bds/desktop/shutdown.ex @@ -66,13 +66,33 @@ defmodule BDS.Desktop.Shutdown do defp start_shutdown_task do Task.start(fn -> MainWindow.persist_now() + maybe_hide_window() + Process.sleep(50) quit_module().quit() end) :ok end + defp maybe_hide_window do + module = window_module() + + if function_exported?(module, :hide, 1) do + module.hide(MainWindow.window_id()) + end + + :ok + rescue + _error -> :ok + catch + :exit, _reason -> :ok + end + defp quit_module do Application.get_env(:bds, :desktop_window_quit_module, Window) end + + defp window_module do + Application.get_env(:bds, :desktop_window_module, Window) + end end diff --git a/test/bds/ai_test.exs b/test/bds/ai_test.exs index 4395412..acc5f74 100644 --- a/test/bds/ai_test.exs +++ b/test/bds/ai_test.exs @@ -1,7 +1,9 @@ defmodule BDS.AITest do use ExUnit.Case, async: false + import ExUnit.CaptureLog import Ecto.Query + require Logger alias BDS.Media.Media alias BDS.Persistence @@ -342,17 +344,28 @@ defmodule BDS.AITest do {:ok, {_address, port}} = ThousandIsland.listener_info(server) - assert {:ok, %{content: "Short Title"}} = - BDS.AI.OpenAICompatibleRuntime.generate( - %{url: "http://127.0.0.1:#{port}/v1", api_key: nil}, - %{ - operation: :chat_title, - model: "qwen3.5-122b", - messages: [%{"role" => "user", "content" => "Topic: posts per month"}], - max_output_tokens: 20 - }, - [] - ) + previous_level = Logger.level() + Logger.configure(level: :debug) + + log = + capture_log(fn -> + assert {:ok, %{content: "Short Title"}} = + BDS.AI.OpenAICompatibleRuntime.generate( + %{url: "http://127.0.0.1:#{port}/v1", api_key: nil}, + %{ + operation: :chat_title, + model: "qwen3.5-122b", + messages: [%{"role" => "user", "content" => "Topic: posts per month"}], + max_output_tokens: 20 + }, + [] + ) + end) + + Logger.configure(level: previous_level) + + assert log =~ "AI OpenAI-compatible request operation=:chat_title" + assert log =~ ~s(model="qwen3.5-122b") assert_received {:completion_payload, payload} assert payload["model"] == "qwen3.5-122b" diff --git a/test/bds/desktop_test.exs b/test/bds/desktop_test.exs index c20f1bb..64cf90d 100644 --- a/test/bds/desktop_test.exs +++ b/test/bds/desktop_test.exs @@ -23,6 +23,18 @@ defmodule BDS.DesktopTest do end end + defmodule FakeWindowLifecycle do + def hide(window_id) do + send(Application.fetch_env!(:bds, :desktop_shutdown_test_pid), {:window_hide_requested, window_id}) + :ok + end + + def quit do + send(Application.fetch_env!(:bds, :desktop_shutdown_test_pid), :window_quit_requested) + :ok + end + end + test "desktop configuration no longer uses a pending adapter" do assert Application.get_env(:bds, BDS.Application)[:desktop_adapter] == :desktop end @@ -191,6 +203,30 @@ defmodule BDS.DesktopTest do assert_receive :window_quit_requested end + test "app-owned shutdown hides the window before hard quit" do + previous_module = Application.get_env(:bds, :desktop_shutdown_module) + previous_quit_module = Application.get_env(:bds, :desktop_window_quit_module) + previous_window_module = Application.get_env(:bds, :desktop_window_module) + previous_pid = Application.get_env(:bds, :desktop_shutdown_test_pid) + + Application.put_env(:bds, :desktop_shutdown_module, BDS.Desktop.Shutdown) + Application.put_env(:bds, :desktop_window_quit_module, FakeWindowLifecycle) + Application.put_env(:bds, :desktop_window_module, FakeWindowLifecycle) + Application.put_env(:bds, :desktop_shutdown_test_pid, self()) + expected_window_id = BDS.Desktop.MainWindow.window_id() + + on_exit(fn -> + restore_env(:desktop_shutdown_module, previous_module) + restore_env(:desktop_window_quit_module, previous_quit_module) + restore_env(:desktop_window_module, previous_window_module) + restore_env(:desktop_shutdown_test_pid, previous_pid) + end) + + assert :ok = BDS.Desktop.Shutdown.request_quit() + assert_receive {:window_hide_requested, ^expected_window_id} + assert_receive :window_quit_requested + end + test "desktop root html is a LiveView shell and references only the live bootstrap assets" do conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}") conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))