Exclusifbêta
Certainty <mv-certainty>
Texte dont chaque passage porte un niveau de confiance (réponse d’IA, résumé, rapport), rendu sobrement pour que les lecteurs puissent calibrer leur confiance sans crouler sous les avertissements : trois niveaux (Vérifié sans marque, Probable avec un soulignement pointillé, Incertain avec une vague tracée à la main dont l’amplitude grandit avec le doute) et une encre qui s’éclaircit à mesure que la confiance baisse. Survoler un passage ou lui donner le focus ouvre une loupe (pourcentage, niveau, jauge, raison, sources, « Signaler ») et atténue légèrement le reste ; un curseur « N’afficher que ce qui est fiable à au moins X % » floute ou replie les passages sous le seuil ; une bande résume la fiabilité globale. Fonctionne avec un balisage existant ou avec des segments ajoutés à mesure qu’un flux arrive.
| Catégorie | Texte animé |
|---|---|
| Type | Web Component (<mv-certainty>) |
| Statut | bêta |
| Kit | Une IA vérifiable |
| Keywords | exclusive, 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
Installation
node scripts/add.mjs certainty --out ./src/marvelousAgent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["certainty"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/position.js, components/certainty/certainty.js, components/certainty/certainty.css.
Utilisation
Démarrage rapide, le balisage minimal qui fonctionne :
<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>Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les 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="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems">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="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems;ENERGY STAR · Heat pumps|https://www.energystar.gov/">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="ENERGY STAR · Heat pumps|https://www.energystar.gov/">ENERGY STAR 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 DOE." data-sources="U.S. Department of Energy · Heat pump systems|https://www.energy.gov/energysaver/heat-pump-systems">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="DSIRE · Database of State Incentives|https://www.dsireusa.org/">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="U.S. Department of Energy · Home energy audits|https://www.energy.gov/energysaver/home-energy-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 append()</span></div>
<p class="ct-ask">When did the Musée d'Orsay open?</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 orsay = "Musée d'Orsay · Official website|https://www.musee-orsay.fr/en";
const takes = [
[
{ text: "The Musée d'Orsay opened its doors " },
{ text: "in December 1986", confidence: 0.94, reason: "Date given by the museum's official website.", sources: orsay },
{ text: ", inside the former Gare d'Orsay, " },
{ text: "a railway station built for the 1900 World's Fair", confidence: 0.9, reason: "Several sources agree.", sources: orsay },
{ text: ". " },
{ text: "Its interior was designed by Gae Aulenti", confidence: 0.74, reason: "She designed the interior alongside ACT Architecture, who led the conversion." },
{ text: ", and " },
{ text: "the museum reportedly welcomes nearly 4 million visitors a year", confidence: 0.38, reason: "Attendance varies from year to year; the figure is undated." },
{ text: "." },
],
[
{ text: "Housed in a station " },
{ text: "designed by Victor Laloux", confidence: 0.87, reason: "Architect named by every source consulted.", sources: orsay },
{ text: ", the Musée d'Orsay " },
{ text: "was inaugurated in late 1986", confidence: 0.92, sources: orsay },
{ text: ".\n\n" },
{ text: "Its collection mostly spans 1848 to 1914", confidence: 0.69, reason: "Usual boundaries, sometimes presented differently." },
{ text: ", and " },
{ text: "attendance reportedly neared 3.9 million visitors in 2023", confidence: 0.46, reason: "Press figure, not checked against the annual report." },
{ 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.append(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
| Name | Type | Default | Description |
|---|---|---|---|
summary | boolean | Shows the "Overall reliability" strip: mean confidence weighted by passage length, a segmented bar of tier proportions and a legend of the three underline styles. | |
filter | boolean | Shows the "Only show what's at least X% reliable" threshold slider and the count of passages below the threshold. | |
threshold | number (0..1) | 0 | Confidence threshold: passages below it recede (0 = show everything). Also accepts 0..100. |
mode | dim | collapse | dim | dim: 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. |
tiers | string | 0.5,0.8 | Tier boundaries: below the first = Uncertain, from the second up = Verified, in between = Likely. |
label | string | Text reliability | Accessible name of the strip. |
aria-busy | "true" | While streaming: shows a subtle typing caret at the end of the generated text. | |
data-confidence | number (0..1 or %) | On a child element (span…): makes it an annotated passage. Passages added or changed later are picked up. | |
data-reason | string | On a passage: short justification shown in the lens ("single source, dated 2023"). | |
data-sources | string | On a passage: "Title|https://…;Title 2|https://…". Only http(s) URLs become links, opened in a new tab. |
Properties
| Name | Type | Description |
|---|---|---|
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. |
stats | object | Read-only: { count, confidence (weighted mean), threshold, hidden, flagged, tiers: { verified | likely | uncertain: { label, count, share } }, passages: [...] }. |
Methods
| Name | Description |
|---|---|
append(segment | segment[]) | Appends one or more segments at the end (streamed LLM output) with a fade-in; returns the created passages. Given DOM nodes, behaves like Element#append(). |
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
| Name | Description |
|---|---|
mv-threshold | The threshold changed. detail: { threshold, hidden, count }. |
mv-inspect | The 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
| Name | Description |
|---|---|
(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
| Name | Description |
|---|---|
mv-certainty-passage | Annotated passage (data-tier = verified | likely | uncertain, data-below under the threshold, data-active under the lens). |
mv-certainty-bar / -segments / -legend / -range | Summary strip, segmented bar, legend and threshold slider. |
mv-certainty-lens | Lens (popover): tier, percentage, gauge, reason, sources, .mv-certainty-flag button. |
mv-certainty-stub | Pill standing in for a collapsed passage (collapse mode). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-certainty-hue | var(--mv-accent) | Single hue for the whole scale (underlines, bar, gauge), stepped light to dark. |
--mv-certainty-ink | var(--mv-fg) | Ink of verified text. |
--mv-certainty-ink-likely / -ink-uncertain | 84% / 68% | Ink density of likely / uncertain passages. |
--mv-certainty-leading | 1.8 | Line height (leaves room for the waves). |
--mv-certainty-bar-bg | Summary 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.