import { mdiChevronLeft, mdiClose, mdiMenuDown } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { formatLanguageCode } from "../../common/language/format_language"; import "../../components/chips/ha-assist-chip"; import "../../components/ha-dialog"; import { getLanguageOptions } from "../../components/ha-language-picker"; import "../../components/ha-md-button-menu"; import type { AssistSatelliteConfiguration } from "../../data/assist_satellite"; import { fetchAssistSatelliteConfiguration } from "../../data/assist_satellite"; import { getLanguageScores } from "../../data/conversation"; import { UNAVAILABLE } from "../../data/entity"; import type { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import type { VoiceAssistantSetupDialogParams } from "./show-voice-assistant-setup-dialog"; import "./voice-assistant-setup-step-area"; import "./voice-assistant-setup-step-change-wake-word"; import "./voice-assistant-setup-step-check"; import "./voice-assistant-setup-step-cloud"; import "./voice-assistant-setup-step-local"; import "./voice-assistant-setup-step-pipeline"; import "./voice-assistant-setup-step-success"; import "./voice-assistant-setup-step-update"; import "./voice-assistant-setup-step-wake-word"; export const enum STEP { INIT, UPDATE, CHECK, WAKEWORD, AREA, PIPELINE, SUCCESS, CLOUD, LOCAL, CHANGE_WAKEWORD, } @customElement("ha-voice-assistant-setup-dialog") export class HaVoiceAssistantSetupDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @state() private _params?: VoiceAssistantSetupDialogParams; @state() private _step: STEP = STEP.INIT; @state() private _assistConfiguration?: AssistSatelliteConfiguration; @state() private _error?: string; @state() private _language?: string; @state() private _languages: string[] = []; @state() private _localOption?: string; private _previousSteps: STEP[] = []; private _nextStep?: STEP; public async showDialog( params: VoiceAssistantSetupDialogParams ): Promise { this._params = params; await this._fetchAssistConfiguration(); this._step = STEP.UPDATE; } public async closeDialog(): Promise { this.renderRoot.querySelector("ha-dialog")?.close(); } protected willUpdate(changedProps) { if (changedProps.has("_step") && this._step === STEP.PIPELINE) { this._getLanguages(); } } private _dialogClosed() { this._params = undefined; this._assistConfiguration = undefined; this._previousSteps = []; this._nextStep = undefined; this._step = STEP.INIT; this._language = undefined; this._languages = []; this._localOption = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } private _deviceEntities = memoizeOne( ( deviceId: string, entities: HomeAssistant["entities"] ): EntityRegistryDisplayEntry[] => Object.values(entities).filter((entity) => entity.device_id === deviceId) ); private _findDomainEntityId = memoizeOne( ( deviceId: string, entities: HomeAssistant["entities"], domain: string ): string | undefined => { const deviceEntities = this._deviceEntities(deviceId, entities); return deviceEntities.find( (ent) => computeDomain(ent.entity_id) === domain )?.entity_id; } ); protected render() { if (!this._params) { return nothing; } const assistSatelliteEntityId = this._findDomainEntityId( this._params.deviceId, this.hass.entities, "assist_satellite" ); const assistEntityState = assistSatelliteEntityId ? this.hass.states[assistSatelliteEntityId] : undefined; return html` ${this._step === STEP.LOCAL ? nothing : this._previousSteps.length ? html`` : this._step !== STEP.UPDATE ? html`` : nothing} ${this._step === STEP.WAKEWORD || this._step === STEP.AREA ? html`${this.hass.localize( "ui.panel.config.voice_assistants.satellite_wizard.skip" )}` : this._step === STEP.PIPELINE ? this._language ? html` ${getLanguageOptions( this._languages, false, false, this.hass.locale ).map( (lang) => html` ${lang.primary} ` )} ` : nothing : nothing}
${this._step === STEP.UPDATE ? html`` : this._error ? html`${this._error}` : assistEntityState?.state === UNAVAILABLE ? html`${this.hass.localize( "ui.panel.config.voice_assistants.satellite_wizard.not_available" )}` : this._step === STEP.CHECK ? html`` : this._step === STEP.WAKEWORD ? html`` : this._step === STEP.CHANGE_WAKEWORD ? html` ` : this._step === STEP.AREA ? html` ` : this._step === STEP.PIPELINE ? html`` : this._step === STEP.CLOUD ? html`` : this._step === STEP.LOCAL ? html`` : this._step === STEP.SUCCESS ? html`` : nothing}
`; } private async _getLanguages() { if (this._languages.length) { return; } const scores = await getLanguageScores(this.hass); this._languages = Object.entries(scores.languages) .filter( ([_lang, score]) => score.cloud > 0 || score.full_local > 0 || score.focused_local > 0 ) .map(([lang, _score]) => lang); this._language = scores.preferred_language && this._languages.includes(scores.preferred_language) ? scores.preferred_language : undefined; } private async _fetchAssistConfiguration() { try { this._assistConfiguration = await fetchAssistSatelliteConfiguration( this.hass, this._findDomainEntityId( this._params!.deviceId, this.hass.entities, "assist_satellite" )! ); } catch (err: any) { this._error = err.message; } } private _handlePickLanguage(ev) { if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") return; this._language = ev.target.value; } private _languageChanged(ev: CustomEvent) { if (!ev.detail.value) { return; } this._language = ev.detail.value; } private _goToPreviousStep() { if (!this._previousSteps.length) { return; } this._step = this._previousSteps.pop()!; } private _goToNextStep(ev?: CustomEvent) { if (ev?.detail?.updateConfig) { this._fetchAssistConfiguration(); } if (ev?.detail?.nextStep) { this._nextStep = ev.detail.nextStep; } if (!ev?.detail?.noPrevious) { this._previousSteps.push(this._step); } if (ev?.detail?.step) { this._step = ev.detail.step; if (ev.detail.step === STEP.LOCAL) { this._localOption = ev.detail.option; } } else if (this._nextStep) { this._step = this._nextStep; this._nextStep = undefined; } else { this._step += 1; } } static get styles(): CSSResultGroup { return [ haStyleDialog, css` ha-dialog { --dialog-content-padding: 0; } @media all and (min-width: 450px) and (min-height: 500px) { ha-dialog { --mdc-dialog-min-width: 560px; --mdc-dialog-max-width: 560px; --mdc-dialog-min-width: min(560px, 95vw); --mdc-dialog-max-width: min(560px, 95vw); } } ha-dialog-header { height: 56px; } @media all and (max-width: 450px), all and (max-height: 500px) { .content { height: calc(100vh - 56px); } } .skip-btn { margin-top: 6px; } ha-alert { margin: 24px; display: block; } ha-md-button-menu { height: 48px; display: flex; align-items: center; margin-right: 12px; margin-inline-end: 12px; margin-inline-start: initial; } `, ]; } } declare global { interface HTMLElementTagNameMap { "ha-voice-assistant-setup-dialog": HaVoiceAssistantSetupDialog; } interface HASSDomEvents { "next-step": | { step?: STEP; updateConfig?: boolean; noPrevious?: boolean; nextStep?: STEP; option?: string; } | undefined; "prev-step": undefined; "language-changed": { value: string }; } }