Voice setup feedback (#22134)

* Voice setup feedback

* Update voice-assistant-setup-step-check.ts
This commit is contained in:
Bram Kragten 2024-09-27 16:56:38 +02:00 committed by GitHub
parent 94e321a364
commit 7a60763786
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 217 additions and 124 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -8,7 +8,6 @@ export const AssistantSetupStyles = [
align-items: center; align-items: center;
text-align: center; text-align: center;
min-height: 300px; min-height: 300px;
max-width: 500px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -21,16 +20,27 @@ export const AssistantSetupStyles = [
} }
.content img { .content img {
width: 120px; width: 120px;
margin-top: 68px; }
margin-bottom: 68px; @media all and (max-width: 450px), all and (max-height: 500px) {
.content img {
margin-top: 68px;
margin-bottom: 68px;
}
} }
.footer { .footer {
width: 100%;
display: flex; display: flex;
width: 100%;
flex-direction: row;
justify-content: flex-end;
}
.footer.full-width {
flex-direction: column; flex-direction: column;
} }
.footer ha-button { .footer.full-width ha-button {
width: 100%; width: 100%;
} }
.footer.side-by-side {
justify-content: space-between;
}
`, `,
]; ];

View File

@ -1,5 +1,5 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { mdiChevronLeft } from "@mdi/js"; import { mdiChevronLeft, mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -50,6 +50,8 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
private _previousSteps: STEP[] = []; private _previousSteps: STEP[] = [];
private _nextStep?: STEP;
public async showDialog( public async showDialog(
params: VoiceAssistantSetupDialogParams params: VoiceAssistantSetupDialogParams
): Promise<void> { ): Promise<void> {
@ -113,19 +115,38 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
@closed=${this._dialogClosed} @closed=${this._dialogClosed}
.heading=${"Voice Satellite setup"} .heading=${"Voice Satellite setup"}
hideActions hideActions
escapeKeyAction
scrimClickAction
> >
<ha-dialog-header slot="heading"> <ha-dialog-header slot="heading">
${this._previousSteps.length ${this._previousSteps.length
? html`<ha-icon-button ? html`<ha-icon-button
slot="navigationIcon" slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.generic.close") ?? .label=${this.hass.localize("ui.common.back") ?? "Back"}
"Close"}
.path=${mdiChevronLeft} .path=${mdiChevronLeft}
@click=${this._goToPreviousStep} @click=${this._goToPreviousStep}
></ha-icon-button>` ></ha-icon-button>`
: this._step !== STEP.UPDATE
? html`<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.generic.close") ??
"Close"}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>`
: nothing}
${this._step === STEP.WAKEWORD ||
this._step === STEP.AREA ||
this._step === STEP.PIPELINE
? html`<ha-button
@click=${this._goToNextStep}
class="skip-btn"
slot="actionItems"
>Skip</ha-button
>`
: nothing} : nothing}
</ha-dialog-header> </ha-dialog-header>
<div class="content" @next-step=${this._nextStep}> <div class="content" @next-step=${this._goToNextStep}>
${this._step === STEP.UPDATE ${this._step === STEP.UPDATE
? html`<ha-voice-assistant-setup-step-update ? html`<ha-voice-assistant-setup-step-update
.hass=${this.hass} .hass=${this.hass}
@ -229,15 +250,21 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
this._step = this._previousSteps.pop()!; this._step = this._previousSteps.pop()!;
} }
private _nextStep(ev) { private _goToNextStep(ev) {
if (ev.detail?.updateConfig) { if (ev.detail?.updateConfig) {
this._fetchAssistConfiguration(); this._fetchAssistConfiguration();
} }
if (ev.detail?.nextStep) {
this._nextStep = ev.detail.nextStep;
}
if (!ev.detail?.noPrevious) { if (!ev.detail?.noPrevious) {
this._previousSteps.push(this._step); this._previousSteps.push(this._step);
} }
if (ev.detail?.step) { if (ev.detail?.step) {
this._step = ev.detail.step; this._step = ev.detail.step;
} else if (this._nextStep) {
this._step = this._nextStep;
this._nextStep = undefined;
} else { } else {
this._step += 1; this._step += 1;
} }
@ -250,6 +277,14 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
ha-dialog { ha-dialog {
--dialog-content-padding: 0; --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 { ha-dialog-header {
height: 56px; height: 56px;
} }
@ -258,6 +293,9 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
height: calc(100vh - 56px); height: calc(100vh - 56px);
} }
} }
.skip-btn {
margin-top: 6px;
}
`, `,
]; ];
} }
@ -270,7 +308,12 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"next-step": "next-step":
| { step?: STEP; updateConfig?: boolean; noPrevious?: boolean } | {
step?: STEP;
updateConfig?: boolean;
noPrevious?: boolean;
nextStep?: STEP;
}
| undefined; | undefined;
} }
} }

