ExklusivBeta

Context Rings <mv-context-rings>

Macht sichtbar und bearbeitbar, was ein KI-Assistent für die aktuelle Aufgabe „weiß“: drei konzentrische Ringe („This task“ in der Mitte, „Always“ für Vorlieben und Regeln, „Ambient“ für Streams: E-Mail, Kalender, Chat) plus eine Ablage „Out of context“. Jedes Element ist ein Chip (Quellen-Icon, Größe nach Token-Kosten), kollisionsfrei um seinen Ring verteilt; ziehe ihn mit einer Federbewegung auf einen anderen Ring oder verschiebe ihn per Tastatur. Eine dünne Anzeige um den äußeren Ring vergleicht die Tokens mit dem Budget: Wird es überschritten, werden die nicht angehefteten Ambient-Elemente mit der niedrigsten Priorität in Verdrängungsreihenfolge abgedunkelt und mit „will be ignored“ beschriftet. Neue Ambient-Elemente kreisen herein; auf kleinen Bildschirmen übernimmt eine Listenansicht.

KategorieDatenanzeige
TypWeb Component (<mv-context-rings>)
StatusBeta
KitKI, die du überprüfen kannst
Keywordsexclusive, ai, assistant, agent, context, context-window, memory, tokens, budget, gauge, drag-and-drop, transparency, user-control, rings, orbit

When to use

  • Users must see and edit which files, rules and streams an AI assistant uses for the current task
  • A token budget should show which low-priority context will be ignored when over the limit
  • Context items must be moved between task, always-on and ambient scopes, or excluded

Avoid when

  • The goal is to capture the outcome and constraints of a request as the user types → use Intent Composer instead
  • A plain token usage indicator is enough, without per-item control → use Progress instead
  • The context holds hundreds of items; rings get crowded and fall back to a list anyway

Installation

node scripts/add.mjs context-rings --out ./src/marvelous

KI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["context-rings"], 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/pointer.js, core/position.js, components/context-rings/context-rings.js, components/context-rings/context-rings.css.

Verwendung

Schnellstart mit dem kleinsten funktionierenden Markup:

<mv-context-rings budget="8000" task="Reply to Dana about the late delivery">
  <script type="application/json">[
    { "id": "mail", "ring": "local", "label": "Latest email from Dana", "source": "email", "tokens": 1800 },
    { "id": "tone", "ring": "global", "label": "Tone: warm, concise", "source": "style", "tokens": 120, "pinned": true },
    { "id": "thread", "ring": "ambient", "label": "#support channel", "source": "slack", "tokens": 7400 }
  ]</script>
</mv-context-rings>

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

