import { type HTMLAttributes, type ReactNode } from 'react'; import { type AiCreditsInfo, type AiCreditsNoticeProps } from '../ai-credits-notice'; import { type AiConsentBannerHeadingLevel, type AiConsentGate } from '../ai-permission-notice'; import { type KbdNamedKey } from '../kbd'; /** Where the host's voice session is. */ export type VoiceControlStatus = 'idle' | 'starting' | 'listening' | 'paused' | 'error'; /** How a message reads: the tone decides the colour and the icon. */ export type VoiceControlTone = 'info' | 'success' | 'warning' | 'error'; /** Generic failures the kit words itself, translated. */ export type VoiceControlError = 'microphoneDenied' | 'connectionLost' | 'startFailed' | 'recordingFailed' | 'unsupported'; /** A host message, already translated by the host. */ export interface VoiceControlMessage { tone: VoiceControlTone; text: string; /** * Change it to announce the same words again (the live region only speaks * when the message changes), e.g. a counter for "Not recognised" twice. */ id?: string | number; } /** What the speech service heard. `partial` may still change. */ export interface VoiceControlTranscript { final?: string; partial?: string; } /** One dictation language the host's speech service accepts. */ export interface VoiceControlLanguage { value: string; label: string; } /** One match the user can pick by voice ("option 1") or by hand. */ export interface VoiceControlCandidate { id: string; label: string; detail?: string; } /** One voice command shown in the commands disclosure. */ export interface VoiceControlCommand { phrase: ReactNode; description: ReactNode; } /** What a press asks the host to do: start (or resume), or stop (or cancel * a start). */ export type VoiceControlToggle = 'start' | 'stop'; /** Inline in the page flow, or a floating pill that opens the panel. */ export type VoiceControlLayout = 'inline' | 'floating'; /** The viewport corner of the floating pill. Logical: mirrors in RTL. */ export type VoiceControlPosition = 'bottom-end' | 'bottom-start'; /** * The practice's AI credits, as the platform keeps them (a yearly allowance * that expires, then welcome and bought credits that do not), plus whether * they have run out. See `AiCreditsInfo` for the allowance, the dates and * the next steps the host can offer. */ export interface VoiceControlAiCredits extends AiCreditsInfo { /** * Out of AI credits. Every way of starting shows the out-of-credits * notice in the panel instead of calling `onToggle`; stopping is never * gated. Set while a session runs or is paused, the panel stops it * (`onToggle('stop')`, once) and shows the notice on its own. */ exhausted?: boolean; } /** Curated imperative handle for agent / external automation. */ export interface VoiceControlPanelHandle { getStatus: () => VoiceControlStatus; /** Ask the host to start (`onToggle('start')`) unless already starting * or listening, or disabled. Gated by `aiConsent` like a press. */ start: () => void; /** Ask the host to stop, cancel a start or end a paused session * (`onToggle('stop')`); a no-op when idle or stopped. Never gated. */ stop: () => void; getCandidateIds: () => string[]; pickCandidate: (id: string) => void; isCommandsOpen: () => boolean; openCommands: () => void; closeCommands: () => void; /** Floating layout: whether the panel above the pill is open. Always * false inline. */ isPanelOpen: () => boolean; /** Floating layout: open the panel (no focus move). A no-op inline. */ openPanel: () => void; /** Floating layout: close the panel; listening carries on. A no-op inline. */ closePanel: () => void; /** Whether `aiCredits.exhausted` is gating every start. */ isCreditsExhausted: () => boolean; /** Whether the out-of-credits notice is showing. */ isCreditsNoticeOpen: () => boolean; /** Close the out-of-credits notice, back to the controls. */ closeCreditsNotice: () => void; } export interface VoiceControlPanelProps extends Omit, 'title' | 'children' | 'onToggle'> { /** Where the host's voice session is. Drives the button, dot and status. */ status: VoiceControlStatus; /** * The start/stop button, the pill's voice segment, the shortcut and the * agent call this, saying which way: `'start'` when idle, paused or * stopped, `'stop'` when starting or listening (stop, or cancel a start). * Every stop is passed on, so act on the argument rather than flip your * own state. A host that ignores the argument keeps working. */ onToggle: (next: VoiceControlToggle) => void; /** * Stops a session from starting: disables the button, the shortcut, the * settings and the matches. A session already starting or listening can * still be stopped, so the microphone is never trapped on. */ disabled?: boolean; /** Panel title. Default: the kit's "Voice control". */ title?: ReactNode; /** Shows a "Beta" badge beside the title. */ beta?: boolean; /** A host notice under the title, e.g. "Free during the beta." */ betaNotice?: ReactNode; /** The current dictation language (a `languages` value). */ language?: string; /** Dictation languages. The select shows only with two or more. */ languages?: VoiceControlLanguage[]; onLanguageChange?: (value: string) => void; /** Extra settings beside the language, e.g. the periodontal route select. */ extraControls?: ReactNode; /** The bold "where am I" line: the field or elements voice acts on. */ target?: ReactNode; /** What the speech service heard: `final` reads normal, `partial` muted. */ transcript?: VoiceControlTranscript; /** A host message. The tone sets colour and icon; not every message is red. */ message?: VoiceControlMessage | null; /** A generic failure, worded and translated by the kit (tone: error). */ error?: VoiceControlError | null; /** Several matches to choose from, numbered as they are spoken. */ candidates?: VoiceControlCandidate[]; /** Host label above the matches, e.g. "Several matches: say 'option 1'…". */ candidatesLabel?: string; onCandidatePick?: (id: string) => void; /** Voice commands listed in the disclosure under the panel. */ commands?: VoiceControlCommand[]; /** Start with the commands disclosure open. */ defaultCommandsOpen?: boolean; /** * Keyboard shortcut the kit binds document-wide to toggle, e.g. `"Alt+V"` * (default). Modifiers: Alt, Control, Shift, Meta. `false` turns it off. */ shortcut?: string | false; /** Opaque instance id: `data-component-id` and agent registration. */ id?: string; /** * `inline` (default): the panel sits in the page flow, above the chart. * `floating`: the RichTextEditor's AI pill (mic | panel) fixed to a * viewport corner; the second segment opens this panel above it, non-modal. */ layout?: VoiceControlLayout; /** * Floating only: the viewport corner. Default `bottom-end`; `bottom-start` * moves the pill away from another floating widget (a chat bubble). */ position?: VoiceControlPosition; /** Floating only: whether the panel above the pill is open (controlled). */ open?: boolean; /** Floating only: the open state when uncontrolled. Default `false`. */ defaultOpen?: boolean; /** * Floating only: called when the panel opens or closes, by the pill, the * close button, Escape, or on its own for an error or a set of matches. */ onOpenChange?: (open: boolean) => void; /** * Floating only: the pill's sparkle, as on the RichTextEditor's pill. It * plays once when the pill first shows and again when a success message * arrives (a write landed); never in a loop while listening. Default `true`. */ aiSparkle?: boolean; /** * Patient AI-consent gate: voice sends patient audio to the host's speech * service. Same contract as RichTextEditor's `aiConsent`. While present and * not granted, every way of starting shows the `AiConsentBanner` instead of * calling `onToggle`; stopping is never gated. Omit for ungated behaviour. */ aiConsent?: AiConsentGate; /** * Heading level for the `AiConsentBanner` title and the out-of-credits * notice title, which show in the same place. Default 5. */ aiConsentHeadingLevel?: AiConsentBannerHeadingLevel; /** * The practice's AI credits. With `exhausted`, every way of starting (the * Start button, the pill's mic, the shortcut, the agent's * `start_listening`) shows the out-of-credits notice instead of calling * `onToggle`, after the consent check; stopping is never gated. Running * low (80% used, or `runningLow`) and `expiring` show as quiet lines in * the panel and never block. Omit for ungated behaviour. */ aiCredits?: VoiceControlAiCredits; /** * Floating only: the `AiCreditsNotice` hover card on the pill's mic, as on * the RichTextEditor's AI triggers. It never takes the click: the mic * still starts, stops or opens a gate exactly as without it. */ aiCreditsNotice?: AiCreditsNoticeProps; } type ShortcutModifier = Extract; interface ParsedShortcut { modifiers: Set; key: string; /** `aria-keyshortcuts` value, e.g. `Alt+V`. */ aria: string; /** Keys for the kit Kbd, e.g. `['Alt', 'V']`. */ keys: string[]; } /** Parse `"Alt+V"`-style shortcuts. Returns null for an unusable string. */ export declare function parseVoiceShortcut(shortcut: string): ParsedShortcut | null; /** * VoiceControlPanel: the kit UI for a host-driven voice control session. * The host keeps the microphone and the speech service; the panel shows the * state, what was heard, where the voice acts, messages and matches, and * calls `onToggle` from its button and its keyboard shortcut. */ export declare const VoiceControlPanel: import("react").ForwardRefExoticComponent>; export {}; //# sourceMappingURL=voice-control-panel.d.ts.map