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;
text-align: center;
min-height: 300px;
max-width: 500px;
display: flex;
flex-direction: column;
justify-content: space-between;
@ -21,16 +20,27 @@ export const AssistantSetupStyles = [
}
.content img {
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 {
width: 100%;
display: flex;
width: 100%;
flex-direction: row;
justify-content: flex-end;
}
.footer.full-width {
flex-direction: column;
}
.footer ha-button {
.footer.full-width ha-button {
width: 100%;
}
.footer.side-by-side {
justify-content: space-between;
}
`,
];

View File

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

View File

@ -42,24 +42,7 @@ export class HaVoiceAssistantSetupStepAddons extends LitElement {
powerful device to run. If you device is not powerful enough, Home
Assistant cloud might be a better option.
</p>
<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>
<h3>Raspberry Pi 4:</h3>
<h3>Raspberry Pi 4</h3>
<div class="messages-container rpi">
<div class="message user ${this._showThird ? "show" : ""}">
${!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>`
: nothing}
</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 class="footer">
<div class="footer side-by-side">
<ha-button @click=${this._goToCloud}
>Try Home Assistant Cloud</ha-button
>
<a
href=${documentationUrl(
this.hass,
@ -85,19 +88,14 @@ export class HaVoiceAssistantSetupStepAddons extends LitElement {
)}
target="_blank"
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>`;
}
private _close() {
fireEvent(this, "closed");
private _goToCloud() {
fireEvent(this, "next-step", { step: STEP.CLOUD });
}
private _skip() {

View File

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

View File

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

View File

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

View File

@ -10,16 +10,24 @@ export class HaVoiceAssistantSetupStepCloud extends LitElement {
protected override render() {
return html`<div class="content">
<img src="/static/icons/casita/loving.png" />
<h1>Home Assistant Cloud</h1>
<img src="/static/images/logo_nabu_casa.png" />
<h1>Supercharge your assistant with Home Assistant Cloud</h1>
<p class="secondary">
With Home Assistant Cloud, you get the best results for your voice
assistant, sign up for a free trial now.
Speed up and take the load off your system by running your
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>
</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}
><ha-button>Start your free trial</ha-button></a
><ha-button unelevated>Try 1 month for free</ha-button></a
>
</div>`;
}

View File

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

View File

@ -1,9 +1,9 @@
import { mdiCog, mdiMicrophone, mdiPlay } from "@mdi/js";
import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } 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-md-list-item";
import "../../components/ha-select";
import "../../components/ha-tts-voice-picker";
import {
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() {
const pipelineEntity = this.assistConfiguration
? this.hass.states[this.assistConfiguration.pipeline_entity_id]
: undefined;
return html`<div class="content">
<img src="/static/icons/casita/loving.png" />
<h1>Ready to assist!</h1>
<p class="secondary">
Make your assistant more personal by customizing shizzle to the
manizzle
Your device is all ready to go! If you want to tweak some more
settings, you can change that below.
</p>
<ha-md-list-item
interactive
type="button"
@click=${this._changeWakeWord}
>
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}
<div class="rows">
<div class="row">
<ha-select
.label=${"Wake word"}
@closed=${stopPropagation}
></ha-tts-voice-picker>`
: nothing}
fixedMenuPosition
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 class="footer">
<ha-button @click=${this._openPipeline}
>Change assistant settings</ha-button
>
<ha-button @click=${this._close} unelevated>Done</ha-button>
</div>`;
}
@ -160,6 +180,9 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
...pipeline,
tts_voice: ev.detail.value,
});
}
private _testTts() {
this._announce("Hello, how can I help you?");
}
@ -170,8 +193,11 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
await assistSatelliteAnnounce(this.hass, this.assistEntityId, message);
}
private _changeWakeWord() {
fireEvent(this, "next-step", { step: STEP.CHANGE_WAKEWORD });
private _testWakeWord() {
fireEvent(this, "next-step", {
step: STEP.WAKEWORD,
nextStep: STEP.SUCCESS,
});
}
private async _openPipeline() {
@ -209,12 +235,28 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
text-align: initial;
}
ha-tts-voice-picker {
margin-top: 16px;
display: block;
}
.footer {
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 {
super.willUpdate(changedProperties);
if (!this.updateEntityId) {
this._nextStep();
return;
}
if (changedProperties.has("hass") && this.updateEntityId) {
const oldHass = changedProperties.get("hass") as this["hass"] | undefined;
if (oldHass) {
@ -32,16 +37,9 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement {
}
}
if (!changedProperties.has("updateEntityId")) {
return;
if (changedProperties.has("updateEntityId")) {
this._tryUpdate();
}
if (!this.updateEntityId) {
this._nextStep();
return;
}
this._tryUpdate();
}
protected override render() {

View File

@ -58,7 +58,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
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>`;
}
@ -80,7 +80,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
To make sure the wake word works for you.
</p>`}
</div>
<div class="footer">
<div class="footer full-width">
<ha-button @click=${this._changeWakeWord}>Change wake word</ha-button>
</div>`;
}