<div id="cr-demo" style="width:min(100%,70rem);margin-inline:auto">
  <style>
    #cr-demo .cr-bar { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:.75rem 1rem; margin:0 0 .75rem }
    #cr-demo .cr-bar p { margin:0; color:var(--mv-fg-muted); font-size:.8125rem; max-width:40rem }
    #cr-demo .cr-controls { display:flex; align-items:center; flex-wrap:wrap; gap:.5rem }
    #cr-demo .cr-controls label { display:inline-flex; align-items:center; gap:.5rem; color:var(--mv-fg-muted); font-size:.8125rem }
    #cr-demo .cr-controls .mv-select { width:auto; min-width:7.5rem }
    #cr-demo .cr-log { display:flex; align-items:center; flex-wrap:wrap; gap:.5rem; margin:.75rem 0 0; color:var(--mv-fg-muted); font-size:.75rem; font-variant-numeric:tabular-nums }
    #cr-demo .cr-log code { font:500 .7rem/1 var(--mv-font-mono); padding:.2rem .4rem; border-radius:var(--mv-radius-sm); background:var(--mv-bg-muted); color:var(--mv-fg) }
  </style>

  <div class="cr-bar">
    <p>Drag a chip to another ring, or to “Out of context”. Keyboard: <kbd class="mv-kbd">Enter</kbd> opens the card, <kbd class="mv-kbd">1</kbd> <kbd class="mv-kbd">2</kbd> <kbd class="mv-kbd">3</kbd> move it, <kbd class="mv-kbd">P</kbd> pins it.</p>
    <div class="cr-controls">
      <label>Budget
        <select class="mv-select" id="cr-budget">
          <option value="24000">24k tokens</option>
          <option value="32000" selected>32k tokens</option>
          <option value="64000">64k tokens</option>
        </select>
      </label>
      <button class="mv-button" data-variant="outline" data-size="sm" type="button" id="cr-stream">Stream a new ambient item</button>
    </div>
  </div>

  <mv-context-rings id="cr-rings" budget="32000" task="Reply to Harper &amp; Lane about the late delivery"></mv-context-rings>

  <p class="cr-log"><code>mv-context-change</code><span id="cr-log">Context ready to send.</span></p>

  <script type="module">
    const rings = document.getElementById("cr-rings");
    const min = (m) => Date.now() - m * 60000;

    rings.items = [
      // This task
      { id: "mail-last", ring: "local", label: "Latest email from Dana Harper", source: "email", tokens: 1800, updated: min(38), excerpt: "Hi, we still haven't received the 40 Arlo tables ordered on September 2. Can you give us a firm delivery date? Without an answer by Friday we'll have to cancel." },
      { id: "contract", ring: "local", label: "Contract 2025.pdf", source: "pdf", sourceLabel: "PDF · Drive", tokens: 9600, updated: min(60 * 24 * 41), excerpt: "Section 7 (Lead times): for delays over 15 business days, the customer is entitled to a 5% discount on the affected order." },
      { id: "ticket", ring: "local", label: "Ticket #4821 · late order", source: "ticket", tokens: 2400, updated: min(95), pinned: true, excerpt: "Order HL-2291 held at the Denver warehouse: pallets waiting for the carrier (pickup slot 9/24)." },
      { id: "crm", ring: "local", label: "Account history", source: "crm", tokens: 1600, updated: min(60 * 5), excerpt: "Customer since 2019 · 6 orders · 1 dispute closed in 2023 (partial delivery)." },
      // Always
      { id: "tone", ring: "global", label: "Tone: warm, concise", source: "style", tokens: 120, pinned: true, priority: 2, excerpt: "Short sentences, no internal jargon, always offer a next step." },
      { id: "signature", ring: "global", label: "Signature", source: "profile", tokens: 180, priority: 2, excerpt: "Sam Okafor · Customer Care, Nomad Workshop · +1 (415) 555-0142" },
      { id: "glossary", ring: "global", label: "Product glossary", source: "glossary", tokens: 3200, updated: min(60 * 24 * 6), excerpt: "Arlo: folding oak table · Mist: stackable chair · HL-: prefix for Harper & Lane orders." },
      { id: "refunds", ring: "global", label: "Refund policy", source: "doc", tokens: 1900, updated: min(60 * 24 * 12), excerpt: "Goodwill credit up to 8% without manager approval." },
      // Ambient
      { id: "thread", ring: "ambient", label: "Harper & Lane thread", source: "email", tokens: 5400, priority: 2, pinned: true, updated: min(40), excerpt: "12 messages since September 2, including 3 follow-ups from the customer." },
      { id: "agenda", ring: "ambient", label: "This week's calendar", source: "calendar", tokens: 1200, updated: min(15), excerpt: "Thursday 10:00 AM: logistics sync with the Denver warehouse." },
      { id: "support", ring: "ambient", label: "#support channel", source: "slack", sourceLabel: "Slack · #support", tokens: 4600, priority: 0, updated: min(60 * 3), excerpt: "Leah: “The carrier confirms a Tuesday pickup for HL-2291.”" },
      { id: "recent", ring: "ambient", label: "Recent files", source: "file", tokens: 2600, priority: 0, updated: min(60 * 26), excerpt: "Inventory_Sept.xlsx, Delivery_schedule.pdf, Quote_Mist.docx" },
      { id: "notes", ring: "ambient", label: "Monday meeting notes", source: "note", tokens: 1800, updated: min(60 * 30), excerpt: "Carrier delays rising; warn B2B accounts over $20,000." },
      // Out of context
      { id: "random", ring: "excluded", label: "#random channel", source: "slack", sourceLabel: "Slack · #random", tokens: 4100, priority: 0, updated: min(20) },
      { id: "personal", ring: "excluded", label: "Personal calendar", source: "calendar", tokens: 900, updated: min(60 * 2) },
    ];

    const queue = [
      { id: "reply", label: "Reply from Dana Harper", source: "email", tokens: 900, priority: 2, excerpt: "“Thanks. A Tuesday delivery works for us if you confirm it in writing.”" },
      { id: "invite", label: "Invite: Harper & Lane sync", source: "calendar", tokens: 400, priority: 1, excerpt: "Thursday 2:30 PM · video call · organizer: Leah Martin" },
      { id: "leah", label: "Message from Leah", source: "slack", sourceLabel: "Slack · #support", tokens: 700, priority: 1, excerpt: "“Pickup confirmed Tuesday 8 AM, sending you the bill of lading.”" },
      { id: "invoice", label: "Invoice INV-2291.pdf", source: "pdf", tokens: 1500, priority: 0, excerpt: "Invoice HL-2291 · 40 × Arlo table · $12,960.00" },
    ];
    const stream = document.getElementById("cr-stream");
    stream.addEventListener("click", () => {
      const next = queue.shift();
      if (next) rings.add({ ...next, ring: "ambient", updated: Date.now() });
      if (!queue.length) { stream.disabled = true; stream.textContent = "No more new items"; }
    });

    document.getElementById("cr-budget").addEventListener("change", (e) => { rings.budget = Number(e.target.value); });

    const log = document.getElementById("cr-log");
    const k = (n) => `${(n / 1000).toLocaleString("en-US", { maximumFractionDigits: 1 })}k`;
    rings.addEventListener("mv-context-change", (e) => {
      const { used, budget, evicted } = e.detail;
      log.textContent = `${k(used)} / ${k(budget)} tokens · ${evicted.length ? `${evicted.length} ignored (${evicted.join(", ")})` : "nothing ignored"}`;
    });
  </script>
