Exclusivo

Certainty <mv-certainty>

Texto cuyos pasajes llevan cada uno un nivel de confianza (respuesta de IA, resumen, informe), presentado con calma para que los lectores calibren su confianza sin un muro de advertencias: tres niveles (Verified sin marca, Likely con un subrayado punteado, Uncertain con una onda dibujada a mano cuya amplitud crece con la duda) y una tinta que se aclara a medida que baja la confianza.

Cómo funciona

Texto cuyos pasajes llevan cada uno un nivel de confianza (respuesta de IA, resumen, informe), presentado con calma para que los lectores calibren su confianza sin un muro de advertencias: tres niveles (Verified sin marca, Likely con un subrayado punteado, Uncertain con una onda dibujada a mano cuya amplitud crece con la duda) y una tinta que se aclara a medida que baja la confianza. Al pasar el cursor o enfocar un pasaje se abre una lupa (porcentaje, nivel, indicador, motivo, fuentes, "Flag") y el resto se atenúa ligeramente; un slider "Only show what's at least X% reliable" desenfoca o contrae los pasajes por debajo del umbral; una franja resume la fiabilidad global. Funciona con marcado existente o con segmentos añadidos a medida que llega un stream.

CategoríaTexto animado
TipoWeb Component (<mv-certainty>)
Estadoestable
KitIA que puedes verificar
Keywordsexclusive, text, ai, llm, confidence, uncertainty, trust, calibration, citations, sources, hallucination, streaming, annotation, threshold

When to use

  • An AI answer, summary or report must show per-passage confidence so readers can calibrate trust
  • Low-confidence passages should be dimmed or collapsed below a reliability threshold
  • Streamed generated text needs a reason, sources and a flag action on each passage

Avoid when

  • A full chat interface with composer and streaming replies is needed → use AI Chat instead
  • No real confidence scores are available; made-up levels would mislead readers

Instalación

