fix: added delete buttons on chats and have chat titeling (maybe)

This commit is contained in:
2026-05-01 22:29:06 +02:00
parent a17c549817
commit d3aa7f2438
11 changed files with 205 additions and 6 deletions

View File

@@ -23,6 +23,8 @@ defmodule BDS.AI.Chat do
@default_system_prompt "You are the bDS AI backend. Be precise, prefer structured JSON when asked, and avoid inventing blog facts."
@default_max_output_tokens 16_384
@title_max_output_tokens 20
@chat_title_max_length 30
@chat_max_tool_rounds 10
@default_context_window 128_000
@@ -59,6 +61,22 @@ defmodule BDS.AI.Chat do
Repo.get(ChatConversation, conversation_id)
end
@spec delete_chat_conversation(String.t()) :: {:ok, :deleted} | {:error, :not_found | term()}
def delete_chat_conversation(conversation_id) when is_binary(conversation_id) do
case Repo.get(ChatConversation, conversation_id) do
nil ->
{:error, :not_found}
%ChatConversation{} = conversation ->
Repo.delete_all(from message in ChatMessage, where: message.conversation_id == ^conversation_id)
case Repo.delete(conversation) do
{:ok, _conversation} -> {:ok, :deleted}
{:error, reason} -> {:error, reason}
end
end
end
@spec available_chat_models(String.t() | nil) :: [map()]
def available_chat_models(current_model \\ nil) do
endpoint_models = configured_chat_models()
@@ -303,7 +321,7 @@ defmodule BDS.AI.Chat do
defp fallback_provider_name(_provider), do: "Other"
defp do_send_chat_message(conversation, _user_message, opts) do
defp do_send_chat_message(conversation, user_message, opts) do
runtime = Keyword.get(opts, :runtime, OpenAICompatibleRuntime)
project_id = Keyword.get(opts, :project_id, active_project_id())
@@ -327,11 +345,106 @@ defmodule BDS.AI.Chat do
runtime,
opts,
@chat_max_tool_rounds
) do
),
{:ok, reply} <- maybe_generate_chat_title(conversation.id, user_message.content, reply, opts) do
{:ok, reply}
end
end
defp maybe_generate_chat_title(conversation_id, user_content, reply, opts) do
conversation = Repo.get!(ChatConversation, conversation_id)
cond do
chat_user_message_count(conversation_id) != 1 ->
{:ok, reply}
not generated_chat_title?(conversation.title, conversation.model) ->
{:ok, reply}
true ->
case generate_chat_title(user_content, opts) do
{:ok, title} when is_binary(title) and title != "" ->
now = Persistence.now_ms()
conversation
|> ChatConversation.changeset(%{title: title, updated_at: now})
|> Repo.update()
|> case do
{:ok, updated_conversation} ->
{:ok, %{reply | conversation: format_conversation(updated_conversation)}}
{:error, _reason} ->
{:ok, reply}
end
_other ->
{:ok, reply}
end
end
end
defp generate_chat_title(user_content, opts) when is_binary(user_content) do
runtime = Keyword.get(opts, :runtime, OpenAICompatibleRuntime)
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, sanitize_chat_title(Map.get(response, :content))}
end
end
defp build_chat_title_request(user_content, model) do
%{
operation: :chat_title,
model: model,
max_output_tokens: @title_max_output_tokens,
messages: [
%{
"role" => "system",
"content" =>
"Generate an ultra-short title (2-3 words, max 25 characters) for this conversation. Focus ONLY on the topic. Ignore any capability disclaimers. Output ONLY the title text."
},
%{"role" => "user", "content" => "Topic: #{String.slice(user_content, 0, 100)}"}
]
}
end
defp sanitize_chat_title(title) when is_binary(title) do
title =
title
|> String.trim()
|> String.trim_leading("\"")
|> String.trim_leading("'")
|> String.trim_trailing("\"")
|> String.trim_trailing("'")
|> String.trim_trailing(".")
|> String.trim_trailing("!")
|> String.trim_trailing("?")
if String.length(title) > @chat_title_max_length do
String.slice(title, 0, @chat_title_max_length - 3) <> "..."
else
title
end
end
defp sanitize_chat_title(_title), do: ""
defp chat_user_message_count(conversation_id) do
Repo.aggregate(
from(message in ChatMessage,
where: message.conversation_id == ^conversation_id and message.role == :user
),
:count,
:id
)
end
defp generated_chat_title?(title, model) do
title in [generated_chat_title(nil), generated_chat_title(model)]
end
defp chat_round(
_conversation,
_messages,