empowered.house ↗

agents/run.ex

Agent run

One agent run, step by step, with the tool each step uses and the point where it asks a person.

Use it
# in the module that renders the template
use EHVisuals

<div class="h-96 rounded-xl border border-border">
  <.agents_run animated />
</div>
default
isometric
waiting for a person
custom agent · three steps
light surface
isometric · fade out · still

Props

Prop Type Default What it does
agent string "Intake agent"
run string "Run 771 · 00:42"
status string "Running"
steps list [%{state: "done", text: "Read the purchase order", tool: "mail.read"}, %{state: "done", ...}, ...] Up to five `%{text, tool, state}`. State is `done`, `running`, `waiting` or `pending`.
Frame props, shared by every composition
Prop Type Default What it does
animated boolean false Turns motion on. Without it the composition renders in its final, still state.
trigger "mount" | "in-view" | "in-view-repeat" "in-view" When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view.
hover boolean false Enables the hover micro-interactions the composition supports.
glow boolean true The warm incandescent halo behind the composition.
fade_out boolean false Fades the bottom edge into the surface underneath.
isometric boolean false Tilts the composition into the isometric plane of the platform stack.
theme "dark" | "light" "dark" `dark` for the site canvas, `light` for the cream reversed surface.
class string nil Extra classes for the frame.
rest global — Any other HTML attribute, set on the frame.

Source

Two files. Copy them into your app if you would rather own the code than depend on the library.

lib/eh_visuals/agents/run.ex
run.ex
defmodule EHVisuals.Agents.Run do
  @moduledoc """
  One agent run, step by step: what is done, what is running, and where it
  stops to ask a person.
  """

  use EHVisuals.Composition,
    name: "agents/run",
    title: "Agent run",
    description:
      "One agent run, step by step, with the tool each step uses and the point where it asks a person."

  @steps [
    %{text: "Read the purchase order", tool: "mail.read", state: "done"},
    %{text: "Match it to the contract", tool: "wiki.search", state: "done"},
    %{text: "Draft the ERP entry", tool: "erp.write", state: "running"},
    %{text: "Ask Marta to approve", tool: "people", state: "pending"}
  ]

  attr(:agent, :string, default: "Intake agent")
  attr(:run, :string, default: "Run 771 · 00:42")
  attr(:status, :string, default: "Running")

  attr(:steps, :list,
    default: @steps,
    doc: "Up to five `%{text, tool, state}`. State is `done`, `running`, `waiting` or `pending`."
  )

  attr(:label, :string, default: "An agent run with two steps done and the third running")
  frame_attrs()

  @spec agents_run(map()) :: Phoenix.LiveView.Rendered.t()
  def agents_run(assigns) do
    rows = assigns.steps |> Enum.take(5) |> Enum.with_index()

    assigns =
      assigns
      |> assign(:rows, rows)
      |> assign(:last, length(rows) - 1)
      |> assign(:pill_w, 28 + String.length(assigns.status) * 6.4)
      |> assign(:height, 74 + length(rows) * 38 + 6)

    ~H"""
    <.frame name="agents/run" label={@label} {frame_opts(assigns)}>
      <svg viewBox={"0 0 340 #{@height}"}>
        <g class="ehv-h-lift">
          <rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="339" height={@height - 1} rx="12" />
          <g class="ehv-a-fade ehv-i-1">
            <rect class="ehv-card-3" x="20" y="17" width="28" height="28" rx="8" />
            <rect class="ehv-fill-lime" x="29" y="26" width="10" height="10" rx="3" />
            <text class="ehv-t-strong" x="58" y="29" font-size="13">{@agent}</text>
            <text class="ehv-mono" x="58" y="43" font-size="8.5">{String.upcase(@run)}</text>
            <rect
              class="ehv-fill-lime-soft"
              x={320 - @pill_w}
              y="21"
              width={@pill_w}
              height="20"
              rx="6"
            />
            <circle class="ehv-fill-lime ehv-a-pulse" cx={320 - @pill_w + 11} cy="31" r="2.5" />
            <text class="ehv-mono ehv-t-lime" x="311" y="34.5" font-size="8.5" text-anchor="end">
              {String.upcase(@status)}
            </text>
          </g>
          <line class="ehv-line-faint" x1="20" x2="320" y1="60.5" y2="60.5" />
          <g :for={{step, index} <- @rows}>
            <line
              :if={index < @last}
              class={"ehv-line-soft ehv-a-fade ehv-i-#{index + 3}"}
              x1="30"
              x2="30"
              y1={98 + index * 38}
              y2={112 + index * 38}
            />
            <g class={"ehv-a-rise ehv-i-#{index + 2}"}>
              <g :if={step.state == "done"}>
                <circle class="ehv-fill-lime-soft" cx="30" cy={86 + index * 38} r="9" />
                <path
                  class={"ehv-line-lime ehv-round ehv-a-draw ehv-i-#{index + 4}"}
                  d={"M26 #{86 + index * 38} l2.8 2.8 l5 -5.6"}
                  pathLength="1"
                />
              </g>
              <g :if={step.state == "running"} class="ehv-agents-run__spin">
                <circle class="ehv-line-soft" cx="30" cy={86 + index * 38} r="8" />
                <path class="ehv-line-lime ehv-round" d={"M30 #{78 + index * 38} a8 8 0 0 1 8 8"} />
              </g>
              <g :if={step.state == "waiting"}>
                <circle class="ehv-fill-lime ehv-a-ping" cx="30" cy={86 + index * 38} r="6" />
                <circle class="ehv-node-lime" cx="30" cy={86 + index * 38} r="8" />
                <rect class="ehv-fill-lime" x="27" y={83 + index * 38} width="6" height="6" rx="2" />
              </g>
              <circle
                :if={step.state not in ["done", "running", "waiting"]}
                class="ehv-node-soft"
                cx="30"
                cy={86 + index * 38}
                r="8"
              />
              <text
                class={[step.state not in ["done", "running", "waiting"] && "ehv-t-3"]}
                x="50"
                y={90.5 + index * 38}
                font-size="12.5"
              >
                {step.text}
              </text>
              <text
                class={["ehv-mono", step.state == "waiting" && "ehv-t-lime"]}
                x="320"
                y={90 + index * 38}
                font-size="8.5"
                text-anchor="end"
              >
                {String.upcase(step.tool)}
              </text>
            </g>
          </g>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/agents/run.css
run.css
/* agents/run */
.ehv-agents-run { --ehv-max-w: 360px; --ehv-step: 130ms; }

/* The spinner turns around the centre of its own track circle. */
@keyframes ehv-agents-run-spin { to { transform: rotate(360deg); } }

.ehv-agents-run__spin { transform-box: fill-box; transform-origin: center; }
.ehv--animated .ehv-agents-run__spin { animation: ehv-agents-run-spin 1.1s linear infinite; }