node scripts/add.mjs certainty --out ./src/marvelous

Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["certainty"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/i18n.js, core/position.js, components/certainty/certainty.js, components/certainty/certainty.css.

Uso

Inicio rápido, el marcado mínimo que funciona:

<mv-certainty summary>
  <p><span data-confidence="0.93">Heat pumps work efficiently in most climates</span>, and <span data-confidence="0.34" data-reason="Based on 2023 contractor quotes.">a ducted system costs about $14,000 installed</span>.</p>
</mv-certainty>

Marcado de referencia: parte de él y personalízalo con atributos, data-* y variables CSS:

<div id="ct-demo" style="width:min(100%,46rem);margin-inline:auto">
  <style>
    #ct-demo { display:grid; gap:2.25rem }
    #ct-demo .ct-card { display:grid; gap:1rem; padding:1.25rem 1.375rem 1.375rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-xs) }
    #ct-demo .ct-ask { justify-self:end; max-width:85%; margin:0; padding:.55rem .9rem; border-radius:1rem 1rem .25rem 1rem; background:var(--mv-bg-muted); font-size:.9375rem; line-height:1.45 }
    #ct-demo .ct-who { display:flex; align-items:center; gap:.5rem; margin:0; color:var(--mv-fg-muted); font-size:.75rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase }
    #ct-demo .ct-who::before { content:""; width:.5rem; height:.5rem; border-radius:50%; background:var(--mv-accent) }
    #ct-demo mv-certainty { font-size:1rem }
    #ct-demo .ct-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding-top:.25rem; color:var(--mv-fg-muted); font-size:.8125rem }
    #ct-demo .ct-foot .mv-choice { font-size:.8125rem }
    #ct-demo .ct-head { display:flex; align-items:baseline; justify-content:space-between; gap:1rem; flex-wrap:wrap; margin:0 0 -.25rem }
    #ct-demo .ct-head h3 { margin:0; font-size:1rem; letter-spacing:-.01em }
    #ct-demo .ct-head span { color:var(--mv-fg-muted); font-size:.8125rem }
    #ct-demo .ct-flagged { font-variant-numeric:tabular-nums }
  </style>

  <!-- 1 · Annotated markup: summary strip + threshold -->
  <section class="ct-card" aria-label="Annotated answer">
    <p class="ct-ask">Is a heat pump worth it for my house built in 1975?</p>
    <p class="ct-who">Assistant</p>
    <mv-certainty id="ct-answer" summary filter mode="dim" label="Answer reliability">
      <p>For a house built in 1975, <span data-confidence="0.93" data-reason="Standard retrofit for forced-air homes of that era." data-sources="Home Energy Handbook · Heat pump systems|https://energy.example.com/heat-pumps">an air-source heat pump can usually replace both the furnace and the central air conditioner</span>. <span data-confidence="0.66" data-reason="Inferred from the construction year; only an energy audit will confirm it.">Homes from that period often have thin wall insulation and leaky ductwork</span>.</p>
      <p><span data-confidence="0.95" data-reason="Well established, confirmed by two official sources." data-sources="Home Energy Handbook · Heat pump systems|https://energy.example.com/heat-pumps;Efficiency Label Program · Heat pumps|https://label.example.com/heat-pumps">Modern heat pumps work efficiently in most climates, and cold-climate models keep heating well below freezing</span>. <span data-confidence="0.34" data-reason="Based on 2023 contractor quotes; current regional prices were not found.">A ducted whole-house system would cost about $14,000 to $20,000 installed</span>.</p>
      <p><span data-confidence="0.9" data-reason="Certification criteria are published and stable." data-sources="Efficiency Label Program · Heat pumps|https://label.example.com/heat-pumps">Efficiency-certified models use noticeably less electricity than standard ones</span>, and <span data-confidence="0.97" data-reason="Basic physics of heat transfer, explained by the handbook." data-sources="Home Energy Handbook · Heat pump systems|https://energy.example.com/heat-pumps">they move heat rather than generate it, which is why they can deliver more energy than they consume</span>. <span data-confidence="0.58" data-reason="Depends on your utility and local program.">Many utilities offer rebates for qualifying installations</span>, <span data-confidence="0.18" data-reason="Federal credit rules changed recently; no current source confirms this.">and these would still stack with federal tax credits</span>.</p>
      <p><span data-confidence="0.72" data-reason="Common in many states, not everywhere." data-sources="Incentive Finder · State programs|https://incentives.example.com/states">Your state or city may add its own incentive</span>: <span data-confidence="0.41" data-reason="Generalized from a handful of programs only.">it often takes the form of a $500 to $1,500 rebate</span>. <span data-confidence="0.88" data-sources="Home Energy Handbook · Energy audits|https://energy.example.com/audits">The best first step is a professional home energy audit</span>, which will confirm current prices and programs.</p>
    </mv-certainty>
    <div class="ct-foot">
      <label class="mv-choice"><input type="checkbox" role="switch" class="mv-switch" id="ct-collapse"><span class="mv-choice-text"><span class="mv-choice-title">Collapse hidden passages</span></span></label>
      <span class="ct-flagged" id="ct-flagged" aria-live="polite">Hover or tab to a passage to see where it comes from.</span>
    </div>
  </section>

  <!-- 2 · Streaming: segments appended as they arrive -->
  <section class="ct-card" aria-label="Streamed answer">
    <div class="ct-head"><h3>Live stream</h3><span>segments added one by one with appendSegments()</span></div>
    <p class="ct-ask">How far away is the Moon?</p>
    <mv-certainty id="ct-stream" summary label="Reliability of the answer in progress"></mv-certainty>
    <div class="ct-foot">
      <span id="ct-stream-state">Generating…</span>
      <button class="mv-button" data-variant="outline" data-size="sm" type="button" id="ct-regen">Regenerate</button>
    </div>
  </section>

  <script type="module">
    await customElements.whenDefined("mv-certainty");
    const answer = document.getElementById("ct-answer");
    const flagged = document.getElementById("ct-flagged");
    document.getElementById("ct-collapse").addEventListener("change", (e) => { answer.mode = e.target.checked ? "collapse" : "dim"; });
    answer.addEventListener("mv-flag", () => {
      const n = answer.stats.flagged;
      flagged.textContent = n ? `${n} passage${n > 1 ? "s" : ""} flagged for review` : "No passages flagged";
    });

    // Streaming: two alternative answers, each segment arrives on its own.
    const atlas = "Sky Atlas · The Moon|https://atlas.example.com/moon";
    const takes = [
      [
        { text: "The Moon orbits Earth at an average distance of " },
        { text: "about 384,400 km (238,900 miles)", confidence: 0.95, reason: "Standard value in astronomy references.", sources: atlas },
        { text: ", a figure measured to within centimeters " },
        { text: "by bouncing lasers off reflectors left on its surface", confidence: 0.9, reason: "Several sources agree.", sources: atlas },
        { text: ". " },
        { text: "Its orbit is slightly elliptical, so the distance varies by about 42,000 km", confidence: 0.74, reason: "Rounded; the closest and farthest points change from one orbit to the next." },
        { text: ", and " },
        { text: "total solar eclipses will reportedly end in about 600 million years", confidence: 0.38, reason: "Estimate quoted in popular articles; the underlying model is not cited." },
        { text: "." },
      ],
      [
        { text: "Light from the Moon reaches us in " },
        { text: "about 1.3 seconds", confidence: 0.93, reason: "Average distance divided by the speed of light.", sources: atlas },
        { text: ", so " },
        { text: "a radio signal needs roughly 2.6 seconds for the round trip", confidence: 0.88, sources: atlas },
        { text: ".\n\n" },
        { text: "The Moon drifts away from Earth by about 3.8 cm a year", confidence: 0.69, reason: "Current rate; it has changed over geological time." },
        { text: ", and " },
        { text: "it may once have orbited only about 25,000 km away", confidence: 0.34, reason: "Early-history models disagree widely." },
        { text: "." },
      ],
    ];
    const stream = document.getElementById("ct-stream");
    const state = document.getElementById("ct-stream-state");
    let run = 0;
    let take = 0;
    async function play() {
      const id = ++run;
      stream.segments = [];
      stream.setAttribute("aria-busy", "true");
      state.textContent = "Generating…";
      for (const seg of takes[take]) {
        await new Promise((r) => setTimeout(r, seg.confidence === undefined ? 160 : 520));
        if (id !== run || !stream.isConnected) return;
        stream.appendSegments(seg);
      }
      stream.setAttribute("aria-busy", "false");
      state.textContent = `Done · ${stream.stats.count} annotated passages`;
    }
    document.getElementById("ct-regen").addEventListener("click", () => { take = (take + 1) % takes.length; play(); });
    play();
  </script>
</div>

API

Attributes

NameTipoPredeterminadoDescripción
summarybooleanShows the "Overall reliability" strip: mean confidence weighted by passage length, a segmented bar of tier proportions and a legend of the three underline styles.
filterbooleanShows the "Only show what's at least X% reliable" threshold slider and the count of passages below the threshold.
thresholdnumber (0..1)0Confidence threshold: passages below it recede (0 = show everything). Also accepts 0..100.
modedim | collapsedimdim: passages below the threshold stay in place, blurred and faded (stable layout; hover or focus to read them). collapse: they leave the flow and are replaced by a "… uncertain passage hidden" pill that reveals them on click.
tiersstring0.5,0.8Tier boundaries: below the first = Uncertain, from the second up = Verified, in between = Likely.
labelstringText reliabilityAccessible name of the strip.
aria-busy"true"While streaming: shows a subtle typing caret at the end of the generated text.
data-confidencenumber (0..1 or %)On a child element (span…): makes it an annotated passage. Passages added or changed later are picked up.
data-reasonstringOn a passage: short justification shown in the lens ("single source, dated 2023").
data-sourcesstringOn a passage: "Title|https://…;Title 2|https://…". Only http(s) URLs become links, opened in a new tab.

Properties

NameTipoDescripción
segments{ text, confidence?, reason?, sources? }[]Text rendered with text nodes only (never HTML). Without confidence: neutral text; a blank line (\n\n) starts a paragraph. sources: an array of { title, url } or a string in the data-sources format. Assigning replaces all generated content.
statsobjectRead-only: { count, confidence (weighted mean), threshold, hidden, flagged, tiers: { verified | likely | uncertain: { label, count, share } }, passages: [...] }.
stringsPartial<Record<string, string | { one, other }>>Overrides for the default text (en-US): verified, likely, uncertain (tier labels, lower-cased inside sentences), verifiedMark, likelyMark, uncertainMark (underline style read in the legend), describe and describeReason (passage description, {pct}, {tier}, {reason}), sourceCount ({n}, plural forms as { one, other }), noSources, describeBelow, describeFlagged, stub and stubLabel (collapsed passage button, {tier}, {pct}), overall (score label), legend (legend name), legendMark ({mark}), reliable (slider value, {pct}), filterLabel, allClear, showingAll, belowCount ({n}, plural), announceAllClear, announceShowingAll, announceBelow (threshold announcements, {pct}, {n}), lensPct ({pct}), source, sources ({n}), noSourcesCited, newTab (link suffix for screen readers), lensBelow ({pct}), lensPosition ({n}, {total}), flag, flagged. App-wide translations: setStrings() from core/i18n.js, under the key "certainty". Can be set before the element is defined.

Methods

NameDescripción
appendSegments(segment | segment[])Appends one or more segments at the end (streamed LLM output) with a fade-in; returns the created passages. The native append() is untouched.
setThreshold(value)Sets the threshold (0..1 or 0..100); same as the threshold attribute.
inspect(index) / close()Opens the lens on passage number index / closes it.

Events

NameDescripción
mv-thresholdThe threshold changed. detail: { threshold, hidden, count }.
mv-inspectThe lens opens on a passage. detail: { index, text, confidence, tier, label, reason, sources, below, flagged, element }.
mv-flag"Flag" (toggle). detail: the passage data, including flagged. The passage gets data-flagged.

Content structure

NameDescripción
(content)Any text (paragraphs, lists…) containing data-confidence elements. With segments, text is rendered into a [data-certainty-body] container (provided or created).

CSS classes

NameDescripción
mv-certainty-passageAnnotated passage (data-tier = verified | likely | uncertain, data-below under the threshold, data-active under the lens).
mv-certainty-bar / -segments / -legend / -rangeSummary strip, segmented bar, legend and threshold slider.
mv-certainty-lensLens (popover): tier, percentage, gauge, reason, sources, .mv-certainty-flag button.
mv-certainty-stubPill standing in for a collapsed passage (collapse mode).

CSS variables

NamePredeterminadoDescripción
--mv-certainty-huevar(--mv-accent)Single hue for the whole scale (underlines, bar, gauge), stepped light to dark.
--mv-certainty-inkvar(--mv-fg)Ink of verified text.
--mv-certainty-ink-likely / -ink-uncertain84% / 68%Ink density of likely / uncertain passages.
--mv-certainty-leading1.8Line height (leaves room for the waves).
--mv-certainty-bar-bgSummary strip background.

Accessibility

Confidence is never conveyed by color alone: ink density + underline style (none / dotted / wavy) + a text label (lens, legend, description). Every passage is focusable (Tab) and described via aria-describedby ("42% confidence, uncertain: reason. 2 sources."); on keyboard focus the lens opens without stealing focus, Enter or Space moves into it (links, "Flag"), Tab out of it resumes reading right after the passage, Shift+Tab returns to the passage, Escape closes it and restores focus. The lens is a non-modal role="dialog" (manual popover), also closed by an outside click; on touch, a tap opens or closes it. The threshold slider is a labelled native range input (aria-valuetext "60% reliable"); the number of passages below the threshold is announced politely after a pause (role=status). Documented choice for hidden passages: in dim mode they stay in the accessibility tree with "Below the threshold" in their description; in collapse mode they leave it (display: none) and the pill, a real button, announces them ("Hidden uncertain passage, 32% confidence. Show"). prefers-reduced-motion and [data-motion="reduce"]: instant transitions, no fade-in or blinking caret. Forced colors: CanvasText dotted and native wavy underlines, solid slider track and lens gauge. Known limits: the component rewrites the inside of each annotated passage (its content moves into a .mv-certainty-text span and a hidden stub button is appended), so with a framework that renders the children, prefer the segments property; a data-confidence element nested in another one is ignored; segments are rendered as plain text only (no links or formatting inside a passage).

Esta página se tradujo con IA. Informar de un problema de traducción