独家

Context Rings <mv-context-rings>

让 AI 助手在当前任务中“知道”的内容可见且可编辑:三个同心圆环(中心为“This task”,偏好和规则为“Always”,信息流为“Ambient”:邮件、日历、聊天),外加一个“Out of context”托盘。

工作原理

让 AI 助手在当前任务中“知道”的内容可见且可编辑:三个同心环(中心是 “This task”,“Always” 用于偏好和规则,“Ambient” 用于信息流:邮件、日历、聊天),外加一个 “Out of context” 托盘。每个条目都是一个标签(显示来源图标,大小取决于 token 开销),沿所在的环无碰撞地分布;可以带弹簧效果拖到另一个环,也可以用键盘移动。外环上的一圈细量表把 token 用量与预算对比:超出时,优先级最低且未固定的 ambient 条目会按淘汰顺序变暗,并显示 “will be ignored” 标签。新的 ambient 条目会环绕飞入;在小屏幕上改用列表视图。

分类数据展示
类型Web Component(<mv-context-rings>)
状态稳定版
套件可核查的 AI
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

安装

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

使用 Marvelous UI MCP 服务器的 AI 智能体:install_components({ slugs: ["context-rings"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

复制的文件(含依赖):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。

用法

快速开始,最小可运行标记:

<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": "chat", "tokens": 7400 }
  ]</script>
</mv-context-rings>

标准标记,可在此基础上通过属性、data-* 和 CSS 变量进行定制:

<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 · Shared 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, Fernpeak 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: "chat", sourceLabel: "Team chat · #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: "chat", sourceLabel: "Team chat · #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: "chat", sourceLabel: "Team chat · #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

Name类型DefaultDescription
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

Name类型Description
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.
removeItem(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). Known limits: an item is kept or ignored as a whole (no partial truncation of a long document); dragging a chip on touch starts right away and blocks page scrolling while the finger is on the chip (no long press; the card and list view stay available); the rings are ellipses rather than circles so wide chips fit, which is a deliberate visual choice.

本页面由 AI 翻译。报告翻译问题