diff --git a/src/components/ha-conversation-agent-picker.ts b/src/components/ha-conversation-agent-picker.ts new file mode 100644 index 0000000000..5175e99f35 --- /dev/null +++ b/src/components/ha-conversation-agent-picker.ts @@ -0,0 +1,109 @@ +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValueMap, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; +import { Agent, listAgents } from "../data/conversation"; +import { HomeAssistant } from "../types"; +import "./ha-list-item"; +import "./ha-select"; +import type { HaSelect } from "./ha-select"; + +const DEFAULT = "default_agent_option"; +@customElement("ha-conversation-agent-picker") +export class HaConversationAgentPicker extends LitElement { + @property() public value?: string; + + @property() public label?: string; + + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) public disabled = false; + + @property({ type: Boolean }) public required = false; + + @state() _agents?: Agent[]; + + @state() _defaultAgent: string | null = null; + + protected render() { + if (!this._agents) { + return nothing; + } + const value = this.value ?? DEFAULT; + return html` + + + ${this.hass!.localize( + "ui.components.coversation-agent-picker.default", + { + default: this._agents.find( + (agent) => agent.id === this._defaultAgent + )?.name, + } + )} + + ${this._agents.map( + (agent) => + html`${agent.name}` + )} + + `; + } + + protected firstUpdated( + changedProperties: PropertyValueMap | Map + ): void { + super.firstUpdated(changedProperties); + listAgents(this.hass).then((agents) => { + this._agents = agents.agents; + this._defaultAgent = agents.default_agent; + }); + } + + static get styles(): CSSResultGroup { + return css` + ha-select { + width: 100%; + } + `; + } + + private _changed(ev): void { + const target = ev.target as HaSelect; + if ( + !this.hass || + target.value === "" || + target.value === this.value || + (this.value === undefined && target.value === DEFAULT) + ) { + return; + } + this.value = target.value === DEFAULT ? undefined : target.value; + fireEvent(this, "value-changed", { value: this.value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-conversation-agent-picker": HaConversationAgentPicker; + } +} diff --git a/src/components/ha-selector/ha-selector-conversation-agent.ts b/src/components/ha-selector/ha-selector-conversation-agent.ts new file mode 100644 index 0000000000..21dcfe12d7 --- /dev/null +++ b/src/components/ha-selector/ha-selector-conversation-agent.ts @@ -0,0 +1,45 @@ +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { ConversationAgentSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "../ha-conversation-agent-picker"; + +@customElement("ha-selector-conversation_agent") +export class HaConversationAgentSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: ConversationAgentSelector; + + @property() public value?: any; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + protected render() { + return html``; + } + + static styles = css` + ha-conversation-agent-picker { + width: 100%; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-conversation_agent": HaConversationAgentSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index f387867025..7d4e456e60 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -17,6 +17,7 @@ const LOAD_ELEMENTS = { boolean: () => import("./ha-selector-boolean"), color_rgb: () => import("./ha-selector-color-rgb"), config_entry: () => import("./ha-selector-config-entry"), + conversation_agent: () => import("./ha-selector-conversation-agent"), constant: () => import("./ha-selector-constant"), date: () => import("./ha-selector-date"), datetime: () => import("./ha-selector-datetime"), diff --git a/src/components/ha-theme-picker.ts b/src/components/ha-theme-picker.ts index 95c568f592..47aadd7e62 100644 --- a/src/components/ha-theme-picker.ts +++ b/src/components/ha-theme-picker.ts @@ -1,4 +1,3 @@ -import "@material/mwc-button"; import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; diff --git a/src/data/conversation.ts b/src/data/conversation.ts index 3745271c8b..d74c64d0f6 100644 --- a/src/data/conversation.ts +++ b/src/data/conversation.ts @@ -56,6 +56,11 @@ export interface AgentInfo { attribution?: { name: string; url: string }; } +export interface Agent { + id: string; + name: string; +} + export const processConversationInput = ( hass: HomeAssistant, text: string, @@ -70,9 +75,20 @@ export const processConversationInput = ( language, }); -export const getAgentInfo = (hass: HomeAssistant): Promise => +export const listAgents = ( + hass: HomeAssistant +): Promise<{ agents: Agent[]; default_agent: string | null }> => + hass.callWS({ + type: "conversation/agent/list", + }); + +export const getAgentInfo = ( + hass: HomeAssistant, + agent_id?: string +): Promise => hass.callWS({ type: "conversation/agent/info", + agent_id, }); export const prepareConversation = ( diff --git a/src/data/selector.ts b/src/data/selector.ts index 3ee0d020b7..1282323595 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -14,6 +14,7 @@ export type Selector = | BooleanSelector | ColorRGBSelector | ColorTempSelector + | ConversationAgentSelector | ConfigEntrySelector | ConstantSelector | DateSelector @@ -85,6 +86,11 @@ export interface ColorTempSelector { } | null; } +export interface ConversationAgentSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + conversation_agent: {} | null; +} + export interface ConfigEntrySelector { config_entry: { integration?: string; diff --git a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts index 64b4b9efb5..15861616e2 100644 --- a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts +++ b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts @@ -125,7 +125,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { name: "conversation_engine", required: true, selector: { - text: {}, + conversation_agent: {}, }, }, { diff --git a/src/translations/en.json b/src/translations/en.json index 6ee28472e1..7f6adbabb4 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -401,6 +401,10 @@ "config-entry-picker": { "config_entry": "Integration" }, + "coversation-agent-picker": { + "conversation_agent": "Conversation agent", + "default": "Default agent ({default})" + }, "theme-picker": { "theme": "Theme", "no_theme": "No theme"