Exclusifbêta
Intent Composer <mv-intent-composer>
Un champ de rédaction pour les requêtes à une IA qui rend visibles, pendant la saisie, les trois parties d’une intention : le résultat attendu, les contraintes (budget, échéance, choses à éviter, lieu, quantité…) et la limite de délégation. Les passages détectés sont surlignés sur place, chaque partie avec son propre style de soulignement, puis s’envolent vers trois emplacements sous forme de puces modifiables et supprimables ; les emplacements vides proposent des pistes d’enrichissement en un clic et une jauge montre à quel point l’intention est complète. Une laisse de délégation à trois crans (Suggérer · Préparer · Agir) se règle d’après les verbes que vous employez, sauf si vous la choisissez vous-même, explicite ce que l’assistant a le droit de faire et transforme le bouton d’envoi : au cran Agir, l’exécution demande un appui maintenu. Analyseur interchangeable (synchrone ou asynchrone, par exemple un LLM) ; les heuristiques intégrées comprennent l’anglais et le français.
| Catégorie | Formulaires |
|---|---|
| Type | Web Component (<mv-intent-composer>) |
| Statut | bêta |
| Kit | Une IA vérifiable |
| Keywords | exclusive, ai, prompt, intent, composer, llm, agent, delegation, autonomy, prompt-augmentation, constraints, highlight, textarea, hold-to-confirm, form |
When to use
- Users write requests for an AI assistant and should see outcome, constraints and delegation level
- An agent may take real actions, so executing must be clearly bounded and confirmed by holding
- Prompts should be enriched by nudging users to add missing constraints like budget or deadline
Avoid when
Installation
node scripts/add.mjs intent-composer --out ./src/marvelousAgent IA avec le serveur MCP Marvelous UI : install_components({ slugs: ["intent-composer"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/focus.js, core/form.js, core/motion.js, core/observe.js, components/intent-composer/intent-composer.js, components/intent-composer/intent-composer.css.
Utilisation
Démarrage rapide, le balisage minimal qui fonctionne :
<mv-intent-composer name="intent">Plan my trip to Chicago next Tuesday, budget $400 max, no flights</mv-intent-composer>Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :
<div id="ic-demo" style="width:min(100%,54rem);margin-inline:auto">
<style>
#ic-demo .ic-title { display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:.25rem 1rem; margin:0 0 .75rem }
#ic-demo .ic-title h3 { margin:0; font-size:1.125rem; letter-spacing:-.01em }
#ic-demo .ic-title span { color:var(--mv-fg-muted); font-size:.8125rem }
#ic-demo .ic-card { padding:1.125rem 1.25rem 1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm) }
#ic-demo .ic-try { display:flex; align-items:center; flex-wrap:wrap; gap:.5rem; margin-top:.875rem }
#ic-demo .ic-try > span { color:var(--mv-fg-muted); font-size:.8125rem; margin-inline-end:.125rem }
#ic-demo .ic-log { min-height:1.25rem; margin:.75rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem; text-align:center }
#ic-demo .ic-log strong { color:var(--mv-fg); font-weight:600 }
#ic-demo details { margin-top:.5rem; font-size:.8125rem }
#ic-demo summary { cursor:pointer; color:var(--mv-fg-muted); width:max-content }
#ic-demo pre { max-height:14rem; overflow:auto; margin:.5rem 0 0; padding:.75rem 1rem; border-radius:var(--mv-radius-md); background:var(--mv-bg-muted); color:var(--mv-fg); font:.75rem/1.5 var(--mv-font-mono) }
@media (max-width:30rem) { #ic-demo .ic-card { padding:1rem .875rem } }
</style>
<div class="ic-title"><h3>Travel & work assistant</h3><span>Northwind Consulting · Business travel</span></div>
<div class="ic-card">
<mv-intent-composer id="ic-composer" name="intent" label="What should the assistant do?" hold="1400">Plan my trip to Chicago next Tuesday, budget $400 max, no flights, and book the hotel near Union Station</mv-intent-composer>
</div>
<div class="ic-try" role="group" aria-label="Example requests">
<span>Try:</span>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" data-example="Plan my trip to Chicago next Tuesday, budget $400 max, no flights, and book the hotel near Union Station">Chicago trip</button>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" data-example="Find me three gift ideas for my sister, under $50, delivered by Saturday">Gift ideas</button>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" data-example="Draft an email to the team to move Thursday's meeting, friendly tone, without mentioning the budget">Team email</button>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" data-example="Plan my Chicago trip">Underspecified</button>
</div>
<p class="ic-log" id="ic-log" role="status"></p>
<details>
<summary>Structured intent (JSON)</summary>
<pre id="ic-json"></pre>
</details>
<script type="module">
const c = document.getElementById("ic-composer");
await customElements.whenDefined("mv-intent-composer");
const out = document.getElementById("ic-json");
const log = document.getElementById("ic-log");
const show = () => {
const { text, ...rest } = c.intent;
out.textContent = JSON.stringify(rest, null, 2);
};
c.addEventListener("mv-intent-change", show);
show();
c.addEventListener("mv-intent-submit", (e) => {
const { delegation, constraints, outcome } = e.detail;
log.replaceChildren(
Object.assign(document.createElement("strong"), { textContent: `${delegation.label}` }),
` · “${outcome || "No outcome"}” with ${constraints.length} constraint${constraints.length === 1 ? "" : "s"} sent to the assistant.`,
);
});
for (const b of document.querySelectorAll("#ic-demo [data-example]")) {
b.addEventListener("click", () => {
c.resetDelegation();
c.value = b.dataset.example;
log.textContent = "";
});
}
</script>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
label | string | Your request | Visible label of the text area. |
placeholder | string | Describe the result you want, your constraints, and what the assistant may do on its own… | Placeholder of the text area. |
name | string | Form field name (the element is form-associated). | |
value | string | Initial request text. The element's text content is used when absent. | |
delegation | suggest | prepare | act | suggest | Level used when the text contains no delegation verb. A detected verb overrides it; a manual choice on the leash overrides both. |
hold | number (ms) | 1200 | Press-and-hold duration required to execute at the Act level (minimum 400). |
debounce | number (ms) | 280 | Pause in typing before the text is analysed again (highlights follow the edit immediately). |
rows | number | 3 | Minimum height in lines; the text area grows with its content. |
max-rows | number | 10 | Height in lines after which the text area scrolls. |
submit-as | json | text | json | Submitted form value: a JSON intent { text, outcome, constraints, delegation } or the plain text. |
disabled | boolean | Disables the field (also follows a disabled fieldset). | |
required | boolean | The request cannot be empty (form validation). | |
data-level | suggest | prepare | act | Reflected: effective delegation level, for styling. | |
data-analyzing | boolean | Reflected while an asynchronous parser is running. |
Properties
| Name | Type | Description |
|---|---|---|
parser | (text) => IntentResult | Promise<IntentResult> | Replaces the built-in heuristics (can be set before the element is defined). IntentResult: { outcome: string | { text, start?, end?, spans?: [start, end][], vague? }, constraints: { type, text, label?, start?, end? }[], delegation: "suggest" | "prepare" | "act" | { level, verbs?: { level, text, phrase?, start?, end? }[] }, spans?: { type: "outcome" | "constraint" | "delegation", kind?, start, end }[] }. Offsets are optional: without them there is no highlight or chip editing. Stale async results are dropped. |
value | string | Request text (read / write). |
intent | object | Read only: { text, outcome, constraints, delegation: { level, label, mode: "auto" | "manual", verbs }, completeness (0-3), complete }. |
level | "suggest" | "prepare" | "act" | Effective delegation level. Assigning it is a manual choice; assigning null returns to automatic. |
Methods
| Name | Description |
|---|---|
analyze() | Runs the parser now; resolves with the intent. |
insert(template) | Inserts text at the caret; a part wrapped in {braces} is selected afterwards so the user can type over it. |
resetDelegation() | Drops the manual choice: the text decides the level again. |
submit() | Submits programmatically (no hold required). Resolves to false if cancelled or empty. |
focus() | Focuses the text area. |
Events
| Name | Description |
|---|---|
mv-intent-change | After each analysis or delegation change. detail: the intent ({ text, outcome, constraints, delegation, completeness, complete }). |
mv-intent-submit | Cancelable. detail: { text, outcome, constraints, delegation }. When not cancelled and the element is inside a form, the form is submitted (requestSubmit). |
Content structure
| Name | Description |
|---|---|
(text) | Initial request as plain text content (rendered before the element upgrades, which also suits SSR). |
CSS classes
| Name | Description |
|---|---|
mv-intent-composer-mark[data-type] | Highlight behind the text: outcome (solid underline), constraint (dashed + tint), delegation (dotted + stronger tint), ignored verb (struck through). |
mv-intent-composer-slot / -chip / -hint-chip | The three slots, their chips (data-type, data-kind) and the augmentation prompts. |
mv-intent-composer-track / -stop / -thumb | Delegation leash (role=radiogroup). |
mv-intent-composer-submit[data-level] | Morphing submit button (data-holding, data-armed, data-state="sent"). |
mv-intent-composer-meter | Intent completeness (role=meter, 3 segments: full, hatched = partial, empty). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-intent-composer-outcome | var(--mv-accent) | Outcome color (highlight, slot, meter). |
--mv-intent-composer-constraint | var(--mv-info) | Constraints color. |
--mv-intent-composer-delegation | var(--mv-warning) | Delegation verbs color. |
--mv-intent-composer-act | var(--mv-warning) | Emphasized edge and hold ring at the Act level. |
--mv-intent-composer-font-size | var(--mv-text-base) | Text size of the request (16px avoids zoom on iOS). |
Accessibility
The highlight layer is aria-hidden; the text area is a native textarea with a visible label and its keyboard hint as description. Each slot is a labelled group with a labelled list; chips are real buttons (Enter or F2 edits in place, Delete removes a constraint, Escape cancels an edit), removals go through the editing pipeline so Ctrl+Z in the text restores them. Ignoring a delegation verb strikes it through (never color alone). A polite live region announces detected parts in batches (“Constraint detected: budget, $400 max”), level changes and the boundary sentence, without interrupting typing. The leash is a role=radiogroup with roving focus (arrows, Home, End) and a described boundary; its mode (auto / manual) is shown as text. The completeness indicator is a role=meter with a spoken summary. At Act, a pointer press-and-hold fills a progress ring; on keyboard, holding Enter or Space works too, and a short press arms a confirm step (Enter again to execute, Escape to cancel); assistive-technology clicks use the same confirm step. Ctrl/⌘ Enter submits from the text (or arms confirmation at Act). Status colors always come with an icon and a label. Reduced motion: no flying chips or label morph; the hold ring still shows progress. No animation loop runs except while holding.