</div>

API

Attributes

NameTypDefaultDescription
budgetnumber (tokens)32000The assistant's token budget. When exceeded, unpinned ambient items are marked “will be ignored” (lowest priority first, then oldest, then heaviest) until the total fits again.
taskstringTask title shown as the heading (“Reply to Harper & Lane about the late delivery”). Also names the region.
viewrings | list(auto)Forces a view. Unset: rings, unless the component is narrower than list-below or too crowded for readable chips (list). The “List view” button writes this attribute.
list-belownumber (px)600Component width under which the automatic view switches to the list (ResizeObserver).
data-view / data-budgetrings | list · ok | near | overReflected by the component (effective view, budget state), usable from CSS.

Properties

NameTypDescription
items{ id, label, ring, tokens, source, sourceLabel?, pinned?, priority?, excerpt?, updated? }[]The model. ring: "local" | "global" | "ambient" | "excluded". source: email, calendar, chat, slack, file, pdf, profile, style, preference, doc, glossary, ticket, note, crm, history, web, memory (icon and label), or free text. priority: 0 low, 1 normal (default), 2 high. updated: Date, timestamp or ISO string. Reading returns copies. Can be set before the element is defined; a child <script type="application/json"> works too.
usednumberTokens sent (rings, not the tray), before eviction. Read-only.
evictedstring[]Ids that will be ignored, in eviction order. Read-only.

Methods

NameDescription
add(item, { animate? })Adds an item (or updates the one with the same id). An ambient item orbits in from outside the rings with a faint pulse.
move(id, ring)Moves an item to a ring or to "excluded". Returns false if it was already there.
pin(id, pinned?)Pins (or unpins, or toggles): a pinned item is never dropped automatically.
remove(id)Removes an item entirely.

Events

NameDescription
mv-context-changeAfter every change (drag and drop, keyboard, methods, budget). detail: { items, used, budget, evicted } (evicted = ignored ids, in order). Not fired when items is assigned.

Content structure

NameDescription
script[type="application/json"]Optional: array of items read on mount when items was not provided.

CSS classes

NameDescription
mv-context-rings-chip / -chip-bodyChip (button): position on the outer element, visuals and scale on the body. data-ring, data-weight (0-3, cost), data-pinned, data-evicted, data-fresh.
mv-context-rings-band / -tabSVG ellipses of the rings (data-ring) and the name tabs on their top edge.
mv-context-rings-gauge-track / -fill / -over / -tickBudget gauge: track, share within budget, overflow, budget mark.
mv-context-rings-tray“Out of context” tray (drop target).
mv-context-rings-list / -section / -rowList view: four labelled sections, one row per item.
mv-context-rings-popDetail card (role=dialog, top layer): source, excerpt, cost, last update, actions.

CSS variables

NameDefaultDescription
--mv-context-rings-accentvar(--mv-accent)Accent color: center ring, drop targets, gauge.
--mv-context-rings-warningvar(--mv-warning)Over-budget color (always paired with an icon and a label).
--mv-context-rings-local / -global / -ambientFill of each ring.
--mv-context-rings-bgvar(--mv-surface)Component background.

Accessibility

A region named by the task title. Each ring (and the tray) is a role="group" labelled with its item count and cost; chips are real buttons with roving focus (arrows, Home/End; Tab moves between rings), in clockwise order. Full alternative to drag and drop: Enter opens a card (role=dialog, focus trapped, Escape returns focus) with “Move to…”, “Pin” (aria-pressed) and “Exclude”; direct shortcuts 1, 2, 3 (rings), Delete or 0 (exclude), P (pin), exposed through aria-keyshortcuts and an aria-describedby hint. Each chip's accessible name gives source, cost, pinned state and eviction rank. A live region announces moves and budget state changes; the budget is also a role="meter" with aria-valuetext. Ring identity never relies on color: name tabs on the rings, solid vs dashed strokes; the over-budget state pairs an icon with text. “List view” (aria-pressed) presents the same model as four labelled lists, automatically below list-below. Reduced motion (prefers-reduced-motion or data-motion="reduce"): no FLIP, orbit or pulse. No animation loop: everything is event-driven (ResizeObserver, Web Animations).

Diese Seite wurde mit KI übersetzt. Übersetzungsfehler melden