View File

@ -42,24 +42,7 @@ export class HaVoiceAssistantSetupStepAddons extends LitElement {
powerful device to run. If you device is not powerful enough, Home powerful device to run. If you device is not powerful enough, Home
Assistant cloud might be a better option. Assistant cloud might be a better option.
</p> </p>
<h3>Home Assistant Cloud:</h3> <h3>Raspberry Pi 4</h3>
<div class="messages-container cloud">
<div class="message user ${this._showFirst ? "show" : ""}">
${!this._showFirst ? "…" : "Turn on the lights in the bedroom"}
</div>
${this._showFirst
? html`<div class="timing user">0.2 seconds</div>`
: nothing}
${this._showFirst
? html` <div class="message hass ${this._showSecond ? "show" : ""}">
${!this._showSecond ? "…" : "Turned on the lights"}
</div>`
: nothing}
${this._showSecond
? html`<div class="timing hass">0.4 seconds</div>`
: nothing}
</div>
<h3>Raspberry Pi 4:</h3>
<div class="messages-container rpi"> <div class="messages-container rpi">
<div class="message user ${this._showThird ? "show" : ""}"> <div class="message user ${this._showThird ? "show" : ""}">
${!this._showThird ? "…" : "Turn on the lights in the bedroom"} ${!this._showThird ? "…" : "Turn on the lights in the bedroom"}
@ -76,8 +59,28 @@ export class HaVoiceAssistantSetupStepAddons extends LitElement {
? html`<div class="timing hass">5 seconds</div>` ? html`<div class="timing hass">5 seconds</div>`
: nothing} : nothing}
</div> </div>
<h3>Home Assistant Cloud</h3>
<div class="messages-container cloud">
<div class="message user ${this._showFirst ? "show" : ""}">
${!this._showFirst ? "…" : "Turn on the lights in the bedroom"}
</div>
${this._showFirst
? html`<div class="timing user">0.2 seconds</div>`
: nothing}
${this._showFirst
? html` <div class="message hass ${this._showSecond ? "show" : ""}">
${!this._showSecond ? "…" : "Turned on the lights"}
</div>`
: nothing}
${this._showSecond
? html`<div class="timing hass">0.4 seconds</div>`
: nothing}
</div>
</div> </div>
<div class="footer"> <div class="footer side-by-side">
<ha-button @click=${this._goToCloud}
>Try Home Assistant Cloud</ha-button
>
<a <a
href=${documentationUrl( href=${documentationUrl(
this.hass, this.hass,
@ -85,19 +88,14 @@ export class HaVoiceAssistantSetupStepAddons extends LitElement {
)} )}
target="_blank" target="_blank"
rel="noreferrer noopenner" rel="noreferrer noopenner"
@click=${this._close}
><ha-button unelevated
>Learn how to setup local assistant</ha-button
></a
>
<ha-button @click=${this._skip}
>I already have a local assistant</ha-button
> >
<ha-button @click=${this._skip} unelevated>Learn more</ha-button>
</a>
</div>`; </div>`;
} }
private _close() { private _goToCloud() {
fireEvent(this, "closed"); fireEvent(this, "next-step", { step: STEP.CLOUD });
} }
private _skip() { private _skip() {

View File

@ -28,7 +28,7 @@ export class HaVoiceAssistantSetupStepArea extends LitElement {
></ha-area-picker> ></ha-area-picker>
</div> </div>
<div class="footer"> <div class="footer">
<ha-button @click=${this._setArea}>Next</ha-button> <ha-button @click=${this._setArea} unelevated>Next</ha-button>
</div>`; </div>`;
} }

View File

@ -25,8 +25,8 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
<img src="/static/icons/casita/smiling.png" /> <img src="/static/icons/casita/smiling.png" />
<h1>Change wake word</h1> <h1>Change wake word</h1>
<p class="secondary"> <p class="secondary">
When you voice assistant knows where it is, it can better control the Some wake words are better for [your language] and voice than others.
devices around it. Please try them out.
</p> </p>
</div> </div>
<ha-md-list> <ha-md-list>
@ -72,6 +72,7 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
ha-md-list { ha-md-list {
width: 100%; width: 100%;
text-align: initial; text-align: initial;
margin-bottom: 24px;
} }
`, `,
]; ];

