Packages
phoenix_kit
1.7.118
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
221 Versions
Jump to
Current section
221 Versions
Compare versions
16
files changed
+163
additions
-66
deletions
unknownCHANGELOG.md
File is too large to be displayed (100 KB limit).
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/BeamLabEU/phoenix_kit">>}]}. |
| 2 2 | {<<"name">>,<<"phoenix_kit">>}. |
| 3 | - {<<"version">>,<<"1.7.117">>}. |
| 3 | + {<<"version">>,<<"1.7.118">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<65,32,102,111,117,110,100,97,116,105,111,110,32,102,111,114,32,98,117,105, |
| 6 6 | 108,100,105,110,103,32,69,108,105,120,105,114,32,80,104,111,101,110,105, |
| @@ -124,12 +124,24 @@ defmodule PhoenixKit.Modules.AI.Translation do | |
| 124 124 | # not an input-shape question — fail fast on bad args either way. |
| 125 125 | with :ok <- validate_uuid(endpoint_uuid, :no_endpoint), |
| 126 126 | :ok <- validate_uuid(prompt_uuid, :missing_prompt), |
| 127 | + :ok <- validate_non_empty(fields), |
| 127 128 | :ok <- validate_unique_markers(Map.keys(fields)), |
| 128 129 | :ok <- ensure_available() do |
| 129 130 | do_translate(endpoint_uuid, prompt_uuid, source_lang, target_lang, fields, opts) |
| 130 131 | end |
| 131 132 | end |
| 132 133 | |
| 134 | + # Short-circuit before dispatching to the AI plugin — an empty |
| 135 | + # `fields` map would render a prompt with no field variables, spend |
| 136 | + # real tokens on a `PhoenixKitAI.ask_with_prompt/4` call, and then |
| 137 | + # fail downstream in `parse_response/2` with `:no_markers`. Cheap to |
| 138 | + # reject up front. The error sentinel mirrors `parse_response/2`'s |
| 139 | + # shape so callers don't have to branch on a new error class. |
| 140 | + defp validate_non_empty(fields) when map_size(fields) == 0, |
| 141 | + do: {:error, {:parse_error, :no_markers}} |
| 142 | + |
| 143 | + defp validate_non_empty(_fields), do: :ok |
| 144 | + |
| 133 145 | defp validate_uuid(value, error) when is_binary(value) do |
| 134 146 | if String.trim(value) == "", do: {:error, error}, else: :ok |
| 135 147 | end |
| @@ -186,11 +198,8 @@ defmodule PhoenixKit.Modules.AI.Translation do | |
| 186 198 | # which `rescue` alone wouldn't catch. |
| 187 199 | try do |
| 188 200 | case PhoenixKitAI.ask_with_prompt(endpoint_uuid, prompt_uuid, variables, ai_opts) do |
| 189 | - {:ok, response} when is_binary(response) -> |
| 190 | - parse_response(response, Map.keys(fields)) |
| 191 | - |
| 192 | - {:ok, other} -> |
| 193 | - {:error, {:ai_error, {:unexpected_response, other}}} |
| 201 | + {:ok, response} -> |
| 202 | + handle_ai_response(response, fields) |
| 194 203 | |
| 195 204 | {:error, reason} -> |
| 196 205 | {:error, {:ai_error, reason}} |
| @@ -206,6 +215,35 @@ defmodule PhoenixKit.Modules.AI.Translation do | |
| 206 215 | end |
| 207 216 | end |
| 208 217 | |
| 218 | + # `PhoenixKitAI.ask_with_prompt/4` returns the full OpenAI-shaped |
| 219 | + # response map (`%{"choices" => [%{"message" => %{"content" => "..."}}]}`), |
| 220 | + # not a raw string. Reach for `PhoenixKitAI.Completion.extract_content/1` |
| 221 | + # to pull the assistant's content out — same helper publishing's |
| 222 | + # `TranslatePostWorker` already uses for its post-translate AI call. |
| 223 | + # Older versions of the plugin (or test stubs) may still return a |
| 224 | + # plain binary; the second clause keeps that working. |
| 225 | + @compile {:no_warn_undefined, [{PhoenixKitAI.Completion, :extract_content, 1}]} |
| 226 | + defp handle_ai_response(response, fields) when is_map(response) do |
| 227 | + case PhoenixKitAI.Completion.extract_content(response) do |
| 228 | + {:ok, content} when is_binary(content) -> |
| 229 | + parse_response(content, Map.keys(fields)) |
| 230 | + |
| 231 | + {:ok, _other} -> |
| 232 | + {:error, {:ai_error, {:unexpected_response, response}}} |
| 233 | + |
| 234 | + {:error, reason} -> |
| 235 | + {:error, {:ai_error, reason}} |
| 236 | + end |
| 237 | + end |
| 238 | + |
| 239 | + defp handle_ai_response(response, fields) when is_binary(response) do |
| 240 | + parse_response(response, Map.keys(fields)) |
| 241 | + end |
| 242 | + |
| 243 | + defp handle_ai_response(other, _fields) do |
| 244 | + {:error, {:ai_error, {:unexpected_response, other}}} |
| 245 | + end |
| 246 | + |
| 209 247 | @doc """ |
| 210 248 | Parses a structured `---FIELD_NAME---` response into a field map. |
| @@ -84,7 +84,12 @@ defmodule PhoenixKitWeb.Components.Core.DraggableList do | |
| 84 84 | |
| 85 85 | attr :on_reorder, :string, required: true, doc: "Event name to send on reorder" |
| 86 86 | attr :layout, :atom, default: :grid, values: [:grid, :list], doc: "Layout mode" |
| 87 | - attr :cols, :integer, default: 4, doc: "Number of grid columns (only for layout={:grid})" |
| 87 | + |
| 88 | + attr :cols, :any, |
| 89 | + default: 4, |
| 90 | + doc: |
| 91 | + "Grid columns (only for layout={:grid}). Either an integer 1..6 (mapped to a static `grid-cols-N`), or a string of Tailwind grid-column classes used verbatim — e.g. `\"grid-cols-4 lg:grid-cols-6 2xl:grid-cols-8\"` — for a responsive grid. The string form must be a literal in a Tailwind-scanned source so the classes are compiled. Any other value (out-of-range integer, atom, etc.) falls back to `grid-cols-4`." |
| 92 | + |
| 88 93 | attr :gap, :string, default: "gap-2", doc: "Gap between items (Tailwind class)" |
| 89 94 | attr :class, :string, default: "", doc: "Additional CSS classes for the container" |
| 90 95 | attr :item_class, :string, default: "", doc: "Additional CSS classes for each item wrapper" |
| @@ -159,7 +164,10 @@ defmodule PhoenixKitWeb.Components.Core.DraggableList do | |
| 159 164 | """ |
| 160 165 | end |
| 161 166 | |
| 162 | - # Map column count to Tailwind class (must be static for Tailwind to recognize) |
| 167 | + # Map column count to Tailwind class (must be static for Tailwind to recognize). |
| 168 | + # A string is taken verbatim so consumers can pass responsive grid-column |
| 169 | + # classes (e.g. "grid-cols-4 lg:grid-cols-6 3xl:grid-cols-8"). |
| 170 | + defp cols_to_class(cols) when is_binary(cols), do: cols |
| 163 171 | defp cols_to_class(1), do: "grid-cols-1" |
| 164 172 | defp cols_to_class(2), do: "grid-cols-2" |
| 165 173 | defp cols_to_class(3), do: "grid-cols-3" |
| @@ -120,6 +120,11 @@ defmodule PhoenixKitWeb.Components.Core.TableDefault do | |
| 120 120 | attr :storage_key, :string, default: nil |
| 121 121 | attr :wrapper_class, :string, default: "rounded-lg shadow-md overflow-x-auto overflow-y-clip" |
| 122 122 | |
| 123 | + attr :card_grid_class, :string, |
| 124 | + default: "gap-4 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4", |
| 125 | + doc: |
| 126 | + "Layout classes for the card-view grid (column density, gaps). Must NOT include a `display` utility (`grid`/`hidden`) — the component sets `display` per view-mode branch so controlled table mode can emit `hidden` cleanly. Override to change column count, e.g. a denser `gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5`. Must be a literal in a Tailwind-scanned source so the classes are compiled — a dynamically-built string won't be in the generated CSS." |
| 127 | + |
| 123 128 | attr :view_mode, :string, |
| 124 129 | default: nil, |
| 125 130 | values: [nil, "card", "table"], |
| @@ -325,7 +330,7 @@ defmodule PhoenixKitWeb.Components.Core.TableDefault do | |
| 325 330 | # Layout-only classes (no `display`) are always safe; the `display` |
| 326 331 | # utility is set per-branch so controlled "table" mode emits only |
| 327 332 | # `hidden` — no reliance on Tailwind's hidden-beats-grid source order. |
| 328 | - "gap-4 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4", |
| 333 | + @card_grid_class, |
| 329 334 | is_nil(@view_mode) && "grid md:hidden", |
| 330 335 | @view_mode == "card" && "grid", |
| 331 336 | @view_mode == "table" && "hidden" |
Loading more files…