defmodule ArcanaWeb.SearchLive do @moduledoc """ LiveView for searching documents in Arcana. """ use Phoenix.LiveView import ArcanaWeb.DashboardComponents alias Arcana.Document @impl true def mount(_params, session, socket) do repo = get_repo_from_session(session) {:ok, socket |> assign(repo: repo) |> assign( search_results: [], search_query: "", expanded_result_id: nil, viewing_document: nil, stats: nil, collections: [], source_ids: [] )} end @impl true def handle_params(_params, _uri, socket) do {:noreply, load_data(socket)} end defp load_data(socket) do repo = socket.assigns.repo socket |> assign(stats: load_stats(repo)) |> assign(collections: load_collections(repo)) |> assign(source_ids: load_source_ids(repo)) end @impl true def handle_event("search", params, socket) do query = params["query"] || "" results = run_search(query, params, socket.assigns.repo) {:noreply, assign(socket, search_results: results, search_query: query, expanded_result_id: nil)} end def handle_event("toggle_result", %{"id" => id}, socket) do current = socket.assigns.expanded_result_id new_id = if current == id, do: nil, else: id {:noreply, assign(socket, expanded_result_id: new_id)} end def handle_event("view_search_document", %{"id" => id}, socket) do repo = socket.assigns.repo import Ecto.Query document = repo.get(Document, id) chunks = repo.all( from(c in Arcana.Chunk, where: c.document_id == ^id, order_by: c.chunk_index ) ) {:noreply, assign(socket, viewing_document: %{document: document, chunks: chunks})} end def handle_event("close_search_document", _params, socket) do {:noreply, assign(socket, viewing_document: nil)} end defp run_search("", _params, _repo), do: [] defp run_search(query, params, repo) do case params |> build_search_opts(repo) |> then(&Arcana.search(query, &1)) do {:ok, results} -> results {:error, _reason} -> [] end end defp build_search_opts(params, repo) do [ repo: repo, limit: parse_int(params["limit"], 10), threshold: parse_float(params["threshold"], 0.0), mode: parse_mode(params["mode"]) ] |> add_source_id_opt(params["source_id"]) |> add_collection_opt(normalize_collections(params["collections"])) end defp add_source_id_opt(opts, nil), do: opts defp add_source_id_opt(opts, ""), do: opts defp add_source_id_opt(opts, source_id), do: Keyword.put(opts, :source_id, source_id) defp add_collection_opt(opts, []), do: opts defp add_collection_opt(opts, [single]), do: Keyword.put(opts, :collection, single) defp add_collection_opt(opts, multiple), do: Keyword.put(opts, :collection, multiple) defp normalize_collections(nil), do: [] defp normalize_collections(collections) when is_list(collections), do: Enum.filter(collections, &(&1 != "")) defp normalize_collections(collection), do: [collection] @impl true def render(assigns) do ~H""" <.dashboard_layout stats={@stats} current_tab={:search}> """ end defp search_document_detail(assigns) do ~H"""

Document Details

<%= @viewing.document.id %>
<%= @viewing.document.source_id || "-" %>
<%= format_metadata(@viewing.document.metadata) %>
<%= @viewing.document.inserted_at %>

Full Content

<%= @viewing.document.content %>

Chunks (<%= length(@viewing.chunks) %>)

<%= for chunk <- @viewing.chunks do %>
Chunk <%= chunk.chunk_index %> <%= chunk.token_count %> tokens
<%= chunk.text %>
<% end %>
""" end end