View File

@ -22,7 +22,7 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
if ( if (
this._status === "success" && this._status === "success" &&
changedProperties.has("hass") && changedProperties.has("hass") &&
this.hass.states[this.assistEntityId!]?.state === "listening_wake_word" this.hass.states[this.assistEntityId!]?.state === "idle"
) { ) {
this._nextStep(); this._nextStep();
} }
@ -38,16 +38,13 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
</p>` </p>`
: this._status === "timeout" : this._status === "timeout"
? html`<img src="/static/icons/casita/sad.png" /> ? html`<img src="/static/icons/casita/sad.png" />
<h1>Error</h1> <h1>Voice assistant can not connect to Home Assistant</h1>
<p class="secondary"> <p class="secondary">
Your device was unable to reach Home Assistant. Make sure you A good explanation what is happening and what action you should
have setup your take.
<a href="/config/network" @click=${this._close}
>Home Assistant URL's</a
>
correctly.
</p> </p>
<div class="footer"> <div class="footer">
<a href="#"><ha-button>Help me</ha-button></a>
<ha-button @click=${this._testConnection}>Retry</ha-button> <ha-button @click=${this._testConnection}>Retry</ha-button>
</div>` </div>`
: html`<img src="/static/icons/casita/loading.png" /> : html`<img src="/static/icons/casita/loading.png" />
@ -73,10 +70,6 @@ export class HaVoiceAssistantSetupStepCheck extends LitElement {
fireEvent(this, "next-step", { noPrevious: true }); fireEvent(this, "next-step", { noPrevious: true });
} }
private _close() {
fireEvent(this, "closed");
}
static styles = AssistantSetupStyles; static styles = AssistantSetupStyles;
} }

View File

