Update add assistant dialog (#16266)

* Update add assistant dialog

* fix default agent to ha when no supported

* Update ha-tts-voice-picker.ts

* Update assist-pipeline-detail-conversation.ts

* Update ha-dialog.ts

* dont override config

* Update ha-language-picker.ts
This commit is contained in:
Bram Kragten 2023-04-22 02:41:30 +02:00 committed by GitHub
parent 878f3b8df4
commit 85a27e8bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 225 additions and 119 deletions

View File

@ -37,7 +37,15 @@ export class HaConversationAgentPicker extends LitElement {
if (!this._agents) {
return nothing;
}
const value = this.value ?? (this.required ? "homeassistant" : NONE);
const value =
this.value ??
(this.required &&
(!this.language ||
this._agents
.find((agent) => agent.id === "homeassistant")
?.supported_languages?.includes(this.language))
? "homeassistant"
: NONE);
return html`
<ha-select
.label=${this.label ||
@ -84,7 +92,11 @@ export class HaConversationAgentPicker extends LitElement {
private _debouncedUpdateAgents = debounce(() => this._updateAgents(), 500);
private async _updateAgents() {
const { agents } = await listAgents(this.hass, this.language);
const { agents } = await listAgents(
this.hass,
this.language,
this.hass.config.country || undefined
);
this._agents = agents;

View File

@ -7,7 +7,7 @@ import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import type { HomeAssistant } from "../types";
import "./ha-icon-button";
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button"];
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button", "ha-list-item"];
export const createCloseHeading = (
hass: HomeAssistant,

View File

@ -33,6 +33,11 @@ export class HaFormGrid extends LitElement implements HaFormElement {
@property() public computeHelper?: (schema: HaFormSchema) => string;
public async focus() {
await this.updateComplete;
this.renderRoot.querySelector("ha-form")?.focus();
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("schema")) {

View File

@ -4,6 +4,7 @@ import {
html,
LitElement,
PropertyValues,
ReactiveElement,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
@ -56,13 +57,18 @@ export class HaForm extends LitElement implements HaFormElement {
@property() public localizeValue?: (key: string) => string;
public focus() {
const root = this.shadowRoot?.querySelector(".root");
public async focus() {
await this.updateComplete;
const root = this.renderRoot.querySelector(".root");
if (!root) {
return;
}
for (const child of root.children) {
if (child.tagName !== "HA-ALERT") {
if (child instanceof ReactiveElement) {
// eslint-disable-next-line no-await-in-loop
await child.updateComplete;
}
(child as HTMLElement).focus();
break;
}

View File

@ -1,17 +1,11 @@
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { formatLanguageCode } from "../common/language/format_language";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { FrontendLocaleData } from "../data/translation";
import { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
@ -35,13 +29,39 @@ export class HaLanguagePicker extends LitElement {
@state() _defaultLanguages: string[] = [];
@query("ha-select") private _select!: HaSelect;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._computeDefaultLanguageOptions();
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has("languages") || changedProperties.has("value")) {
this._select.layoutOptions();
if (this._select.value !== this.value) {
fireEvent(this, "value-changed", { value: this._select.value });
}
if (!this.value) {
return;
}
const languageOptions = this._getLanguagesOptions(
this.languages ?? this._defaultLanguages,
this.hass.locale,
this.nativeName
);
const selectedItem = languageOptions.find(
(option) => option.value === this.value
);
if (!selectedItem) {
this.value = undefined;
}
}
}
private _getLanguagesOptions = memoizeOne(
(languages: string[], language: string, nativeName: boolean) => {
(languages: string[], locale: FrontendLocaleData, nativeName: boolean) => {
let options: { label: string; value: string }[] = [];
if (nativeName) {
@ -53,12 +73,12 @@ export class HaLanguagePicker extends LitElement {
} else {
options = languages.map((lang) => ({
value: lang,
label: formatLanguageCode(lang, this.hass.locale),
label: formatLanguageCode(lang, locale),
}));
}
options.sort((a, b) =>
caseInsensitiveStringCompare(a.label, b.label, language)
caseInsensitiveStringCompare(a.label, b.label, locale.language)
);
return options;
}
@ -75,21 +95,19 @@ export class HaLanguagePicker extends LitElement {
}
protected render() {
const value = this.value;
const languageOptions = this._getLanguagesOptions(
this.languages ?? this._defaultLanguages,
this.hass.locale.language,
this.hass.locale,
this.nativeName
);
if (languageOptions.length === 0) {
return nothing;
}
const value =
this.value ?? (this.required ? languageOptions[0]?.value : this.value);
return html`
<ha-select
.label=${this.label}
.label=${this.label ||
this.hass.localize("ui.components.language-picker.language")}
.value=${value}
.required=${this.required}
.disabled=${this.disabled}
@ -98,11 +116,19 @@ export class HaLanguagePicker extends LitElement {
fixedMenuPosition
naturalMenuWidth
>
${languageOptions.map(
(option) => html`
<ha-list-item .value=${option.value}>${option.label}</ha-list-item>
`
)}
${languageOptions.length === 0
? html`<ha-list-item value=""
>${this.hass.localize(
"ui.components.language-picker.no_languages"
)}</ha-list-item
>`
: languageOptions.map(
(option) => html`
<ha-list-item .value=${option.value}
>${option.label}</ha-list-item
>
`
)}
</ha-select>
`;
}
@ -117,6 +143,9 @@ export class HaLanguagePicker extends LitElement {
private _changed(ev): void {
const target = ev.target as HaSelect;
if (!this.hass || target.value === "" || target.value === this.value) {
return;
}
this.value = target.value;
fireEvent(this, "value-changed", { value: this.value });
}

View File

@ -30,6 +30,13 @@ export class HaTextSelector extends LitElement {
@state() private _unmaskedPassword = false;
public async focus() {
await this.updateComplete;
(
this.renderRoot.querySelector("ha-textarea, ha-textfield") as HTMLElement
)?.focus();
}
protected render() {
if (this.selector.text?.multiline) {
return html`<ha-textarea

View File

@ -75,8 +75,9 @@ export class HaSelector extends LitElement {
@property() public context?: Record<string, any>;
public focus() {
this.shadowRoot?.getElementById("selector")?.focus();
public async focus() {
await this.updateComplete;
(this.renderRoot.querySelector("#selector") as HTMLElement)?.focus();
}
private get _type() {

View File

@ -96,7 +96,13 @@ export class HaSTTPicker extends LitElement {
private _debouncedUpdateEngines = debounce(() => this._updateEngines(), 500);
private async _updateEngines() {
this._engines = (await listSTTEngines(this.hass, this.language)).providers;
this._engines = (
await listSTTEngines(
this.hass,
this.language,
this.hass.config.country || undefined
)
).providers;
if (!this.value) {
return;

View File

@ -96,7 +96,13 @@ export class HaTTSPicker extends LitElement {
private _debouncedUpdateEngines = debounce(() => this._updateEngines(), 500);
private async _updateEngines() {
this._engines = (await listTTSEngines(this.hass, this.language)).providers;
this._engines = (
await listTTSEngines(
this.hass,
this.language,
this.hass.config.country || undefined
)
).providers;
if (!this.value) {
return;

View File

@ -10,7 +10,7 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { debounce } from "../common/util/debounce";
import { listTTSVoices } from "../data/tts";
import { listTTSVoices, TTSVoice } from "../data/tts";
import { HomeAssistant } from "../types";
import "./ha-list-item";
import "./ha-select";
@ -34,13 +34,14 @@ export class HaTTSVoicePicker extends LitElement {
@property({ type: Boolean }) public required = false;
@state() _voices?: string[] | null;
@state() _voices?: TTSVoice[] | null;
protected render() {
if (!this._voices) {
return nothing;
}
const value = this.value ?? (this.required ? this._voices[0] : NONE);
const value =
this.value ?? (this.required ? this._voices[0]?.voice_id : NONE);
return html`
<ha-select
.label=${this.label ||
@ -59,8 +60,8 @@ export class HaTTSVoicePicker extends LitElement {
</ha-list-item>`
: nothing}
${this._voices.map(
(voice) => html`<ha-list-item .value=${voice}>
${voice}
(voice) => html`<ha-list-item .value=${voice.voice_id}>
${voice.name}
</ha-list-item>`
)}
</ha-select>
@ -94,7 +95,10 @@ export class HaTTSVoicePicker extends LitElement {
return;
}
if (!this._voices || !this._voices.includes(this.value)) {
if (
!this._voices ||
!this._voices.find((voice) => voice.voice_id === this.value)
) {
this.value = undefined;
fireEvent(this, "value-changed", { value: this.value });
}

View File

@ -78,11 +78,13 @@ export const processConversationInput = (
export const listAgents = (
hass: HomeAssistant,
language?: string
language?: string,
country?: string
): Promise<{ agents: Agent[] }> =>
hass.callWS({
type: "conversation/agent/list",
language,
country,
});
export const getAgentInfo = (

View File

@ -25,9 +25,11 @@ export interface STTEngine {
export const listSTTEngines = (
hass: HomeAssistant,
language?: string
language?: string,
country?: string
): Promise<{ providers: STTEngine[] }> =>
hass.callWS({
type: "stt/engine/list",
language,
country,
});

View File

@ -31,18 +31,20 @@ export const getProviderFromTTSMediaSource = (mediaContentId: string) =>
export const listTTSEngines = (
hass: HomeAssistant,
language?: string
language?: string,
country?: string
): Promise<{ providers: TTSEngine[] }> =>
hass.callWS({
type: "tts/engine/list",
language,
country,
});
export const listTTSVoices = (
hass: HomeAssistant,
engine_id: string,
language: string
): Promise<{ voices: string[] | null }> =>
): Promise<{ voices: TTSVoice[] | null }> =>
hass.callWS({
type: "tts/engine/voices",
engine_id,

View File

@ -13,6 +13,12 @@ export class AssistPipelineDetailConfig extends LitElement {
@property() public supportedLanguages?: string[];
public async focus() {
await this.updateComplete;
const input = this.renderRoot?.querySelector("ha-form");
input?.focus();
}
private _schema = memoizeOne(
(supportedLanguages?: string[]) =>
[
@ -84,11 +90,8 @@ export class AssistPipelineDetailConfig extends LitElement {
margin-bottom: 4px;
}
p {
font-weight: normal;
color: var(--secondary-text-color);
font-size: 16px;
line-height: 24px;
letter-spacing: 0.5px;
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
margin-top: 0;
margin-bottom: 0;
}

View File

@ -1,9 +1,10 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { SchemaUnion } from "../../../../components/ha-form/types";
import { LocalizeKeys } from "../../../../common/translations/localize";
import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
import "../../../../components/ha-form/ha-form";
@customElement("assist-pipeline-detail-conversation")
export class AssistPipelineDetailConversation extends LitElement {
@ -29,24 +30,26 @@ export class AssistPipelineDetailConversation extends LitElement {
},
},
},
{
name: "conversation_language",
required: true,
selector: {
language: { languages: supportedLanguages ?? [] },
},
},
supportedLanguages?.length
? {
name: "conversation_language",
required: true,
selector: {
language: { languages: supportedLanguages },
},
}
: { name: "", type: "constant" },
] as const,
},
] as const
);
private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string =>
this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}`
);
private _computeLabel = (schema): string =>
schema.name
? this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}` as LocalizeKeys
)
: "";
protected render() {
return html`
@ -92,11 +95,8 @@ export class AssistPipelineDetailConversation extends LitElement {
margin-bottom: 4px;
}
p {
font-weight: normal;
color: var(--secondary-text-color);
font-size: 16px;
line-height: 24px;
letter-spacing: 0.5px;
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
margin-top: 0;
margin-bottom: 0;
}

View File

@ -1,9 +1,10 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { SchemaUnion } from "../../../../components/ha-form/types";
import { LocalizeKeys } from "../../../../common/translations/localize";
import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
import "../../../../components/ha-form/ha-form";
@customElement("assist-pipeline-detail-stt")
export class AssistPipelineDetailSTT extends LitElement {
@ -28,24 +29,26 @@ export class AssistPipelineDetailSTT extends LitElement {
},
},
},
{
name: "stt_language",
required: true,
selector: {
language: { languages: supportedLanguages ?? [] },
},
},
supportedLanguages?.length
? {
name: "stt_language",
required: true,
selector: {
language: { languages: supportedLanguages },
},
}
: { name: "", type: "constant" },
] as const,
},
] as const
);
private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string =>
this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}`
);
private _computeLabel = (schema): string =>
schema.name
? this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}` as LocalizeKeys
)
: "";
protected render() {
return html`
@ -91,11 +94,8 @@ export class AssistPipelineDetailSTT extends LitElement {
margin-bottom: 4px;
}
p {
font-weight: normal;
color: var(--secondary-text-color);
font-size: 16px;
line-height: 24px;
letter-spacing: 0.5px;
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
margin-top: 0;
margin-bottom: 0;
}

View File

@ -1,9 +1,10 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { SchemaUnion } from "../../../../components/ha-form/types";
import { LocalizeKeys } from "../../../../common/translations/localize";
import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
import "../../../../components/ha-form/ha-form";
@customElement("assist-pipeline-detail-tts")
export class AssistPipelineDetailTTS extends LitElement {
@ -28,13 +29,17 @@ export class AssistPipelineDetailTTS extends LitElement {
},
},
},
{
name: "tts_language",
selector: {
language: { languages: supportedLanguages ?? [] },
},
required: true,
},
supportedLanguages?.length
? {
name: "tts_language",
required: true,
selector: {
language: { languages: supportedLanguages },
},
}
: { name: "", type: "constant" },
{ name: "", type: "constant" },
{
name: "tts_voice",
selector: {
@ -48,12 +53,12 @@ export class AssistPipelineDetailTTS extends LitElement {
] as const
);
private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string =>
this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}`
);
private _computeLabel = (schema): string =>
schema.name
? this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}` as LocalizeKeys
)
: "";
protected render() {
return html`
@ -100,11 +105,8 @@ export class AssistPipelineDetailTTS extends LitElement {
margin-bottom: 4px;
}
p {
font-weight: normal;
color: var(--secondary-text-color);
font-size: 16px;
line-height: 24px;
letter-spacing: 0.5px;
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
margin-top: 0;
margin-bottom: 0;
}

View File

@ -41,7 +41,13 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
this._data = this._params.pipeline;
this._preferred = this._params.preferred;
} else {
this._data = {};
this._data = {
language: (
this.hass.config.language || this.hass.locale.language
).substring(0, 2),
stt_engine: "cloud",
tts_engine: "cloud",
};
}
}
@ -88,21 +94,26 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.hass=${this.hass}
.data=${this._data}
.supportedLanguages=${this._supportedLanguages}
keys="name,language"
@value-changed=${this._valueChanged}
dialogInitialFocus
></assist-pipeline-detail-config>
<assist-pipeline-detail-conversation
.hass=${this.hass}
.data=${this._data}
keys="conversation_engine,conversation_language"
@value-changed=${this._valueChanged}
></assist-pipeline-detail-conversation>
<assist-pipeline-detail-stt
.hass=${this.hass}
.data=${this._data}
keys="stt_engine,stt_language"
@value-changed=${this._valueChanged}
></assist-pipeline-detail-stt>
<assist-pipeline-detail-tts
.hass=${this.hass}
.data=${this._data}
keys="tts_engine,tts_language,tts_voice"
@value-changed=${this._valueChanged}
></assist-pipeline-detail-tts>
</div>
@ -151,31 +162,35 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
private _valueChanged(ev: CustomEvent) {
this._error = undefined;
const value = ev.detail.value;
this._data = value;
const value = {};
(ev.currentTarget as any)
.getAttribute("keys")
.split(",")
.forEach((key) => {
value[key] = ev.detail.value[key];
});
this._data = { ...this._data, ...value };
}
private async _updatePipeline() {
this._submitting = true;
try {
const data = this._data!;
const values: AssistPipelineMutableParams = {
name: data.name!,
language: data.language!,
conversation_engine: data.conversation_engine!,
conversation_language: data.conversation_language ?? null,
stt_engine: data.stt_engine ?? null,
stt_language: data.stt_language ?? null,
tts_engine: data.tts_engine ?? null,
tts_language: data.tts_language ?? null,
tts_voice: data.tts_voice ?? null,
};
if (this._params!.pipeline?.id) {
const data = this._data!;
const values: AssistPipelineMutableParams = {
name: data.name!,
language: data.language!,
conversation_engine: data.conversation_engine!,
conversation_language: data.conversation_language ?? null,
stt_engine: data.stt_engine ?? null,
stt_language: data.stt_language ?? null,
tts_engine: data.tts_engine ?? null,
tts_language: data.tts_language ?? null,
tts_voice: data.tts_voice ?? null,
};
await this._params!.updatePipeline(values);
} else {
await this._params!.createPipeline(
this._data as AssistPipelineMutableParams
);
await this._params!.createPipeline(values);
}
this.closeDialog();
} catch (err: any) {

View File

@ -413,6 +413,10 @@
"theme": "Theme",
"no_theme": "No theme"
},
"language-picker": {
"language": "Language",
"no_languages": "No languages available"
},
"tts-picker": {
"tts": "Text to Speech",
"none": "None"