Packages

LLM prompt evaluation workbench for Phoenix applications

Retired package: Renamed - Renamed to aludel: https://hex.pm/packages/aludel

Current section

Files

Jump to
vial_llm lib vial web live prompt_live evolution.html.heex
Raw

lib/vial/web/live/prompt_live/evolution.html.heex

<Layouts.app flash={@flash} current_path={@current_path}>
<div style="max-width: 1200px; margin: 0 auto; padding: 0 20px;">
<div style="margin-bottom: 32px; padding-bottom: 20px; border-bottom: 1px solid var(--border);">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
<div style="flex: 1;">
<h1 style="margin: 0 0 8px 0; font-size: 28px; font-weight: 600;">{@prompt.name}</h1>
<p
:if={@prompt.description}
style="color: var(--text-secondary); margin: 0; font-size: 15px;"
>
{@prompt.description}
</p>
</div>
<.link navigate={vial_path("prompts/#{@prompt.id}/edit")} class="button-primary">
<.icon name="hero-pencil" class="size-4" /> Edit Prompt
</.link>
</div>
<div :if={@prompt.tags != []} style="display: flex; gap: 8px;">
<span :for={tag <- @prompt.tags} class="tag">
{tag}
</span>
</div>
</div>
<div style="border-bottom: 1px solid var(--border); margin-bottom: 24px;">
<nav style="display: flex; gap: 32px; margin-bottom: -1px;">
<.link
navigate={vial_path("prompts/#{@prompt.id}")}
style="border-bottom: 2px solid transparent; padding: 16px 4px; font-size: 14px; font-weight: 500; color: var(--text-secondary); text-decoration: none;"
>
Details
</.link>
<.link
navigate={vial_path("prompts/#{@prompt.id}/evolution")}
style="border-bottom: 2px solid var(--accent); padding: 16px 4px; font-size: 14px; font-weight: 500; color: var(--accent); text-decoration: none; display: flex; align-items: center; gap: 6px;"
>
<.icon name="hero-beaker" class="size-4" /> Evolution
</.link>
</nav>
</div>
<%= if Enum.empty?(@metrics) do %>
<div class="vial-card" style="padding: 60px 20px; text-align: center;">
<.icon name="hero-chart-bar" class="size-12 text-muted empty-state-icon" />
<div class="empty-state-description">
No versions have been created yet. Create a version and run test suites to see evolution metrics.
</div>
</div>
<% else %>
<div class="vial-card" style="margin-bottom: 24px;">
<div style="padding: 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0; font-size: 18px; font-weight: 600;">Evolution Trends</h2>
<button
type="button"
phx-click="toggle_view_mode"
class="button-secondary"
style="padding: 8px 16px; font-size: 14px;"
>
<%= if @view_mode == :overall do %>
Switch to Per-Provider
<% else %>
Switch to Overall
<% end %>
</button>
</div>
<div
id="evolution-charts"
phx-hook="EvolutionChart"
data-chart-data={Jason.encode!(@chart_data)}
data-view-mode={@view_mode}
style="padding: 20px;"
>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px;">
<!-- Pass Rate Chart -->
<div>
<h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: var(--text-secondary);">
Pass Rate
</h3>
<div style="height: 200px;">
<canvas id="pass-rate-chart"></canvas>
</div>
</div>
<!-- Cost Chart (only if data exists) -->
<div id="cost-chart-container" style="display: none;">
<h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: var(--text-secondary);">
Average Cost
</h3>
<div style="height: 200px;">
<canvas id="cost-chart"></canvas>
</div>
</div>
<!-- Latency Chart (only if data exists) -->
<div id="latency-chart-container" style="display: none;">
<h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: var(--text-secondary);">
Average Latency
</h3>
<div style="height: 200px;">
<canvas id="latency-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="vial-card" style="overflow: hidden;">
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="border-bottom: 1px solid var(--border);">
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Version
</th>
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Created
</th>
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Runs
</th>
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Pass Rate
</th>
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Avg Cost
</th>
<th style="padding: 16px; text-align: left; font-size: 13px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em;">
Avg Latency
</th>
</tr>
</thead>
<tbody>
<%= for metric <- @metrics do %>
<tr style="border-bottom: 1px solid var(--border); transition: background-color 0.15s;">
<td style="padding: 16px; font-weight: 600; color: var(--text-primary);">
v{metric.version_number}
</td>
<td style="padding: 16px; color: var(--text-secondary); font-size: 14px;">
{Calendar.strftime(metric.created_at, "%b %d, %Y")}
</td>
<td style="padding: 16px; color: var(--text-primary); font-size: 14px;">
{metric.total_runs}
</td>
<td style="padding: 16px;">
<%= if metric.avg_pass_rate do %>
<% pass_rate = metric.avg_pass_rate %>
<% color =
if(pass_rate >= 80,
do: "color: #10b981;",
else:
if(pass_rate >= 50, do: "color: #f59e0b;", else: "color: #ef4444;")
) %>
<span style={"display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background-color: var(--bg-secondary); border-radius: 12px; font-size: 12px; font-weight: 500; #{color}"}>
<%= cond do %>
<% pass_rate >= 80 -> %>
<.icon name="hero-check-badge-solid" class="size-3" />
<% pass_rate >= 50 -> %>
<.icon name="hero-exclamation-circle" class="size-3" />
<% true -> %>
<.icon name="hero-x-circle" class="size-3" />
<% end %>
{pass_rate}%
</span>
<% else %>
<span style="color: var(--text-muted);"></span>
<% end %>
</td>
<td style="padding: 16px; font-size: 14px; font-family: 'SF Mono', 'Monaco', monospace;">
<%= if metric.avg_cost_usd do %>
<span style="color: var(--text-secondary);">
${:erlang.float_to_binary(Decimal.to_float(metric.avg_cost_usd),
decimals: 4
)}
</span>
<% else %>
<span style="color: var(--text-muted);"></span>
<% end %>
</td>
<td style="padding: 16px; font-size: 14px; color: var(--text-secondary);">
<%= if metric.avg_latency_ms do %>
{metric.avg_latency_ms}ms
<% else %>
<span style="color: var(--text-muted);"></span>
<% end %>
</td>
</tr>
<%= if not Enum.empty?(metric.provider_breakdown) do %>
<tr style="border-bottom: 1px solid var(--border); background-color: var(--bg-tertiary);">
<td colspan="6" style="padding: 20px;">
<div style="font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;">
Provider Breakdown
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px;">
<%= for breakdown <- metric.provider_breakdown do %>
<div style="padding: 12px; background-color: var(--bg-primary); border: 1px solid var(--border); border-radius: 8px;">
<div style="font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">
{breakdown.provider_name}
</div>
<div style="font-size: 13px; color: var(--text-secondary); margin-bottom: 8px;">
{breakdown.runs} runs
</div>
<%= if breakdown.avg_pass_rate do %>
<% pass_rate = breakdown.avg_pass_rate %>
<% color =
if(pass_rate >= 80,
do: "color: #10b981;",
else:
if(pass_rate >= 50,
do: "color: #f59e0b;",
else: "color: #ef4444;"
)
) %>
<span style={"display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background-color: var(--bg-secondary); border-radius: 12px; font-size: 12px; font-weight: 500; #{color}"}>
<%= cond do %>
<% pass_rate >= 80 -> %>
<.icon name="hero-check-badge-solid" class="size-3" />
<% pass_rate >= 50 -> %>
<.icon name="hero-exclamation-circle" class="size-3" />
<% true -> %>
<.icon name="hero-x-circle" class="size-3" />
<% end %>
{pass_rate}%
</span>
<% end %>
</div>
<% end %>
</div>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<% end %>
<div style="margin-top: 24px;">
<.link
navigate={vial_path("prompts")}
style="font-size: 14px; font-weight: 600; color: var(--accent); text-decoration: none;"
>
&larr; Back to prompts
</.link>
</div>
</div>
</Layouts.app>