diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 432cb6167b..091ced7c7b 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -152,6 +152,12 @@ export interface MoreInfoActionConfig extends BaseActionConfig { action: "more-info"; } +export interface AssistActionConfig extends BaseActionConfig { + action: "assist"; + pipeline_id?: string; + start_listening?: boolean; +} + export interface NoActionConfig extends BaseActionConfig { action: "none"; } @@ -180,6 +186,7 @@ export type ActionConfig = | NavigateActionConfig | UrlActionConfig | MoreInfoActionConfig + | AssistActionConfig | NoActionConfig | CustomActionConfig; diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts index e0ec5db5de..b3a83bfce5 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -24,10 +24,10 @@ import { stopPropagation } from "../../common/dom/stop_propagation"; import "../../components/ha-button"; import "../../components/ha-button-menu"; import "../../components/ha-dialog"; +import "../../components/ha-dialog-header"; import "../../components/ha-icon-button"; import "../../components/ha-list-item"; import "../../components/ha-textfield"; -import "../../components/ha-dialog-header"; import type { HaTextField } from "../../components/ha-textfield"; import { AssistPipeline, @@ -41,6 +41,7 @@ import type { HomeAssistant } from "../../types"; import { AudioRecorder } from "../../util/audio-recorder"; import { documentationUrl } from "../../util/documentation-url"; import { showAlertDialog } from "../generic/show-dialog-box"; +import { VoiceCommandDialogParams } from "./show-ha-voice-command-dialog"; interface Message { who: string; @@ -82,7 +83,13 @@ export class HaVoiceCommandDialog extends LitElement { private _stt_binary_handler_id?: number | null; - public async showDialog(): Promise { + private _pipelinePromise?: Promise; + + public async showDialog(params?: VoiceCommandDialogParams): Promise { + if (params?.pipeline_id) { + this._pipelineId = params?.pipeline_id; + } + this._conversation = [ { who: "hass", @@ -92,6 +99,11 @@ export class HaVoiceCommandDialog extends LitElement { this._opened = true; await this.updateComplete; this._scrollMessagesBottom(); + + await this._pipelinePromise; + if (params?.start_listening && this._pipeline?.stt_engine) { + this._toggleListening(); + } } public async closeDialog(): Promise { @@ -230,7 +242,7 @@ export class HaVoiceCommandDialog extends LitElement {
import("./ha-voice-command-dialog"); +export interface VoiceCommandDialogParams { + pipeline_id?: string; + start_listening?: boolean; +} + export const showVoiceCommandDialog = ( element: HTMLElement, - hass: HomeAssistant + hass: HomeAssistant, + dialogParams?: VoiceCommandDialogParams ): void => { if (hass.auth.external?.config.hasAssist) { hass.auth.external!.fireMessage({ type: "assist/show", + payload: { + pipeline_id: dialogParams?.pipeline_id, + start_listening: dialogParams?.start_listening, + }, }); return; } fireEvent(element, "show-dialog", { dialogTag: "ha-voice-command-dialog", dialogImport: loadVoiceCommandDialog, - dialogParams: {}, + dialogParams, }); }; diff --git a/src/panels/lovelace/common/handle-action.ts b/src/panels/lovelace/common/handle-action.ts index 7ea895d723..f782d6ad53 100644 --- a/src/panels/lovelace/common/handle-action.ts +++ b/src/panels/lovelace/common/handle-action.ts @@ -4,6 +4,7 @@ import { forwardHaptic } from "../../../data/haptics"; import { domainToName } from "../../../data/integration"; import { ActionConfig } from "../../../data/lovelace"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { showVoiceCommandDialog } from "../../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; import { toggleEntity } from "./entity/toggle-entity"; @@ -155,6 +156,13 @@ export const handleAction = async ( forwardHaptic("light"); break; } + case "assist": { + showVoiceCommandDialog(node, hass, { + start_listening: actionConfig.start_listening, + pipeline_id: actionConfig.pipeline_id, + }); + break; + } case "fire-dom-event": { fireEvent(node, "ll-custom", actionConfig); } diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index 4077ae9a09..20d5e0f597 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -3,6 +3,8 @@ import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; import { stopPropagation } from "../../../common/dom/stop_propagation"; +import "../../../components/ha-assist-pipeline-picker"; +import { HaFormSchema, SchemaUnion } from "../../../components/ha-form/types"; import "../../../components/ha-help-tooltip"; import "../../../components/ha-navigation-picker"; import "../../../components/ha-service-control"; @@ -24,9 +26,31 @@ const DEFAULT_ACTIONS: UiAction[] = [ "navigate", "url", "call-service", + "assist", "none", ]; +const ASSIST_SCHEMA = [ + { + type: "grid", + name: "", + schema: [ + { + name: "pipeline_id", + selector: { + assist_pipeline: {}, + }, + }, + { + name: "start_listening", + selector: { + boolean: {}, + }, + }, + ], + }, +] as const satisfies readonly HaFormSchema[]; + @customElement("hui-action-editor") export class HuiActionEditor extends LitElement { @property() public config?: ActionConfig; @@ -101,7 +125,7 @@ export class HuiActionEditor extends LitElement { ? html` ` - : ""} + : nothing}
${this.config?.action === "navigate" ? html` @@ -114,7 +138,7 @@ export class HuiActionEditor extends LitElement { @value-changed=${this._navigateValueChanged} > ` - : ""} + : nothing} ${this.config?.action === "url" ? html` ` - : ""} + : nothing} ${this.config?.action === "call-service" ? html` ` - : ""} + : nothing} + ${this.config?.action === "assist" + ? html` + + + ` + : nothing} `; } @@ -182,7 +218,7 @@ export class HuiActionEditor extends LitElement { return; } const target = ev.target! as EditorTarget; - const value = ev.target.value; + const value = ev.target.value ?? ev.target.checked; if (this[`_${target.configValue}`] === value) { return; } @@ -193,6 +229,21 @@ export class HuiActionEditor extends LitElement { } } + private _formValueChanged(ev): void { + ev.stopPropagation(); + const value = ev.detail.value; + + fireEvent(this, "value-changed", { + value: value, + }); + } + + private _computeFormLabel(schema: SchemaUnion) { + return this.hass?.localize( + `ui.panel.lovelace.editor.action-editor.${schema.name}` + ); + } + private _serviceValueChanged(ev: CustomEvent) { ev.stopPropagation(); const value = { @@ -240,17 +291,25 @@ export class HuiActionEditor extends LitElement { width: 100%; } ha-service-control, - ha-navigation-picker { + ha-navigation-picker, + ha-form { display: block; } ha-textfield, ha-service-control, - ha-navigation-picker { + ha-navigation-picker, + ha-form { margin-top: 8px; } ha-service-control { --service-control-padding: 0; } + ha-formfield { + display: flex; + height: 56px; + align-items: center; + --mdc-typography-body2-font-size: 1em; + } `; } } diff --git a/src/panels/lovelace/editor/structs/action-struct.ts b/src/panels/lovelace/editor/structs/action-struct.ts index e8a6ec82be..512e45aa84 100644 --- a/src/panels/lovelace/editor/structs/action-struct.ts +++ b/src/panels/lovelace/editor/structs/action-struct.ts @@ -51,6 +51,12 @@ const actionConfigStructNavigate = object({ confirmation: optional(actionConfigStructConfirmation), }); +const actionConfigStructAssist = type({ + action: literal("assist"), + pipeline_id: optional(string()), + start_listening: optional(boolean()), +}); + const actionConfigStructCustom = type({ action: literal("fire-dom-event"), }); @@ -63,6 +69,7 @@ export const actionConfigStructType = object({ "call-service", "url", "navigate", + "assist", ]), confirmation: optional(actionConfigStructConfirmation), }); @@ -82,6 +89,9 @@ export const actionConfigStruct = dynamic((value) => { case "url": { return actionConfigStructUrl; } + case "assist": { + return actionConfigStructAssist; + } } } diff --git a/src/translations/en.json b/src/translations/en.json index a82e7b34af..5f28ae09ad 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4417,12 +4417,15 @@ "action-editor": { "navigation_path": "Navigation Path", "url_path": "URL Path", + "start_listening": "Start listening", + "pipeline_id": "Assistant", "actions": { "default_action": "Default Action", "call-service": "Call Service", "more-info": "More Info", "toggle": "Toggle", "navigate": "Navigate", + "assist": "Assist", "url": "URL", "none": "No Action" }