@ -10,16 +10,24 @@ export class HaVoiceAssistantSetupStepCloud extends LitElement {
protected override render() { protected override render() {
return html`<div class="content"> return html`<div class="content">
<img src="/static/icons/casita/loving.png" /> <img src="/static/images/logo_nabu_casa.png" />
<h1>Home Assistant Cloud</h1> <h1>Supercharge your assistant with Home Assistant Cloud</h1>
<p class="secondary"> <p class="secondary">
With Home Assistant Cloud, you get the best results for your voice Speed up and take the load off your system by running your
assistant, sign up for a free trial now. text-to-speech and speech-to-text in our private and secure cloud.
Cloud also includes secure remote access to your system while
supporting the development of Home Assistant.
</p> </p>
</div> </div>
<div class="footer"> <div class="footer side-by-side">
<a
href="https://www.nabucasa.com"
target="_blank"
rel="noreferrer noopenner"
><ha-button>Learn more</ha-button></a
>
<a href="/config/cloud/register" @click=${this._close} <a href="/config/cloud/register" @click=${this._close}
><ha-button>Start your free trial</ha-button></a ><ha-button unelevated>Try 1 month for free</ha-button></a
> >
</div>`; </div>`;
} }

View File

@ -92,7 +92,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
)} )}
rel="noreferrer noopenner" rel="noreferrer noopenner"
target="_blank" target="_blank"
@click=${this._close} @click=${this._skip}
> >
Use external system Use external system
<span slot="supporting-text" <span slot="supporting-text"
@ -221,12 +221,12 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
fireEvent(this, "next-step", { step: STEP.ADDONS }); fireEvent(this, "next-step", { step: STEP.ADDONS });
} }
private _nextStep(step?: STEP) { private _skip() {
fireEvent(this, "next-step", { step }); this._nextStep(STEP.SUCCESS);
} }
private _close() { private _nextStep(step?: STEP) {
fireEvent(this, "closed"); fireEvent(this, "next-step", { step });
} }
static styles = [ static styles = [

View File

@ -1,9 +1,9 @@
import { mdiCog, mdiMicrophone, mdiPlay } from "@mdi/js";
import { css, html, LitElement, nothing, PropertyValues } from "lit"; import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
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-md-list-item"; import "../../components/ha-select";
import "../../components/ha-tts-voice-picker"; import "../../components/ha-tts-voice-picker";
import { import {
AssistPipeline, AssistPipeline,
@ -56,58 +56,78 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
} }
} }
private _activeWakeWord = memoizeOne(
(config: AssistSatelliteConfiguration | undefined) => {
if (!config) {
return "";
}
const activeId = config.active_wake_words[0];
return config.available_wake_words.find((ww) => ww.id === activeId)
?.wake_word;
}
);
protected override render() { protected override render() {
const pipelineEntity = this.assistConfiguration
? this.hass.states[this.assistConfiguration.pipeline_entity_id]
: undefined;
return html`<div class="content"> return html`<div class="content">
<img src="/static/icons/casita/loving.png" /> <img src="/static/icons/casita/loving.png" />
<h1>Ready to assist!</h1> <h1>Ready to assist!</h1>
<p class="secondary"> <p class="secondary">
Make your assistant more personal by customizing shizzle to the Your device is all ready to go! If you want to tweak some more
manizzle settings, you can change that below.
</p> </p>
<ha-md-list-item <div class="rows">
interactive <div class="row">
type="button" <ha-select
@click=${this._changeWakeWord} .label=${"Wake word"}
>
Change wake word
<span slot="supporting-text"
>${this._activeWakeWord(this.assistConfiguration)}</span
>
<ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item>
<hui-select-entity-row
.hass=${this.hass}
._config=${{
entity: this.assistConfiguration?.pipeline_entity_id,
}}
></hui-select-entity-row>
${this._ttsSettings
? html`<ha-tts-voice-picker
.hass=${this.hass}
required
.engineId=${this._ttsSettings.engine}
.language=${this._ttsSettings.language}
.value=${this._ttsSettings.voice}
@value-changed=${this._voicePicked}
@closed=${stopPropagation} @closed=${stopPropagation}
></ha-tts-voice-picker>` fixedMenuPosition
: nothing} naturalMenuWidth
.value=${this.assistConfiguration?.active_wake_words[0]}
>
${this.assistConfiguration?.available_wake_words.map(
(wakeword) =>
html`<ha-list-item .value=${wakeword.id}>
${wakeword.wake_word}
</ha-list-item>`
)}
</ha-select>
<ha-button @click=${this._testWakeWord}>
<ha-svg-icon slot="icon" .path=${mdiMicrophone}></ha-svg-icon>
Test
</ha-button>
</div>
<div class="row">
<ha-select
.label=${"Assistant"}
@closed=${stopPropagation}
.value=${pipelineEntity?.state}
fixedMenuPosition
naturalMenuWidth
>
${pipelineEntity?.attributes.options.map(
(pipeline) =>
html`<ha-list-item .value=${pipeline}>
${this.hass.formatEntityState(pipelineEntity, pipeline)}
</ha-list-item>`
)}
</ha-select>
<ha-button @click=${this._openPipeline}>
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
Edit
</ha-button>
</div>
${this._ttsSettings
? html`<div class="row">
<ha-tts-voice-picker
.hass=${this.hass}
.engineId=${this._ttsSettings.engine}
.language=${this._ttsSettings.language}
.value=${this._ttsSettings.voice}
@value-changed=${this._voicePicked}
@closed=${stopPropagation}
></ha-tts-voice-picker>
<ha-button @click=${this._testTts}>
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
Try
</ha-button>
</div>`
: nothing}
</div>
</div> </div>
<div class="footer"> <div class="footer">
<ha-button @click=${this._openPipeline}
>Change assistant settings</ha-button
>
<ha-button @click=${this._close} unelevated>Done</ha-button> <ha-button @click=${this._close} unelevated>Done</ha-button>
</div>`; </div>`;
} }
@ -160,6 +180,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
...pipeline, ...pipeline,
tts_voice: ev.detail.value, tts_voice: ev.detail.value,
}); });
}
private _testTts() {
this._announce("Hello, how can I help you?"); this._announce("Hello, how can I help you?");
} }
@ -170,8 +193,11 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
await assistSatelliteAnnounce(this.hass, this.assistEntityId, message); await assistSatelliteAnnounce(this.hass, this.assistEntityId, message);
} }
private _changeWakeWord() { private _testWakeWord() {
fireEvent(this, "next-step", { step: STEP.CHANGE_WAKEWORD }); fireEvent(this, "next-step", {
step: STEP.WAKEWORD,
nextStep: STEP.SUCCESS,
});
} }
private async _openPipeline() { private async _openPipeline() {
@ -209,12 +235,28 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
text-align: initial; text-align: initial;
} }
ha-tts-voice-picker { ha-tts-voice-picker {
margin-top: 16px;
display: block; display: block;
} }
.footer { .footer {
margin-top: 24px; margin-top: 24px;
} }
.rows {
gap: 16px;
display: flex;
flex-direction: column;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
}
.row > *:first-child {
flex: 1;
margin-right: 4px;
}
.row ha-button {
width: 82px;
}
`, `,
]; ];
} }

