diff --git a/src/data/assist_pipeline.ts b/src/data/assist_pipeline.ts index 3ee5a8a27a..e5f60c2d9c 100644 --- a/src/data/assist_pipeline.ts +++ b/src/data/assist_pipeline.ts @@ -18,6 +18,11 @@ export interface AssistPipeline { wake_word_id: string | null; } +export interface AssistDevice { + device_id: string; + pipeline_entity: string; +} + export interface AssistPipelineMutableParams { name: string; language: string; @@ -366,3 +371,8 @@ export const fetchAssistPipelineLanguages = (hass: HomeAssistant) => hass.callWS<{ languages: string[] }>({ type: "assist_pipeline/language/list", }); + +export const listAssistDevices = (hass: HomeAssistant) => + hass.callWS({ + type: "assist_pipeline/device/list", + }); diff --git a/src/panels/config/voice-assistants/assist-pref.ts b/src/panels/config/voice-assistants/assist-pref.ts index 7a39414f94..fa1350936f 100644 --- a/src/panels/config/voice-assistants/assist-pref.ts +++ b/src/panels/config/voice-assistants/assist-pref.ts @@ -12,9 +12,11 @@ import "../../../components/ha-list-item"; import "../../../components/ha-svg-icon"; import "../../../components/ha-switch"; import { + AssistDevice, AssistPipeline, createAssistPipeline, deleteAssistPipeline, + listAssistDevices, listAssistPipelines, setAssistPipelinePreferred, updateAssistPipeline, @@ -39,6 +41,8 @@ export class AssistPref extends LitElement { @state() private _preferred: string | null = null; + @state() private _devices: AssistDevice[] = []; + @property() public cloudStatus?: CloudStatus; protected firstUpdated(changedProps: PropertyValues) { @@ -48,6 +52,9 @@ export class AssistPref extends LitElement { this._pipelines = pipelines.pipelines; this._preferred = pipelines.preferred_pipeline; }); + listAssistDevices(this.hass).then((devices) => { + this._devices = devices; + }); } private _exposedEntitiesCount = memoizeOne( @@ -132,6 +139,18 @@ export class AssistPref extends LitElement { )} + ${this._devices?.length + ? html` + + + ${this.hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.assist_devices", + { number: this._devices.length } + )} + + + ` + : ""} `; diff --git a/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts b/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts new file mode 100644 index 0000000000..86c450f691 --- /dev/null +++ b/src/panels/config/voice-assistants/assist/ha-config-voice-assistants-assist-devices.ts @@ -0,0 +1,148 @@ +import { LitElement, PropertyValues, html } from "lit"; +import memoizeOne from "memoize-one"; +import { customElement, property, state } from "lit/decorators"; +import "../../../../layouts/hass-subpage"; +import "../../../../components/data-table/ha-data-table"; +import type { DataTableColumnContainer } from "../../../../components/data-table/ha-data-table"; +import { HomeAssistant } from "../../../../types"; +import { + AssistDevice, + AssistPipeline, + listAssistDevices, + listAssistPipelines, +} from "../../../../data/assist_pipeline"; +import { computeDeviceName } from "../../../../data/device_registry"; +import { navigate } from "../../../../common/navigate"; + +interface AssistDeviceExtra { + pipeline: string | undefined; + last_used: string | undefined; +} + +@customElement("ha-config-voice-assistants-assist-devices") +class AssistDevicesPage extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @state() private _pipelines: Record = {}; + + @state() private _preferred: string | null = null; + + @state() private _devices?: (AssistDevice | AssistDeviceExtra)[]; + + private _columns = memoizeOne( + ( + hass: HomeAssistant, + pipelines: Record, + preferred: string | null + ): DataTableColumnContainer => { + const columns: DataTableColumnContainer = { + name: { + title: hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.devices.device" + ), + width: "50%", + filterable: true, + sortable: true, + template: (assistDevice) => + computeDeviceName(hass.devices[assistDevice.device_id], hass), + }, + pipeline: { + title: hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.devices.pipeline" + ), + width: "30%", + filterable: true, + sortable: true, + template: (assistDevice) => { + let selected = hass.states[assistDevice.pipeline_entity].state; + if (!pipelines) { + return selected; + } + let isPreferred = false; + + if (selected === "preferred") { + isPreferred = true; + selected = preferred!; + } + + const name = pipelines[selected].name; + + return isPreferred + ? hass.localize("ui.components.pipeline-picker.preferred", { + preferred: name, + }) + : name; + }, + }, + area: { + title: hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.devices.area" + ), + width: "20%", + template: (assistDevice) => { + const device = hass.devices[assistDevice.device_id]; + return ( + (device && device.area_id && hass.areas[device.area_id]?.name) || + "" + ); + }, + }, + }; + + return columns; + } + ); + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + + listAssistPipelines(this.hass).then((pipelines) => { + const lookup: Record = {}; + for (const pipeline of pipelines.pipelines) { + lookup[pipeline.id] = pipeline; + } + + this._pipelines = lookup; + this._preferred = pipelines.preferred_pipeline; + }); + + listAssistDevices(this.hass).then((devices) => { + this._devices = devices; + }); + } + + render() { + return html` + + + + `; + } + + private _handleRowClicked(ev: CustomEvent) { + const device: AssistDevice = ev.detail.id; + navigate(`/config/devices/device/${device}`); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-voice-assistants-assist-devices": AssistDevicesPage; + } +} diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants.ts index 4c94317ca7..8c99c7808b 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants.ts @@ -75,6 +75,11 @@ class HaConfigVoiceAssistants extends HassRouterPage { tag: "assist-debug", load: () => import("./debug/assist-debug"), }, + assist: { + tag: "ha-config-voice-assistants-assist-devices", + load: () => + import("./assist/ha-config-voice-assistants-assist-devices"), + }, }, }; diff --git a/src/translations/en.json b/src/translations/en.json index adee4f24b8..0ef5836078 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2257,10 +2257,17 @@ "pipeline": { "add_assistant": "Add assistant", "exposed_entities": "[%key:ui::panel::config::cloud::account::google::exposed_entities%]", + "assist_devices": "{number} Assist {number, plural,\n one {device}\n other {devices}\n}", "delete": { "confirm_title": "Delete {name}?", "confirm_text": "{name} will be permanently deleted." }, + "devices": { + "title": "Assist devices", + "device": "Device", + "pipeline": "Assistant", + "area": "Area" + }, "detail": { "update_assistant_action": "Update", "add_assistant_title": "Add assistant", @@ -2269,7 +2276,7 @@ "debug": "Debug", "set_as_preferred": "Set as preferred", "form": { - "name": "Name", + "name": "[%key:ui::common::name%]", "conversation_engine": "Conversation agent", "conversation_language": "[%key:ui::panel::config::voice_assistants::assistants::pipeline::detail::form::language%]", "language": "Language",