mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Fix mobile support for voice dialog (#4154)
* Fix mobile support for voice dialog * Update ha-voice-command-dialog.ts * typo * Add extra data functions * Start listening for choice * Remove extra data logic
This commit is contained in:
parent
da35c263d2
commit
5ca82fd39c
@ -1,5 +1,4 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
import "@polymer/paper-dialog-behavior/paper-dialog-shared-styles";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "../../components/dialog/ha-paper-dialog";
|
import "../../components/dialog/ha-paper-dialog";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
@ -13,16 +12,20 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
query,
|
query,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { processText } from "../../data/conversation";
|
import { processText } from "../../data/conversation";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
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 {
|
interface Message {
|
||||||
who: string;
|
who: string;
|
||||||
text: string;
|
text?: string;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
@property() private _opened = false;
|
@property() private _opened = false;
|
||||||
@query("#messages") private messages!: HTMLDivElement;
|
@query("#messages") private messages!: PaperDialogScrollableElement;
|
||||||
private recognition?: SpeechRecognition;
|
private recognition?: SpeechRecognition;
|
||||||
|
|
||||||
public async showDialog(): Promise<void> {
|
public async showDialog(): Promise<void> {
|
||||||
@ -67,15 +70,46 @@ 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`
|
return html`
|
||||||
|
<style>
|
||||||
|
paper-dialog-scrollable {
|
||||||
|
--paper-dialog-scrollable: {
|
||||||
|
-webkit-overflow-scrolling: auto;
|
||||||
|
max-height: 50vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-dialog-scrollable.can-scroll {
|
||||||
|
--paper-dialog-scrollable: {
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
max-height: 50vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
paper-dialog-scrollable {
|
||||||
|
--paper-dialog-scrollable: {
|
||||||
|
-webkit-overflow-scrolling: auto;
|
||||||
|
max-height: calc(100vh - 175px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-dialog-scrollable.can-scroll {
|
||||||
|
--paper-dialog-scrollable: {
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
max-height: calc(100vh - 175px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<ha-paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
.opened=${this._opened}
|
.opened=${this._opened}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
>
|
>
|
||||||
<div class="content">
|
<paper-dialog-scrollable id="messages">
|
||||||
<div class="messages" id="messages">
|
|
||||||
${this._conversation.map(
|
${this._conversation.map(
|
||||||
(message) => html`
|
(message) => html`
|
||||||
<div class="${this._computeMessageClasses(message)}">
|
<div class="${this._computeMessageClasses(message)}">
|
||||||
@ -83,10 +117,8 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
${this.results
|
${this.results
|
||||||
? html`
|
? html`
|
||||||
<div class="messages">
|
|
||||||
<div class="message user">
|
<div class="message user">
|
||||||
<span
|
<span
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -95,9 +127,9 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
>${this.results.transcript}</span
|
>${this.results.transcript}</span
|
||||||
>${!this.results.final ? "…" : ""}
|
>${!this.results.final ? "…" : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
</paper-dialog-scrollable>
|
||||||
<paper-input
|
<paper-input
|
||||||
@keyup=${this._handleKeyUp}
|
@keyup=${this._handleKeyUp}
|
||||||
label="${this.hass!.localize(
|
label="${this.hass!.localize(
|
||||||
@ -128,13 +160,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</div>
|
|
||||||
</ha-paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedPros: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.updated(changedPros);
|
super.updated(changedProps);
|
||||||
this._conversation = [
|
this._conversation = [
|
||||||
{
|
{
|
||||||
who: "hass",
|
who: "hass",
|
||||||
@ -143,9 +174,9 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedPros: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super.updated(changedPros);
|
super.updated(changedProps);
|
||||||
if (changedPros.has("_conversation")) {
|
if (changedProps.has("_conversation") || changedProps.has("results")) {
|
||||||
this._scrollMessagesBottom();
|
this._scrollMessagesBottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,9 +188,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
private _handleKeyUp(ev: KeyboardEvent) {
|
private _handleKeyUp(ev: KeyboardEvent) {
|
||||||
const input = ev.target as PaperInputElement;
|
const input = ev.target as PaperInputElement;
|
||||||
if (ev.keyCode === 13 && input.value) {
|
if (ev.keyCode === 13 && input.value) {
|
||||||
if (this.recognition) {
|
|
||||||
this.recognition!.abort();
|
|
||||||
}
|
|
||||||
this._processText(input.value);
|
this._processText(input.value);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
}
|
}
|
||||||
@ -219,13 +247,23 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _processText(text: string) {
|
private async _processText(text: string) {
|
||||||
|
if (this.recognition) {
|
||||||
|
this.recognition.abort();
|
||||||
|
}
|
||||||
this._addMessage({ who: "user", text });
|
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 {
|
try {
|
||||||
const response = await processText(this.hass, text);
|
const response = await processText(this.hass, text);
|
||||||
this._addMessage({
|
const plain = response.speech.plain;
|
||||||
who: "hass",
|
message.text = plain.speech;
|
||||||
text: response.speech.plain.speech,
|
|
||||||
});
|
this.requestUpdate("_conversation");
|
||||||
|
|
||||||
if (speechSynthesis) {
|
if (speechSynthesis) {
|
||||||
const speech = new SpeechSynthesisUtterance(
|
const speech = new SpeechSynthesisUtterance(
|
||||||
response.speech.plain.speech
|
response.speech.plain.speech
|
||||||
@ -234,8 +272,9 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
speechSynthesis.speak(speech);
|
speechSynthesis.speak(speech);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this._conversation.slice(-1).pop()!.error = true;
|
message.text = this.hass.localize("ui.dialogs.voice_command.error");
|
||||||
this.requestUpdate();
|
message.error = true;
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,9 +303,8 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _scrollMessagesBottom() {
|
private _scrollMessagesBottom() {
|
||||||
this.messages.scrollTop = this.messages.scrollHeight;
|
this.messages.scrollTarget.scrollTop = this.messages.scrollTarget.scrollHeight;
|
||||||
|
if (this.messages.scrollTarget.scrollTop === 0) {
|
||||||
if (this.messages.scrollTop === 0) {
|
|
||||||
fireEvent(this.messages, "iron-resize");
|
fireEvent(this.messages, "iron-resize");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,12 +316,18 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeMessageClasses(message) {
|
private _computeMessageClasses(message: Message) {
|
||||||
return "message " + message.who + (message.error ? " error" : "");
|
return `message ${message.who} ${message.error ? " error" : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
z-index: 103;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
|
||||||
return css`
|
|
||||||
paper-icon-button {
|
paper-icon-button {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
@ -292,25 +336,16 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
paper-input {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-paper-dialog {
|
||||||
width: 450px;
|
width: 450px;
|
||||||
min-height: 80px;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
max-height: 50vh;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages::after {
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
|
font-size: 18px;
|
||||||
clear: both;
|
clear: both;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@ -334,6 +369,15 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message a {
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.message.error {
|
.message.error {
|
||||||
background-color: var(--google-red-500);
|
background-color: var(--google-red-500);
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
@ -343,34 +387,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host {
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 450px) {
|
|
||||||
:host {
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
max-height: calc(100% - 64px);
|
|
||||||
|
|
||||||
position: fixed !important;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
overflow: auto;
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
max-height: 68vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bouncer {
|
.bouncer {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@ -414,7 +430,14 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
-webkit-transform: scale(1);
|
-webkit-transform: scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
.message {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ export class HuiDialogEditCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 660px) {
|
@media all and (min-width: 850px) {
|
||||||
ha-paper-dialog {
|
ha-paper-dialog {
|
||||||
width: 845px;
|
width: 845px;
|
||||||
}
|
}
|
||||||
|
@ -556,6 +556,8 @@
|
|||||||
"dialogs": {
|
"dialogs": {
|
||||||
"voice_command": {
|
"voice_command": {
|
||||||
"did_not_hear": "Home Assistant did not hear anything",
|
"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?",
|
"how_can_i_help": "How can I help?",
|
||||||
"label": "Type a question and press <Enter>",
|
"label": "Type a question and press <Enter>",
|
||||||
"label_voice": "Type and press <Enter> or tap the microphone icon to speak"
|
"label_voice": "Type and press <Enter> or tap the microphone icon to speak"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user