Open assist from dashboard (#16829)

This commit is contained in:
Paul Bottein 2023-06-21 08:02:09 +02:00 committed by GitHub
parent 1cf24ffc8d
commit 1cb1bcf274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 14 deletions

View File

@ -152,6 +152,12 @@ export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info"; action: "more-info";
} }
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig { export interface NoActionConfig extends BaseActionConfig {
action: "none"; action: "none";
} }
@ -180,6 +186,7 @@ export type ActionConfig =
| NavigateActionConfig | NavigateActionConfig
| UrlActionConfig | UrlActionConfig
| MoreInfoActionConfig | MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig | NoActionConfig
| CustomActionConfig; | CustomActionConfig;

View File

@ -24,10 +24,10 @@ import { stopPropagation } from "../../common/dom/stop_propagation";
import "../../components/ha-button"; import "../../components/ha-button";
import "../../components/ha-button-menu"; import "../../components/ha-button-menu";
import "../../components/ha-dialog"; import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-list-item"; import "../../components/ha-list-item";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import "../../components/ha-dialog-header";
import type { HaTextField } from "../../components/ha-textfield"; import type { HaTextField } from "../../components/ha-textfield";
import { import {
AssistPipeline, AssistPipeline,
@ -41,6 +41,7 @@ import type { HomeAssistant } from "../../types";
import { AudioRecorder } from "../../util/audio-recorder"; import { AudioRecorder } from "../../util/audio-recorder";
import { documentationUrl } from "../../util/documentation-url"; import { documentationUrl } from "../../util/documentation-url";
import { showAlertDialog } from "../generic/show-dialog-box"; import { showAlertDialog } from "../generic/show-dialog-box";
import { VoiceCommandDialogParams } from "./show-ha-voice-command-dialog";
interface Message { interface Message {
who: string; who: string;
@ -82,7 +83,13 @@ export class HaVoiceCommandDialog extends LitElement {
private _stt_binary_handler_id?: number | null; private _stt_binary_handler_id?: number | null;
public async showDialog(): Promise<void> { private _pipelinePromise?: Promise<AssistPipeline>;
public async showDialog(params?: VoiceCommandDialogParams): Promise<void> {
if (params?.pipeline_id) {
this._pipelineId = params?.pipeline_id;
}
this._conversation = [ this._conversation = [
{ {
who: "hass", who: "hass",
@ -92,6 +99,11 @@ export class HaVoiceCommandDialog extends LitElement {
this._opened = true; this._opened = true;
await this.updateComplete; await this.updateComplete;
this._scrollMessagesBottom(); this._scrollMessagesBottom();
await this._pipelinePromise;
if (params?.start_listening && this._pipeline?.stt_engine) {
this._toggleListening();
}
} }
public async closeDialog(): Promise<void> { public async closeDialog(): Promise<void> {
@ -230,7 +242,7 @@ export class HaVoiceCommandDialog extends LitElement {
<div class="listening-icon"> <div class="listening-icon">
<ha-icon-button <ha-icon-button
.path=${mdiMicrophone} .path=${mdiMicrophone}
@click=${this._toggleListening} @click=${this._handleListeningButton}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.voice_command.start_listening" "ui.dialogs.voice_command.start_listening"
)} )}
@ -275,7 +287,8 @@ export class HaVoiceCommandDialog extends LitElement {
private async _getPipeline() { private async _getPipeline() {
try { try {
this._pipeline = await getAssistPipeline(this.hass, this._pipelineId); this._pipelinePromise = getAssistPipeline(this.hass, this._pipelineId);
this._pipeline = await this._pipelinePromise;
} catch (e: any) { } catch (e: any) {
if (e.code === "not_found") { if (e.code === "not_found") {
this._pipelineId = undefined; this._pipelineId = undefined;
@ -392,9 +405,13 @@ export class HaVoiceCommandDialog extends LitElement {
} }
} }
private _toggleListening(ev) { private _handleListeningButton(ev) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this._toggleListening();
}
private _toggleListening() {
const supportsMicrophone = AudioRecorder.isSupported; const supportsMicrophone = AudioRecorder.isSupported;
if (!supportsMicrophone) { if (!supportsMicrophone) {
this._showNotSupportedMessage(); this._showNotSupportedMessage();

View File

@ -3,19 +3,29 @@ import { HomeAssistant } from "../../types";
const loadVoiceCommandDialog = () => import("./ha-voice-command-dialog"); const loadVoiceCommandDialog = () => import("./ha-voice-command-dialog");
export interface VoiceCommandDialogParams {
pipeline_id?: string;
start_listening?: boolean;
}
export const showVoiceCommandDialog = ( export const showVoiceCommandDialog = (
element: HTMLElement, element: HTMLElement,
hass: HomeAssistant hass: HomeAssistant,
dialogParams?: VoiceCommandDialogParams
): void => { ): void => {
if (hass.auth.external?.config.hasAssist) { if (hass.auth.external?.config.hasAssist) {
hass.auth.external!.fireMessage({ hass.auth.external!.fireMessage({
type: "assist/show", type: "assist/show",
payload: {
pipeline_id: dialogParams?.pipeline_id,
start_listening: dialogParams?.start_listening,
},
}); });
return; return;
} }
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "ha-voice-command-dialog", dialogTag: "ha-voice-command-dialog",
dialogImport: loadVoiceCommandDialog, dialogImport: loadVoiceCommandDialog,
dialogParams: {}, dialogParams,
}); });
}; };

View File

@ -4,6 +4,7 @@ import { forwardHaptic } from "../../../data/haptics";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import { ActionConfig } from "../../../data/lovelace"; import { ActionConfig } from "../../../data/lovelace";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; 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 { HomeAssistant } from "../../../types";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { toggleEntity } from "./entity/toggle-entity"; import { toggleEntity } from "./entity/toggle-entity";
@ -155,6 +156,13 @@ export const handleAction = async (
forwardHaptic("light"); forwardHaptic("light");
break; break;
} }
case "assist": {
showVoiceCommandDialog(node, hass, {
start_listening: actionConfig.start_listening,
pipeline_id: actionConfig.pipeline_id,
});
break;
}
case "fire-dom-event": { case "fire-dom-event": {
fireEvent(node, "ll-custom", actionConfig); fireEvent(node, "ll-custom", actionConfig);
} }

View File

@ -3,6 +3,8 @@ import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation"; 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-help-tooltip";
import "../../../components/ha-navigation-picker"; import "../../../components/ha-navigation-picker";
import "../../../components/ha-service-control"; import "../../../components/ha-service-control";
@ -24,9 +26,31 @@ const DEFAULT_ACTIONS: UiAction[] = [
"navigate", "navigate",
"url", "url",
"call-service", "call-service",
"assist",
"none", "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") @customElement("hui-action-editor")
export class HuiActionEditor extends LitElement { export class HuiActionEditor extends LitElement {
@property() public config?: ActionConfig; @property() public config?: ActionConfig;
@ -101,7 +125,7 @@ export class HuiActionEditor extends LitElement {
? html` ? html`
<ha-help-tooltip .label=${this.tooltipText}></ha-help-tooltip> <ha-help-tooltip .label=${this.tooltipText}></ha-help-tooltip>
` `
: ""} : nothing}
</div> </div>
${this.config?.action === "navigate" ${this.config?.action === "navigate"
? html` ? html`
@ -114,7 +138,7 @@ export class HuiActionEditor extends LitElement {
@value-changed=${this._navigateValueChanged} @value-changed=${this._navigateValueChanged}
></ha-navigation-picker> ></ha-navigation-picker>
` `
: ""} : nothing}
${this.config?.action === "url" ${this.config?.action === "url"
? html` ? html`
<ha-textfield <ha-textfield
@ -126,7 +150,7 @@ export class HuiActionEditor extends LitElement {
@input=${this._valueChanged} @input=${this._valueChanged}
></ha-textfield> ></ha-textfield>
` `
: ""} : nothing}
${this.config?.action === "call-service" ${this.config?.action === "call-service"
? html` ? html`
<ha-service-control <ha-service-control
@ -137,7 +161,19 @@ export class HuiActionEditor extends LitElement {
@value-changed=${this._serviceValueChanged} @value-changed=${this._serviceValueChanged}
></ha-service-control> ></ha-service-control>
` `
: ""} : nothing}
${this.config?.action === "assist"
? html`
<ha-form
.hass=${this.hass}
.schema=${ASSIST_SCHEMA}
.data=${this.config}
.computeLabel=${this._computeFormLabel}
@value-changed=${this._formValueChanged}
>
</ha-form>
`
: nothing}
`; `;
} }
@ -182,7 +218,7 @@ export class HuiActionEditor extends LitElement {
return; return;
} }
const target = ev.target! as EditorTarget; const target = ev.target! as EditorTarget;
const value = ev.target.value; const value = ev.target.value ?? ev.target.checked;
if (this[`_${target.configValue}`] === value) { if (this[`_${target.configValue}`] === value) {
return; 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<typeof ASSIST_SCHEMA>) {
return this.hass?.localize(
`ui.panel.lovelace.editor.action-editor.${schema.name}`
);
}
private _serviceValueChanged(ev: CustomEvent) { private _serviceValueChanged(ev: CustomEvent) {
ev.stopPropagation(); ev.stopPropagation();
const value = { const value = {
@ -240,17 +291,25 @@ export class HuiActionEditor extends LitElement {
width: 100%; width: 100%;
} }
ha-service-control, ha-service-control,
ha-navigation-picker { ha-navigation-picker,
ha-form {
display: block; display: block;
} }
ha-textfield, ha-textfield,
ha-service-control, ha-service-control,
ha-navigation-picker { ha-navigation-picker,
ha-form {
margin-top: 8px; margin-top: 8px;
} }
ha-service-control { ha-service-control {
--service-control-padding: 0; --service-control-padding: 0;
} }
ha-formfield {
display: flex;
height: 56px;
align-items: center;
--mdc-typography-body2-font-size: 1em;
}
`; `;
} }
} }

View File

@ -51,6 +51,12 @@ const actionConfigStructNavigate = object({
confirmation: optional(actionConfigStructConfirmation), confirmation: optional(actionConfigStructConfirmation),
}); });
const actionConfigStructAssist = type({
action: literal("assist"),
pipeline_id: optional(string()),
start_listening: optional(boolean()),
});
const actionConfigStructCustom = type({ const actionConfigStructCustom = type({
action: literal("fire-dom-event"), action: literal("fire-dom-event"),
}); });
@ -63,6 +69,7 @@ export const actionConfigStructType = object({
"call-service", "call-service",
"url", "url",
"navigate", "navigate",
"assist",
]), ]),
confirmation: optional(actionConfigStructConfirmation), confirmation: optional(actionConfigStructConfirmation),
}); });
@ -82,6 +89,9 @@ export const actionConfigStruct = dynamic<any>((value) => {
case "url": { case "url": {
return actionConfigStructUrl; return actionConfigStructUrl;
} }
case "assist": {
return actionConfigStructAssist;
}
} }
} }

View File

@ -4417,12 +4417,15 @@
"action-editor": { "action-editor": {
"navigation_path": "Navigation Path", "navigation_path": "Navigation Path",
"url_path": "URL Path", "url_path": "URL Path",
"start_listening": "Start listening",
"pipeline_id": "Assistant",
"actions": { "actions": {
"default_action": "Default Action", "default_action": "Default Action",
"call-service": "Call Service", "call-service": "Call Service",
"more-info": "More Info", "more-info": "More Info",
"toggle": "Toggle", "toggle": "Toggle",
"navigate": "Navigate", "navigate": "Navigate",
"assist": "Assist",
"url": "URL", "url": "URL",
"none": "No Action" "none": "No Action"
} }