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 188e55501f..d5471f5444 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -1,5 +1,4 @@ import "@polymer/iron-icon/iron-icon"; -import "@polymer/paper-dialog-behavior/paper-dialog-shared-styles"; import "@polymer/paper-icon-button/paper-icon-button"; import "../../components/dialog/ha-paper-dialog"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; @@ -13,16 +12,20 @@ import { customElement, query, PropertyValues, + TemplateResult, } from "lit-element"; import { HomeAssistant } from "../../types"; import { fireEvent } from "../../common/dom/fire_event"; import { processText } from "../../data/conversation"; import { classMap } from "lit-html/directives/class-map"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { haStyleDialog } from "../../resources/styles"; +// tslint:disable-next-line +import { PaperDialogScrollableElement } from "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; interface Message { who: string; - text: string; + text?: string; error?: boolean; } @@ -57,7 +60,7 @@ export class HaVoiceCommandDialog extends LitElement { }, ]; @property() private _opened = false; - @query("#messages") private messages!: HTMLDivElement; + @query("#messages") private messages!: PaperDialogScrollableElement; private recognition?: SpeechRecognition; public async showDialog(): Promise { @@ -67,74 +70,102 @@ export class HaVoiceCommandDialog extends LitElement { } } - protected render() { + protected render(): TemplateResult { + // CSS custom property mixins only work in render https://github.com/Polymer/lit-element/issues/633 return html` + -
-
- ${this._conversation.map( - (message) => html` -
- ${message.text} -
- ` - )} -
+ + ${this._conversation.map( + (message) => html` +
+ ${message.text} +
+ ` + )} ${this.results ? html` -
-
- ${this.results.transcript}${!this.results.final ? "…" : ""} -
+
+ ${this.results.transcript}${!this.results.final ? "…" : ""}
` : ""} - - ${SpeechRecognition - ? html` - - ${this.results - ? html` -
-
-
-
- ` - : ""} - - -
- ` - : ""} -
-
+
+ + ${SpeechRecognition + ? html` + + ${this.results + ? html` +
+
+
+
+ ` + : ""} + + +
+ ` + : ""} +
`; } - protected firstUpdated(changedPros: PropertyValues) { - super.updated(changedPros); + protected firstUpdated(changedProps: PropertyValues) { + super.updated(changedProps); this._conversation = [ { who: "hass", @@ -143,9 +174,9 @@ export class HaVoiceCommandDialog extends LitElement { ]; } - protected updated(changedPros: PropertyValues) { - super.updated(changedPros); - if (changedPros.has("_conversation")) { + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (changedProps.has("_conversation") || changedProps.has("results")) { this._scrollMessagesBottom(); } } @@ -157,9 +188,6 @@ export class HaVoiceCommandDialog extends LitElement { private _handleKeyUp(ev: KeyboardEvent) { const input = ev.target as PaperInputElement; if (ev.keyCode === 13 && input.value) { - if (this.recognition) { - this.recognition!.abort(); - } this._processText(input.value); input.value = ""; } @@ -219,13 +247,23 @@ export class HaVoiceCommandDialog extends LitElement { } private async _processText(text: string) { + if (this.recognition) { + this.recognition.abort(); + } this._addMessage({ who: "user", text }); + const message: Message = { + who: "hass", + text: "…", + }; + // To make sure the answer is placed at the right user text, we add it before we process it + this._addMessage(message); try { const response = await processText(this.hass, text); - this._addMessage({ - who: "hass", - text: response.speech.plain.speech, - }); + const plain = response.speech.plain; + message.text = plain.speech; + + this.requestUpdate("_conversation"); + if (speechSynthesis) { const speech = new SpeechSynthesisUtterance( response.speech.plain.speech @@ -234,8 +272,9 @@ export class HaVoiceCommandDialog extends LitElement { speechSynthesis.speak(speech); } } catch { - this._conversation.slice(-1).pop()!.error = true; - this.requestUpdate(); + message.text = this.hass.localize("ui.dialogs.voice_command.error"); + message.error = true; + this.requestUpdate("_conversation"); } } @@ -264,9 +303,8 @@ export class HaVoiceCommandDialog extends LitElement { } private _scrollMessagesBottom() { - this.messages.scrollTop = this.messages.scrollHeight; - - if (this.messages.scrollTop === 0) { + this.messages.scrollTarget.scrollTop = this.messages.scrollTarget.scrollHeight; + if (this.messages.scrollTarget.scrollTop === 0) { fireEvent(this.messages, "iron-resize"); } } @@ -278,143 +316,128 @@ export class HaVoiceCommandDialog extends LitElement { } } - private _computeMessageClasses(message) { - return "message " + message.who + (message.error ? " error" : ""); + private _computeMessageClasses(message: Message) { + return `message ${message.who} ${message.error ? " error" : ""}`; } - static get styles(): CSSResult { - return css` - paper-icon-button { - color: var(--secondary-text-color); - } - - paper-icon-button[active] { - color: var(--primary-color); - } - - .content { - width: 450px; - min-height: 80px; - font-size: 18px; - padding: 16px; - } - - .messages { - max-height: 50vh; - overflow: auto; - } - - .messages::after { - content: ""; - clear: both; - display: block; - } - - .message { - clear: both; - margin: 8px 0; - padding: 8px; - border-radius: 15px; - } - - .message.user { - margin-left: 24px; - float: right; - text-align: right; - border-bottom-right-radius: 0px; - background-color: var(--light-primary-color); - color: var(--primary-text-color); - } - - .message.hass { - margin-right: 24px; - float: left; - border-bottom-left-radius: 0px; - background-color: var(--primary-color); - color: var(--text-primary-color); - } - - .message.error { - background-color: var(--google-red-500); - color: var(--text-primary-color); - } - - .interimTranscript { - color: var(--secondary-text-color); - } - - [hidden] { - display: none; - } - - :host { - border-radius: 2px; - } - - @media all and (max-width: 450px) { + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` :host { - margin: 0; - width: 100%; - max-height: calc(100% - 64px); + z-index: 103; + } - position: fixed !important; - bottom: 0px; - left: 0px; - right: 0px; - overflow: auto; - border-bottom-left-radius: 0px; + paper-icon-button { + color: var(--secondary-text-color); + } + + paper-icon-button[active] { + color: var(--primary-color); + } + + paper-input { + margin: 0 0 16px 0; + } + + ha-paper-dialog { + width: 450px; + } + + .message { + font-size: 18px; + clear: both; + margin: 8px 0; + padding: 8px; + border-radius: 15px; + } + + .message.user { + margin-left: 24px; + float: right; + text-align: right; border-bottom-right-radius: 0px; + background-color: var(--light-primary-color); + color: var(--primary-text-color); } - .messages { - max-height: 68vh; + .message.hass { + margin-right: 24px; + float: left; + border-bottom-left-radius: 0px; + background-color: var(--primary-color); + color: var(--text-primary-color); } - } - .bouncer { - width: 40px; - height: 40px; - position: absolute; - top: 0; - } - .double-bounce1, - .double-bounce2 { - width: 40px; - height: 40px; - border-radius: 50%; - background-color: var(--primary-color); - opacity: 0.2; - position: absolute; - top: 0; - left: 0; - -webkit-animation: sk-bounce 2s infinite ease-in-out; - animation: sk-bounce 2s infinite ease-in-out; - } - .double-bounce2 { - -webkit-animation-delay: -1s; - animation-delay: -1s; - } - @-webkit-keyframes sk-bounce { - 0%, - 100% { - -webkit-transform: scale(0); + .message a { + color: var(--text-primary-color); } - 50% { - -webkit-transform: scale(1); + + .message img { + width: 100%; + border-radius: 10px; } - } - @keyframes sk-bounce { - 0%, - 100% { - transform: scale(0); - -webkit-transform: scale(0); + + .message.error { + background-color: var(--google-red-500); + color: var(--text-primary-color); } - 50% { - transform: scale(1); - -webkit-transform: scale(1); + + .interimTranscript { + color: var(--secondary-text-color); } - } - `; + + .bouncer { + width: 40px; + height: 40px; + position: absolute; + top: 0; + } + .double-bounce1, + .double-bounce2 { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--primary-color); + opacity: 0.2; + position: absolute; + top: 0; + left: 0; + -webkit-animation: sk-bounce 2s infinite ease-in-out; + animation: sk-bounce 2s infinite ease-in-out; + } + .double-bounce2 { + -webkit-animation-delay: -1s; + animation-delay: -1s; + } + @-webkit-keyframes sk-bounce { + 0%, + 100% { + -webkit-transform: scale(0); + } + 50% { + -webkit-transform: scale(1); + } + } + @keyframes sk-bounce { + 0%, + 100% { + transform: scale(0); + -webkit-transform: scale(0); + } + 50% { + transform: scale(1); + -webkit-transform: scale(1); + } + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + .message { + font-size: 16px; + } + } + `, + ]; } } diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index 24c160cc9a..525b703c6c 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -163,7 +163,7 @@ export class HuiDialogEditCard extends LitElement { } } - @media all and (min-width: 660px) { + @media all and (min-width: 850px) { ha-paper-dialog { width: 845px; } diff --git a/src/translations/en.json b/src/translations/en.json index 52ca582497..85704da44c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -556,6 +556,8 @@ "dialogs": { "voice_command": { "did_not_hear": "Home Assistant did not hear anything", + "found": "I found the following for you:", + "error": "Oops, an error has occurred", "how_can_i_help": "How can I help?", "label": "Type a question and press ", "label_voice": "Type and press or tap the microphone icon to speak"