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.
| Kategorie | Datenanzeige |
|---|---|
| Typ | Web Component (<mv-agent-lenses>) |
| Status | Beta |
| Kit | KI, die du überprüfen kannst |
| Keywords | exclusive, 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/marvelousKI-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
| Name | Typ | Default | Description |
|---|---|---|---|
lens | board | inbox | tasks | calendar | graph | board | Active lens. Changing it animates every card to its new place (instant under reduced motion). |
label | string | Agents | Accessible name of the region (and of the lens tab list). |
Properties
| Name | Typ | Description |
|---|---|---|
agents | Agent[] | 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">. |
lens | string | Reflects the lens attribute. |
collapsed | string[] | Ids of collapsed parents in the Tasks lens. |
selected | string | null | Id of the selected agent (set without firing mv-agent-select). |
Methods
| Name | Description |
|---|---|
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
| Name | Description |
|---|---|
mv-lens-change | The lens changed. detail: { lens, previous }. |
mv-agent-select | Click, Enter or Space on a card. detail: { id, agent }. |
mv-agent-move | Cancelable. 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-reply | A 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
| Name | Description |
|---|---|
<script type="application/json"> | Optional: initial agents as JSON, for server-rendered pages. |
CSS classes
| Name | Description |
|---|---|
mv-agent-lenses-card | The persistent card; data-density = card | item | row | bar | node, data-status, data-ask, data-selected. |
mv-agent-lenses-tab / -indicator | Lens switcher (role tablist) and its sliding surface. |
mv-agent-lenses-col / -intent | Board columns and the drop-intention label shown while dragging. |
mv-agent-lenses-edge / -edge-flow | Graph edges (data-kind dep | sub, data-state waiting | flowing | done | idle) and the flowing dash of running work. |
mv-agent-lenses-now | Calendar now-line. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-agent-lenses-max-height | none | Caps the panel height; content then scrolls inside (both axes). |
--mv-agent-lenses-bg | var(--mv-bg) | Background behind the lenses. |
--mv-agent-lenses-radius | var(--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.