ExklusivBeta

Agent Lenses <mv-agent-lenses>

Ein Modell für KI-Agenten im Hintergrund, fünf Linsen (Board, Inbox, Tasks, Calendar, Graph), und jeder Agent ist eine dauerhafte Karte, die physisch zwischen den Layouts wandert: Beim Wechsel der Linse gleitet jede Karte per FLIP von ihrem alten Rechteck zum neuen, während sie sich von der vollen Karte in eine Zeile, einen Zeitbalken oder einen Graph-Knoten verwandelt, sodass du nie einen Agenten verlierst. Jede Linse deckt den toten Winkel einer anderen ab: Absichten beim Ablegen auf dem Board („Drop here = restart the agent“), in der Inbox gruppierte Abhängigkeitsketten, Statuszusammenfassungen der Unteraufgaben bei eingeklappten Tasks, unbefristete Läufe, die am Kalenderrand ausblenden, live fließende Kanten im Abhängigkeitsgraphen. Live-Fortschritt, verstrichene Zeit und Schnellantworten an blockierte Agenten.

KategorieDatenanzeige
TypWeb Component (<mv-agent-lenses>)
StatusBeta
KitKI, die du überprüfen kannst
Keywordsexclusive, ai, agents, agent-management, kanban, inbox, tree, gantt, timeline, dag, dependency-graph, flip, morph, multi-view, live, human-in-the-loop

When to use

  • An app manages several background AI agents and users need board, inbox, task, calendar and graph views
  • Blocked agents need quick replies and dependencies between agents must stay visible
  • Switching views must keep each agent traceable as its card moves to its new place

Avoid when

  • The work items are ordinary tasks moved across columns, not live agents → use Kanban instead
  • A single long-running AI job needs terms, a budget cap and checkpoints → use Run Contract instead
  • Only one or two agents run at a time; five lenses add more interface than the task needs

Installation

