defmodule ArcanaWeb.CollectionsLive do @moduledoc """ LiveView for managing collections in Arcana. """ use Phoenix.LiveView import Ecto.Query import ArcanaWeb.DashboardComponents alias Arcana.Collection @impl true def mount(_params, session, socket) do repo = get_repo_from_session(session) {:ok, socket |> assign(repo: repo) |> assign(editing_collection: nil, confirm_delete_collection: nil) |> assign(stats: nil, collections: [], show_graph_stats: false)} end @impl true def handle_params(_params, _uri, socket) do {:noreply, load_data(socket)} end defp load_data(socket) do repo = socket.assigns.repo {collections, has_graph_data} = load_collections_with_stats(repo) socket |> assign(stats: load_stats(repo)) |> assign(collections: collections) |> assign(show_graph_stats: has_graph_data) end defp load_collections_with_stats(repo) do # Base collection data with document count collections = repo.all( from(c in Collection, left_join: d in Arcana.Document, on: d.collection_id == c.id, group_by: c.id, order_by: c.name, select: %{ id: c.id, name: c.name, description: c.description, document_count: count(d.id) } ) ) # Add chunk counts chunk_counts = repo.all( from(ch in Arcana.Chunk, join: d in Arcana.Document, on: ch.document_id == d.id, group_by: d.collection_id, select: {d.collection_id, count(ch.id)} ) ) |> Map.new() collections = Enum.map(collections, fn c -> Map.put(c, :chunk_count, Map.get(chunk_counts, c.id, 0)) end) # Always try to add graph stats - show columns if any data exists add_graph_stats(repo, collections) end defp add_graph_stats(repo, collections) do entity_counts = repo.all( from(e in Arcana.Graph.Entity, group_by: e.collection_id, select: {e.collection_id, count(e.id)} ) ) |> Map.new() # Relationships don't have collection_id directly - join through source entity relationship_counts = repo.all( from(r in Arcana.Graph.Relationship, join: e in Arcana.Graph.Entity, on: r.source_id == e.id, group_by: e.collection_id, select: {e.collection_id, count(r.id)} ) ) |> Map.new() community_counts = repo.all( from(c in Arcana.Graph.Community, group_by: c.collection_id, select: {c.collection_id, count(c.id)} ) ) |> Map.new() has_graph_data = map_size(entity_counts) > 0 or map_size(relationship_counts) > 0 collections_with_stats = Enum.map(collections, fn c -> c |> Map.put(:entity_count, Map.get(entity_counts, c.id, 0)) |> Map.put(:relationship_count, Map.get(relationship_counts, c.id, 0)) |> Map.put(:community_count, Map.get(community_counts, c.id, 0)) end) {collections_with_stats, has_graph_data} rescue # Tables might not exist _ -> {collections, false} end @impl true def handle_event("create_collection", %{"collection" => params}, socket) do repo = socket.assigns.repo name = params["name"] || "" description = params["description"] case Collection.get_or_create(name, repo, description) do {:ok, _collection} -> {:noreply, load_data(socket)} {:error, changeset} -> {:noreply, put_flash(socket, :error, "Failed to create collection: #{inspect(changeset.errors)}")} end end def handle_event("edit_collection", %{"id" => id}, socket) do repo = socket.assigns.repo collection = repo.get(Collection, id) {:noreply, assign(socket, editing_collection: collection)} end def handle_event("cancel_edit_collection", _params, socket) do {:noreply, assign(socket, editing_collection: nil)} end def handle_event("update_collection", %{"id" => id, "collection" => params}, socket) do repo = socket.assigns.repo collection = repo.get!(Collection, id) changeset = Collection.changeset(collection, %{ name: params["name"] || collection.name, description: params["description"] }) case repo.update(changeset) do {:ok, _updated} -> {:noreply, socket |> assign(editing_collection: nil) |> load_data()} {:error, changeset} -> {:noreply, put_flash(socket, :error, "Failed to update: #{inspect(changeset.errors)}")} end end def handle_event("confirm_delete_collection", %{"id" => id}, socket) do {:noreply, assign(socket, confirm_delete_collection: id)} end def handle_event("cancel_delete_collection", _params, socket) do {:noreply, assign(socket, confirm_delete_collection: nil)} end def handle_event("delete_collection", %{"id" => id}, socket) do repo = socket.assigns.repo case repo.get(Collection, id) do nil -> {:noreply, assign(socket, confirm_delete_collection: nil)} collection -> repo.delete!(collection) {:noreply, socket |> assign(confirm_delete_collection: nil) |> load_data()} end end @impl true def render(assigns) do ~H""" <.dashboard_layout stats={@stats} current_tab={:collections}>

Collections

Organize documents into collections for scoped searches and better organization.

Create Collection

<%= if Enum.empty?(@collections) do %>
No collections yet. Create one above.
<% else %> <%= if @show_graph_stats do %> <% end %> <%= for collection <- @collections do %> <%= if @editing_collection && @editing_collection.id == collection.id do %> <% else %> <%= if @show_graph_stats do %> <% end %> <% end %> <% end %>
Name Description Docs ChunksEntities Rels CommunitiesActions
<%= collection.name %> <%= collection.description || "-" %> <%= collection.document_count %> <%= collection.chunk_count %><%= collection[:entity_count] || 0 %> <%= collection[:relationship_count] || 0 %> <%= collection[:community_count] || 0 %> <%= if @confirm_delete_collection == collection.id do %>
Delete?
<% else %>
<% end %>
<% end %>
""" end end