View File

@ -17,6 +17,11 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement {
protected override willUpdate(changedProperties: PropertyValues): void { protected override willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties); super.willUpdate(changedProperties);
if (!this.updateEntityId) {
this._nextStep();
return;
}
if (changedProperties.has("hass") && this.updateEntityId) { if (changedProperties.has("hass") && this.updateEntityId) {
const oldHass = changedProperties.get("hass") as this["hass"] | undefined; const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
if (oldHass) { if (oldHass) {
@ -32,16 +37,9 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement {
} }
} }
if (!changedProperties.has("updateEntityId")) { if (changedProperties.has("updateEntityId")) {
return; this._tryUpdate();
} }
if (!this.updateEntityId) {
this._nextStep();
return;
}
this._tryUpdate();
} }
protected override render() { protected override render() {

View File

@ -58,7 +58,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
const entityState = this.hass.states[this.assistEntityId]; const entityState = this.hass.states[this.assistEntityId];
if (entityState.state !== "listening_wake_word") { if (entityState.state !== "idle") {
return html`<ha-circular-progress indeterminate></ha-circular-progress>`; return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
} }
@ -80,7 +80,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
To make sure the wake word works for you. To make sure the wake word works for you.
</p>`} </p>`}
</div> </div>
<div class="footer"> <div class="footer full-width">
<ha-button @click=${this._changeWakeWord}>Change wake word</ha-button> <ha-button @click=${this._changeWakeWord}>Change wake word</ha-button>
</div>`; </div>`;
} }