diff --git a/src/components/ha-selector/ha-selector-stt.ts b/src/components/ha-selector/ha-selector-stt.ts new file mode 100644 index 0000000000..75549077fd --- /dev/null +++ b/src/components/ha-selector/ha-selector-stt.ts @@ -0,0 +1,50 @@ +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { STTSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "../ha-stt-picker"; + +@customElement("ha-selector-stt") +export class HaSTTSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: STTSelector; + + @property() public value?: any; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + @property({ attribute: false }) public context?: { + language?: string; + }; + + protected render() { + return html``; + } + + static styles = css` + ha-stt-picker { + width: 100%; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-stt": HaSTTSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 7d4e456e60..8f4d5bb343 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -31,6 +31,7 @@ const LOAD_ELEMENTS = { object: () => import("./ha-selector-object"), select: () => import("./ha-selector-select"), state: () => import("./ha-selector-state"), + stt: () => import("./ha-selector-stt"), target: () => import("./ha-selector-target"), template: () => import("./ha-selector-template"), text: () => import("./ha-selector-text"), diff --git a/src/components/ha-stt-picker.ts b/src/components/ha-stt-picker.ts new file mode 100644 index 0000000000..bbaab72be4 --- /dev/null +++ b/src/components/ha-stt-picker.ts @@ -0,0 +1,105 @@ +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValueMap, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; +import { computeStateName } from "../common/entity/compute_state_name"; +import { listSTTEngines, STTEngine } from "../data/stt"; +import { HomeAssistant } from "../types"; +import "./ha-list-item"; +import "./ha-select"; +import type { HaSelect } from "./ha-select"; + +const DEFAULT = "default_engine_option"; + +@customElement("ha-stt-picker") +export class HaSTTPicker extends LitElement { + @property() public value?: string; + + @property() public label?: string; + + @property() public language?: string; + + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @state() _engines: STTEngine[] = []; + + protected render(): TemplateResult { + const value = this.value ?? DEFAULT; + return html` + + + ${this.hass!.localize("ui.components.stt-picker.default")} + + ${this._engines.map((engine) => { + const stateObj = this.hass!.states[engine.engine_id]; + return html` + ${stateObj ? computeStateName(stateObj) : engine.engine_id} + `; + })} + + `; + } + + protected willUpdate( + changedProperties: PropertyValueMap | Map + ): void { + super.willUpdate(changedProperties); + if (!this.hasUpdated || changedProperties.has("language")) { + listSTTEngines(this.hass, this.language).then((engines) => { + this._engines = engines.providers; + }); + } + } + + static get styles(): CSSResultGroup { + return css` + ha-select { + width: 100%; + } + `; + } + + private _changed(ev): void { + const target = ev.target as HaSelect; + if ( + !this.hass || + target.value === "" || + target.value === this.value || + (this.value === undefined && target.value === DEFAULT) + ) { + return; + } + this.value = target.value === DEFAULT ? undefined : target.value; + fireEvent(this, "value-changed", { value: this.value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-stt-picker": HaSTTPicker; + } +} diff --git a/src/data/selector.ts b/src/data/selector.ts index 1282323595..429e40d3dd 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -35,6 +35,7 @@ export type Selector = | StateSelector | StatisticSelector | StringSelector + | STTSelector | TargetSelector | TemplateSelector | ThemeSelector @@ -300,6 +301,10 @@ export interface StringSelector { } | null; } +export interface STTSelector { + stt: { language?: string } | null; +} + export interface TargetSelector { target: { entity?: EntitySelectorFilter | readonly EntitySelectorFilter[]; diff --git a/src/data/stt.ts b/src/data/stt.ts index c855e77757..7a5d9da961 100644 --- a/src/data/stt.ts +++ b/src/data/stt.ts @@ -1,3 +1,5 @@ +import { HomeAssistant } from "../types"; + export interface SpeechMetadata { language: string; format: "wav" | "ogg"; @@ -15,3 +17,17 @@ export interface SpeechMetadata { | 48000; channel: 1 | 2; } + +export interface STTEngine { + engine_id: string; + language_supported?: boolean; +} + +export const listSTTEngines = ( + hass: HomeAssistant, + language?: string +): Promise<{ providers: STTEngine[] }> => + hass.callWS({ + type: "stt/engine/list", + language, + }); diff --git a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts index 15861616e2..1cc48117d4 100644 --- a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts +++ b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts @@ -139,8 +139,9 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { name: "stt_engine", required: true, selector: { - text: {}, + stt: {}, }, + context: { language: "language" }, }, { name: "tts_engine", diff --git a/src/translations/en.json b/src/translations/en.json index 7f6adbabb4..2f3cbbe2bf 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -462,6 +462,7 @@ } } }, + "stt-picker": { "stt": "Speech to text", "default": "Default" }, "related-filter-menu": { "filter": "Filter", "filter_by_entity": "Filter by entity",