node scripts/add.mjs agent-lenses --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["agent-lenses"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/focus.js, core/motion.js, core/observe.js, core/position.js, components/agent-lenses/agent-lenses.js, components/agent-lenses/agent-lenses.css.

Verwendung

Schnellstart mit dem kleinsten funktionierenden Markup:

<mv-agent-lenses label="Fall launch">
  <script type="application/json">[
    { "id": "copy", "name": "Write launch copy", "status": "running", "progress": 0.6 },
    { "id": "images", "name": "Resize hero images", "status": "blocked", "needsAction": "Use the new logo?", "replies": ["Yes", "No"] },
    { "id": "posts", "name": "Schedule social posts", "status": "scheduled", "dependsOn": ["copy", "images"] }
  ]</script>
</mv-agent-lenses>

Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:

<div id="al-demo" style="width:100%;max-width:78rem;margin-inline:auto">
  <style>
    #al-demo .al-head { display:flex; align-items:flex-end; justify-content:space-between; gap:.5rem 1.5rem; flex-wrap:wrap; margin:0 0 .75rem }
    #al-demo .al-head h3 { margin:0; font-size:1.125rem; letter-spacing:-.01em }
    #al-demo .al-head p { margin:.2rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
    #al-demo .al-hint { color:var(--mv-fg-muted); font-size:.75rem; line-height:1.9 }
    #al-demo mv-agent-lenses { --mv-agent-lenses-max-height: 36rem }
    #al-demo .al-log { display:grid; gap:.25rem; margin:.75rem 0 0; padding:0; list-style:none; font-size:.75rem; color:var(--mv-fg-muted) }
    #al-demo .al-log li { display:flex; gap:.5rem; align-items:baseline; min-width:0 }
    #al-demo .al-log code { flex:none; padding:.0625rem .375rem; border-radius:var(--mv-radius-sm); background:var(--mv-bg-muted); color:var(--mv-fg); font:500 .6875rem/1.4 var(--mv-font-mono) }
    #al-demo .al-log span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
  </style>

  <div class="al-head">
    <div>
      <h3>Fall collection launch</h3>
      <p>Fieldnote Outfitters · 11 agents and subagents working in the background</p>
    </div>
    <span class="al-hint">Drag a board card, or press <kbd class="mv-kbd">Alt</kbd> <kbd class="mv-kbd">←</kbd> <kbd class="mv-kbd">→</kbd> on a focused card</span>
  </div>

  <mv-agent-lenses id="al-demo-lenses" label="Fall launch agents" lens="board"></mv-agent-lenses>

  <ol class="al-log" id="al-demo-log" aria-label="Event log"></ol>

  <script type="module">
    const el = document.getElementById("al-demo-lenses");
    const logEl = document.getElementById("al-demo-log");
    const at = (min) => Date.now() + min * 60000;

    // Set before the element is defined: the component picks it up on upgrade.
    el.agents = [
      { id: "research", name: "Competitor research", goal: "Review the fall lines of six competing brands: pricing, materials, colorways.", status: "reviewed", start: at(-190), end: at(-122), updated: at(-96) },
      { id: "prices", name: "Price watch", goal: "Check competitor prices every hour and flag any gap above 10%.", status: "running", start: at(-172), trigger: "On every competitor price drop", updated: at(-9) },
      { id: "copy", name: "Product copywriting", goal: "Write the 48 product pages of the collection from the brief and the research.", status: "running", dependsOn: ["research"], start: at(-112), eta: at(58), progress: 0.58, updated: at(-3) },
      { id: "copy-coats", name: "Coats & jackets copy", parent: "copy", goal: "16 pages: fit, materials, care, size guide.", status: "done", start: at(-108), end: at(-41), updated: at(-41) },
      { id: "copy-knit", name: "Knitwear copy", parent: "copy", goal: "18 sweater and cardigan pages, highlighting recycled wool.", status: "running", start: at(-62), eta: at(24), progress: 0.66, updated: at(-2) },
      { id: "copy-acc", name: "Accessories copy", parent: "copy", goal: "14 pages for beanies, scarves and gloves.", status: "scheduled", start: at(22), eta: at(58), updated: at(-30) },
      { id: "translate", name: "German & Italian translation", goal: "Translate and localize the product pages for the Berlin and Milan stores.", status: "scheduled", dependsOn: ["copy"], eta: at(150), updated: at(-52) },
      { id: "visuals", name: "Campaign visuals", goal: "Homepage banner, six social posts and a 20-page lookbook.", status: "running", dependsOn: ["research"], start: at(-84), eta: at(72), progress: 0.41, updated: at(-6) },
      { id: "retouch", name: "Packshot retouching", parent: "visuals", goal: "Cutouts and color matching for 24 packshots.", status: "done", start: at(-78), end: at(-13), updated: at(-13),
        needsAction: "All 24 retouched packshots are ready. Publish them to the store?", replies: ["Approve", "Request changes"] },
      { id: "newsletter", name: "Newsletter scheduling", goal: "Schedule the collection announcement for 42,000 subscribers.", status: "blocked", dependsOn: ["research"], start: at(-38), eta: at(35), updated: at(-19),
        needsAction: "Send the newsletter on Thursday, Oct 2 at 9:00 AM?", replies: ["Approve", "Suggest another time"] },
      { id: "golive", name: "Store go-live", goal: "Publish the collection on all three storefronts and switch on the homepage banner.", status: "scheduled", dependsOn: ["translate", "visuals"], eta: at(205), updated: at(-52) },
    ];

    const name = (id) => el.agents.find((a) => a.id === id)?.name ?? id;
    const log = (type, text) => {
      const li = document.createElement("li");
      const code = document.createElement("code");
      code.textContent = type;
      const span = document.createElement("span");
      span.textContent = text;
      li.append(code, span);
      logEl.prepend(li);
      while (logEl.children.length > 3) logEl.lastElementChild.remove();
    };

    el.addEventListener("mv-lens-change", (e) => log("mv-lens-change", `${e.detail.previous} → ${e.detail.lens}`));
    el.addEventListener("mv-agent-select", (e) => log("mv-agent-select", e.detail.agent.name));
    el.addEventListener("mv-agent-move", (e) => log("mv-agent-move", `${name(e.detail.id)}: ${e.detail.from} → ${e.detail.to} (${e.detail.meaning})`));
    el.addEventListener("mv-agent-reply", (e) => {
      const { id, reply } = e.detail;
      log("mv-agent-reply", `${name(id)}: “${reply}”`);
      // The host decides what a reply does; here, a plausible agent reaction.
      if (id === "newsletter" && reply === "Approve") el.update(id, { status: "running", needsAction: null, progress: 0.15, eta: at(12) });
      else if (id === "newsletter") el.update(id, { needsAction: "Which slot works better?", replies: ["Tue, Oct 7 · 9 AM", "Thu, Oct 9 · 6 PM"] });
      else if (id === "retouch" && reply === "Approve") el.update(id, { status: "reviewed", needsAction: null });
      else if (id === "retouch") el.update(id, { status: "running", needsAction: null, progress: 0.7, start: at(0), end: null, eta: at(20) });
      else el.update(id, { status: "running", needsAction: null, progress: 0.1, start: at(0), eta: at(15) });
    });

    // Simulated live progress: running agents advance every few seconds.
    const timer = setInterval(() => {
      if (!el.isConnected) return clearInterval(timer);
      for (const a of el.agents) {
        if (a.status !== "running" || a.progress === null) continue;
        const p = Math.min(1, a.progress + 0.006 + Math.random() * 0.01);
        if (p < 1) { el.update(a.id, { progress: p }); continue; }
        el.update(a.id, { status: "done", progress: 1, end: at(0) });
        if (a.id === "copy-knit") el.update("copy-acc", { status: "running", start: at(0), progress: 0.02 });
      }
    }, 2500);
  </script>
</div>

API

Attributes

NameTypDefaultDescription
lensboard | inbox | tasks | calendar | graphboardActive lens. Changing it animates every card to its new place (instant under reduced motion).
labelstringAgentsAccessible name of the region (and of the lens tab list).

Properties

NameTypDescription
agentsAgent[]The model: { id, name, goal?, status: "scheduled" | "running" | "blocked" | "done" | "reviewed", parent?, dependsOn?: id[], start?, eta?, end?, progress? (0-1), needsAction?: question, replies?: string[], trigger?: string, updated? }. Times are ms, Date or ISO strings. Cards are reused by id. Can be set before the element is defined, or given as a child <script type="application/json">.
lensstringReflects the lens attribute.
collapsedstring[]Ids of collapsed parents in the Tasks lens.
selectedstring | nullId of the selected agent (set without firing mv-agent-select).

Methods

NameDescription
update(id, patch)Merges a live change into one agent (an unknown id adds it). Progress-only changes refresh the card in place; status, dependencies, timing or questions relayout with FLIP. Batched to one layout per frame; status changes are announced.
setLens(name)Switches lens (same as setting the attribute). Returns false for an unknown lens.
toggle(id, open?)Collapses / expands a parent in the Tasks lens.
remove(id)Removes an agent; its card fades out.

Events

NameDescription
mv-lens-changeThe lens changed. detail: { lens, previous }.
mv-agent-selectClick, Enter or Space on a card. detail: { id, agent }.
mv-agent-moveCancelable. A card was dropped in another board column (drag, Alt+arrows or the Move menu). detail: { id, from, to, meaning } (meaning is the plain-language intent, e.g. “unblock and resume the agent”). preventDefault() sends the card back.
mv-agent-replyA quick-reply button was pressed on an agent waiting for a human. detail: { id, reply, question }. The host decides what happens (usually update(id, { status, needsAction: null })).

Content structure

NameDescription
<script type="application/json">Optional: initial agents as JSON, for server-rendered pages.

CSS classes

NameDescription
mv-agent-lenses-cardThe persistent card; data-density = card | item | row | bar | node, data-status, data-ask, data-selected.
mv-agent-lenses-tab / -indicatorLens switcher (role tablist) and its sliding surface.
mv-agent-lenses-col / -intentBoard columns and the drop-intention label shown while dragging.
mv-agent-lenses-edge / -edge-flowGraph edges (data-kind dep | sub, data-state waiting | flowing | done | idle) and the flowing dash of running work.
mv-agent-lenses-nowCalendar now-line.

CSS variables

NameDefaultDescription
--mv-agent-lenses-max-heightnoneCaps the panel height; content then scrolls inside (both axes).
--mv-agent-lenses-bgvar(--mv-bg)Background behind the lenses.
--mv-agent-lenses-radiusvar(--mv-radius-xl)Outer corner radius.

Accessibility

The lens switcher is a role="tablist" (roving focus, arrows switch lens) controlling a single role="tabpanel" that is re-labelled on every change; the change is announced with the agent count and pending actions. Cards are role="listitem" (board, inbox, calendar, graph) or role="treeitem" with aria-level, aria-setsize/posinset and aria-expanded (tasks). Each card's accessible name carries its name, status, progress, timing and relations (“waiting on …”, “subagent of …”); goal and question are its description. One tab stop (roving tabindex): arrows move spatially in every lens, Home/End jump to the ends, Enter/Space selects; in Tasks, Right/Left expand, collapse or move between parent and children. On the board, Alt+Left/Right moves the card to the neighbouring column and Shift+F10 (or the Move button) opens a menu listing each column with what the move means; pointer drag shows that meaning on every column; touch uses the menu so scrolling stays free. Quick replies are real buttons reachable with Tab from the focused card; Escape returns to the card. Status is always an icon shape plus a text label, never color alone. A polite live region announces moves, replies, status changes and collapse summaries. Reduced motion (OS or data-motion="reduce"): instant relayout, no spinner, flowing dash or indeterminate bar. Decorative scaffolding (columns, axis, edges) is aria-hidden.

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden