Packages

phoenix_kit

1.7.21
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules ai web prompts.ex
Raw

lib/modules/ai/web/prompts.ex

defmodule PhoenixKit.Modules.AI.Web.Prompts do
@moduledoc """
LiveView for AI prompts management.
This module provides an interface for managing reusable AI prompt templates
with variable substitution support.
## Features
- **Prompt Management**: Add, edit, delete, enable/disable AI prompts
- **Variable Display**: Shows extracted variables from prompt content
- **Usage Tracking**: View usage count and last used time
## Route
This LiveView is mounted at `{prefix}/admin/ai/prompts` and requires
appropriate admin permissions.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.AI
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes
@sort_options [
{:sort_order, "Order"},
{:name, "Name"},
{:usage_count, "Usage"},
{:last_used_at, "Last Used"},
{:inserted_at, "Created"}
]
@page_size 20
@impl true
def mount(_params, session, socket) do
current_path = get_current_path(socket, session)
project_title = Settings.get_setting("project_title", "PhoenixKit")
# Subscribe to real-time updates
if connected?(socket) do
AI.subscribe_prompts()
end
socket =
socket
|> assign(:current_path, current_path)
|> assign(:page_title, "AI Prompts")
|> assign(:project_title, project_title)
|> assign(:prompts, [])
|> assign(:sort_by, :sort_order)
|> assign(:sort_dir, :asc)
|> assign(:sort_options, @sort_options)
|> assign(:page, 1)
|> assign(:page_size, @page_size)
|> assign(:total_prompts, 0)
{:ok, socket}
end
@impl true
def handle_params(params, uri, socket) do
{sort_by, sort_dir, page} = parse_sort_params(params)
current_path = URI.parse(uri).path
socket =
socket
|> assign(:sort_by, sort_by)
|> assign(:sort_dir, sort_dir)
|> assign(:page, page)
|> assign(:current_path, current_path)
|> reload_prompts()
{:noreply, socket}
end
@valid_sort_fields Enum.map(@sort_options, fn {field, _} -> Atom.to_string(field) end)
defp parse_sort_params(params) do
{
parse_sort_field(params["sort"], @valid_sort_fields, :sort_order),
parse_sort_dir(params["dir"]),
parse_page(params["page"])
}
end
defp parse_sort_field(field, valid_fields, default) when is_binary(field) do
if field in valid_fields, do: String.to_existing_atom(field), else: default
end
defp parse_sort_field(_, _valid_fields, default), do: default
defp parse_sort_dir("asc"), do: :asc
defp parse_sort_dir("desc"), do: :desc
defp parse_sort_dir(_), do: :asc
defp parse_page(nil), do: 1
defp parse_page(""), do: 1
defp parse_page(p) when is_binary(p) do
case Integer.parse(p) do
{n, ""} when n > 0 -> n
_ -> 1
end
end
defp parse_page(_), do: 1
# ===========================================
# PROMPT ACTIONS
# ===========================================
@impl true
def handle_event("toggle_prompt", %{"id" => id}, socket) do
prompt = AI.get_prompt!(String.to_integer(id))
case AI.update_prompt(prompt, %{enabled: !prompt.enabled}) do
{:ok, _updated} ->
{:noreply,
socket
|> reload_prompts()
|> put_flash(:info, "Prompt #{if prompt.enabled, do: "disabled", else: "enabled"}")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to update prompt")}
end
end
@impl true
def handle_event("delete_prompt", %{"id" => id}, socket) do
prompt = AI.get_prompt!(String.to_integer(id))
case AI.delete_prompt(prompt) do
{:ok, _} ->
{:noreply,
socket
|> reload_prompts()
|> put_flash(:info, "Prompt deleted")}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to delete prompt")}
end
end
@impl true
def handle_event("sort", %{"by" => field}, socket) do
# Validate field before converting to atom to prevent crashes from malicious input
field =
if field in @valid_sort_fields do
String.to_existing_atom(field)
else
:sort_order
end
current_sort_by = socket.assigns.sort_by
current_sort_dir = socket.assigns.sort_dir
# Toggle direction if same field, otherwise default to desc for usage/last_used, asc for others
sort_dir =
if field == current_sort_by do
if current_sort_dir == :asc, do: :desc, else: :asc
else
if field in [:usage_count, :last_used_at, :inserted_at], do: :desc, else: :asc
end
# Reset to page 1 when sorting changes
path = Routes.ai_path() <> "/prompts?sort=#{field}&dir=#{sort_dir}"
{:noreply, push_patch(socket, to: path)}
end
@impl true
def handle_event("goto_page", %{"page" => page_str}, socket) do
case Integer.parse(page_str) do
{page, ""} when page > 0 ->
sort_by = socket.assigns.sort_by
sort_dir = socket.assigns.sort_dir
path = build_prompts_url(sort_by, sort_dir, page)
{:noreply, push_patch(socket, to: path)}
_ ->
{:noreply, socket}
end
end
# ===========================================
# PUBSUB HANDLERS - Real-time updates
# ===========================================
@impl true
def handle_info({event, _prompt}, socket)
when event in [:prompt_created, :prompt_updated, :prompt_deleted] do
# Reload prompts list when any prompt changes
{:noreply, reload_prompts(socket)}
end
# Catch-all for other PubSub messages
@impl true
def handle_info(_msg, socket), do: {:noreply, socket}
# ===========================================
# PRIVATE HELPERS
# ===========================================
defp reload_prompts(socket) do
sort_by = socket.assigns.sort_by
sort_dir = socket.assigns.sort_dir
page = socket.assigns.page
page_size = socket.assigns.page_size
{prompts, total} =
AI.list_prompts(
sort_by: sort_by,
sort_dir: sort_dir,
page: page,
page_size: page_size
)
socket
|> assign(:prompts, prompts)
|> assign(:total_prompts, total)
end
defp build_prompts_url(sort_by, sort_dir, page) do
base = Routes.ai_path() <> "/prompts?sort=#{sort_by}&dir=#{sort_dir}"
if page > 1 do
base <> "&page=#{page}"
else
base
end
end
defp get_current_path(socket, session) do
case socket.assigns do
%{__changed__: _, current_path: path} when is_binary(path) -> path
_ -> session["current_path"] || Routes.ai_path() <> "/prompts"
end
end
end