Thinking Orb <mv-thinking-orb>
Canvas 기반 AI 어시스턴트 오브입니다. 상태(대기, 듣는 중, 위성이 궤도를 도는 생각 중, 말하는 중, 성공, 오류)에 따라 휘고 소용돌이치며 재질이 바뀌는 광택 있는 덩어리로, 상태 사이를 부드럽게 전환합니다.
작동 방식
Canvas AI 어시스턴트 오브: 광택 있는 덩어리가 상태(대기, 듣기, 주위를 도는 위성과 함께하는 생각 중, 말하기, 성공, 오류)에 따라 휘고, 소용돌이치고, 재질을 바꾸며, 상태 사이를 부드럽게 전환합니다. 음성에 반응합니다: 수동 레벨, MediaStream 또는 사용자가 허용한 마이크.
| 카테고리 | 피드백 |
|---|---|
| 유형 | Web Component (<mv-thinking-orb>) |
| 상태 | 안정 |
| Keywords | ai, assistant, orb, voice, microphone, audio, loader, agent, canvas, status |
When to use
- A voice or AI assistant needs a visual presence that reflects idle, listening, thinking and speaking states
- The assistant should react to microphone input or an audio stream in real time
- A single orb doubles as a mic toggle button for a voice interface
Avoid when
- A lightweight inline thinking indicator is enough, such as in a chat message → use Pixel Loader instead
- Several instances appear on one page or the target is low-end mobile; each orb runs its own canvas
- A generic loading state with no assistant persona is needed → use Spinner instead
설치
node scripts/add.mjs thinking-orb --out ./src/marvelousMarvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["thinking-orb"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, components/thinking-orb/thinking-orb.js, components/thinking-orb/thinking-orb.css.
사용법
기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:
<div style="display:grid;justify-items:center;gap:1.1rem;width:100%;max-width:560px">
<mv-thinking-orb id="mv-thinking-orb-demo" state="idle" label="Assistant" mic style="--mv-thinking-orb-size:12rem"></mv-thinking-orb>
<p id="mv-thinking-orb-demo-caption" style="margin:0;min-height:1.4em;font-size:.9rem;color:var(--mv-fg-muted)">Tap the orb to talk, or pick a state.</p>
<div role="group" aria-label="Assistant state" style="display:flex;flex-wrap:wrap;justify-content:center;gap:.4rem">
<button class="mv-button" data-size="sm" data-variant="outline" data-state="idle" aria-pressed="true">Idle</button>
<button class="mv-button" data-size="sm" data-variant="outline" data-state="listening" aria-pressed="false">Listening</button>
<button class="mv-button" data-size="sm" data-variant="outline" data-state="thinking" aria-pressed="false">Thinking</button>
<button class="mv-button" data-size="sm" data-variant="outline" data-state="speaking" aria-pressed="false">Speaking</button>
<button class="mv-button" data-size="sm" data-variant="outline" data-state="success" aria-pressed="false">Success</button>
<button class="mv-button" data-size="sm" data-variant="outline" data-state="error" aria-pressed="false">Error</button>
</div>
<div style="display:flex;gap:1.5rem;align-items:center;margin-top:.4rem">
<mv-thinking-orb state="thinking" label="Search" style="--mv-thinking-orb-size:3.5rem"></mv-thinking-orb>
<mv-thinking-orb state="speaking" label="Reading" colors="oklch(0.78 0.16 160), oklch(0.72 0.14 200), oklch(0.85 0.12 110)" style="--mv-thinking-orb-size:3.5rem"></mv-thinking-orb>
<mv-thinking-orb state="listening" label="Dictation" colors="oklch(0.72 0.19 30), oklch(0.8 0.15 70), oklch(0.66 0.2 350)" style="--mv-thinking-orb-size:3.5rem"></mv-thinking-orb>
</div>
</div>
<style>
[aria-label="Assistant state"] .mv-button[aria-pressed="true"] { background: var(--mv-accent-subtle); border-color: var(--mv-accent); color: var(--mv-accent-fg); }
@media (forced-colors: active) { [aria-label="Assistant state"] .mv-button[aria-pressed="true"] { forced-color-adjust: none; background: Highlight; border-color: Highlight; color: HighlightText; } }
</style>
<script type="module">
const orb = document.getElementById("mv-thinking-orb-demo");
const caption = document.getElementById("mv-thinking-orb-demo-caption");
const texts = {
idle: "Tap the orb to talk, or pick a state.",
listening: "I’m listening…",
thinking: "Looking up flights to Lisbon in October…",
speaking: "Three nonstop flights on Oct 12, from $89.",
success: "Booking confirmed.",
error: "Couldn’t reach the server. Try again.",
};
const buttons = [...document.querySelectorAll("[data-state]")].filter((b) => b.closest("[role=group]"));
const sync = () => {
for (const b of buttons) b.setAttribute("aria-pressed", String(b.dataset.state === orb.state));
caption.textContent = texts[orb.state] ?? "";
};
for (const b of buttons) b.addEventListener("click", () => { orb.state = b.dataset.state; sync(); });
orb.addEventListener("mv-mic", (e) => {
if (e.detail.error) caption.textContent = "Microphone unavailable (" + e.detail.error + ").";
else sync();
});
</script>API
Attributes
| Name | 유형 | Default | Description |
|---|---|---|---|
state | idle | listening | thinking | speaking | success | error | idle | Displayed state; parameters (size, warp, swirl, glow, satellites, hue) are smoothly interpolated. |
label | string | Assistant | Name announced with the state (“Assistant: thinking”). |
colors | list of CSS colors | --mv-thinking-orb-1/2/3 | Three material colors (tokens accepted, follow the theme). |
level | number 0-1 | Manual voice level (listening / speaking). With no level or mic, “speaking” simulates a voice. | |
mic | boolean | The orb becomes a toggle button that opens the microphone on click (getUserMedia) and switches to listening. | |
speed | number | 1 | Overall animation speed. |
Properties
| Name | 유형 | Description |
|---|---|---|
level | number | null | Voice level 0-1 (update every frame if needed, without an attribute). |
stream | MediaStream | null | External audio stream to visualize (not stopped by the orb). |
listeningMic | boolean | True while the orb’s microphone is open. |
Methods
| Name | Description |
|---|---|
listenMic() | Opens the microphone (call it from a user gesture); returns a Promise<MediaStream>. |
stopMic() | Stops the orb’s microphone. |
Events
| Name | Description |
|---|---|
mv-mic | detail: { active, stream?, error? }; microphone opened, closed or denied. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-thinking-orb-size | 10rem | Size (the glow and satellites fit inside the box). |
--mv-thinking-orb-1 / -2 / -3 | accent, sky blue, pink | Default material colors. |
Accessibility
Decorative canvas (aria-hidden); the state is exposed as visually hidden text in a polite live region (“Assistant: listening”). With mic: toggle button with a constant name and aria-pressed, visible focus. The microphone is never opened without a user gesture. Reduced motion: a representative still frame for each state. Pauses when off-screen.