Packages

phoenix_kit

1.7.90
1.7.209 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 llm_text sources publishing.ex
Raw

lib/modules/llm_text/sources/publishing.ex

defmodule PhoenixKit.Modules.LLMText.Sources.Publishing do
@moduledoc """
LLM text source for PhoenixKit Publishing module.
Generates:
- Index entries (one per published post) for llms.txt
- Individual `.txt` files per published post at `{group_slug}/{post_slug}.txt`
Only active when the Publishing module is loaded and enabled.
"""
@behaviour PhoenixKit.Modules.LLMText.Sources.Source
@compile {:no_warn_undefined,
[
{PhoenixKit.Modules.Publishing, :enabled?, 0},
{PhoenixKit.Modules.Publishing, :list_groups, 0},
{PhoenixKit.Modules.Publishing, :list_posts, 2}
]}
require Logger
alias PhoenixKit.Utils.Routes
@publishing_mod PhoenixKit.Modules.Publishing
@impl true
def source_name, do: :publishing
@impl true
def enabled? do
Code.ensure_loaded?(@publishing_mod) and
function_exported?(@publishing_mod, :enabled?, 0) and
@publishing_mod.enabled?()
rescue
_ -> false
end
@impl true
def collect_index_entries do
if enabled?() do
language = get_default_language()
groups = @publishing_mod.list_groups()
Enum.flat_map(groups, fn group ->
group_slug = group["slug"]
group_name = group["name"]
group_slug
|> @publishing_mod.list_posts(language)
|> Enum.filter(&published?/1)
|> Enum.map(fn post ->
%{
title: get_title(post),
url: build_post_url(post, group_slug),
description: extract_description(post),
group: group_name
}
end)
end)
else
[]
end
rescue
error ->
Logger.warning(
"LLMText PublishingSource failed to collect index entries: #{inspect(error)}"
)
[]
end
@impl true
def collect_page_files do
if enabled?() do
language = get_default_language()
groups = @publishing_mod.list_groups()
Enum.flat_map(groups, fn group ->
group_slug = group["slug"]
group_slug
|> @publishing_mod.list_posts(language)
|> Enum.filter(&published?/1)
|> Enum.map(fn post ->
path = build_file_path(group_slug, get_post_slug(post))
content = build_post_content(post, group_slug, get_title(post))
{path, content}
end)
end)
else
[]
end
rescue
error ->
Logger.warning("LLMText PublishingSource failed to collect page files: #{inspect(error)}")
[]
end
@doc """
Builds the file path for a post's LLM text file.
iex> build_file_path("blog", "hello-world")
"blog/hello-world.txt"
"""
@spec build_file_path(String.t(), String.t()) :: String.t()
def build_file_path(group_slug, post_slug) do
"#{group_slug}/#{post_slug}.txt"
end
@doc """
Builds markdown content for a post's LLM text file.
"""
@spec build_post_content(map(), String.t(), String.t()) :: String.t()
def build_post_content(post, group_slug, title) do
url = build_post_url(post, group_slug)
description = extract_description(post)
body = Map.get(post, :content, "")
url_line = if url != "", do: "> Source: #{url}\n\n", else: ""
desc_line = if description != "", do: "#{description}\n\n", else: ""
body_part = if is_binary(body), do: body, else: ""
"# #{title}\n\n#{url_line}#{desc_line}#{body_part}"
end
@doc """
Extracts a description for a post.
Uses metadata.description if available, otherwise falls back to the first
160 characters of the post content.
"""
@spec extract_description(map()) :: String.t()
def extract_description(post) do
case post do
%{metadata: %{description: desc}} when is_binary(desc) and desc != "" ->
desc
%{metadata: %{"description" => desc}} when is_binary(desc) and desc != "" ->
desc
_ ->
content = Map.get(post, :content, "")
if is_binary(content) and content != "" do
content
|> String.replace(~r/\s+/, " ")
|> String.trim()
|> String.slice(0, 160)
else
""
end
end
end
# Private helpers
defp published?(post) do
case post do
%{metadata: %{status: "published"}} -> true
%{metadata: %{"status" => "published"}} -> true
_ -> false
end
end
defp get_title(post) do
case post do
%{metadata: %{title: title}} when is_binary(title) and title != "" -> title
%{metadata: %{"title" => title}} when is_binary(title) and title != "" -> title
%{slug: slug} when is_binary(slug) -> format_slug(slug)
_ -> "Post"
end
end
defp get_post_slug(post) do
case Map.get(post, :mode) do
:timestamp ->
date = Map.get(post, :date)
time = Map.get(post, :time)
if date && time do
time_str = time |> Time.to_string() |> String.slice(0..4) |> String.replace(":", "-")
"#{Date.to_iso8601(date)}-#{time_str}"
else
Map.get(post, :slug, "post") || "post"
end
_ ->
Map.get(post, :url_slug) || Map.get(post, :slug, "post") || "post"
end
end
defp build_post_url(post, group_slug) do
site_url = get_site_url()
prefix = get_url_prefix()
post_slug = get_post_slug(post)
path_parts =
[prefix, group_slug, post_slug]
|> Enum.reject(&(&1 in [nil, "", "/"]))
path = "/" <> Enum.join(path_parts, "/")
if site_url != "" do
String.trim_trailing(site_url, "/") <> path
else
path
end
end
defp get_default_language do
PhoenixKit.Settings.get_content_language()
rescue
_ -> nil
end
defp get_site_url do
PhoenixKit.Settings.get_setting("site_url", "")
rescue
_ -> ""
end
defp get_url_prefix do
case Routes.url_prefix() do
"/" -> ""
prefix -> String.trim(prefix, "/")
end
rescue
_ -> ""
end
defp format_slug(slug) do
slug
|> String.replace("-", " ")
|> String.replace("_", " ")
|> String.split()
|> Enum.map_join(" ", &String.capitalize/1)
end
end