From 51acd271ea16853e44c70d75699a7cb549bee231 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Dec 2025 12:12:57 +0000 Subject: [PATCH] Add duplicate voice assistant action (#28511) --- .../config/voice-assistants/assist-pref.ts | 65 +++++++++++++++---- .../dialog-voice-assistant-pipeline-detail.ts | 47 ++++++++++---- ...-dialog-voice-assistant-pipeline-detail.ts | 4 +- src/translations/en.json | 4 ++ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/panels/config/voice-assistants/assist-pref.ts b/src/panels/config/voice-assistants/assist-pref.ts index 4ce49e05b6..e39197669f 100644 --- a/src/panels/config/voice-assistants/assist-pref.ts +++ b/src/panels/config/voice-assistants/assist-pref.ts @@ -1,6 +1,7 @@ import { mdiBug, mdiCommentProcessingOutline, + mdiContentDuplicate, mdiDotsVertical, mdiHelpCircle, mdiPlus, @@ -189,6 +190,17 @@ export class AssistPref extends LitElement { )} + + ${this.hass.localize("ui.common.duplicate")} + + res.id === id); + if (!pipeline) { + showAlertDialog(this, { + text: this.hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.duplicate.error_pipeline_not_found" + ), + }); + return; + } + + const { id: _id, ...pipelineWithoutId } = pipeline; + const newPipeline = { + ...pipelineWithoutId, + name: this.hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.duplicate.name", + { name: pipeline.name } + ), + }; + + this._openDialog(newPipeline); + } + private async _deletePipeline(ev) { const id = ev.currentTarget.id as string; if (this._preferred === id) { @@ -337,7 +373,9 @@ export class AssistPref extends LitElement { this._openDialog(); } - private async _openDialog(pipeline?: AssistPipeline): Promise { + private async _openDialog( + pipeline?: AssistPipeline | Omit + ): Promise { showVoiceAssistantPipelineDetailDialog(this, { cloudActiveSubscription: this.cloudStatus?.logged_in && this.cloudStatus.active_subscription, @@ -346,16 +384,21 @@ export class AssistPref extends LitElement { const created = await createAssistPipeline(this.hass!, values); this._pipelines = this._pipelines!.concat(created); }, - updatePipeline: async (values) => { - const updated = await updateAssistPipeline( - this.hass!, - pipeline!.id, - values - ); - this._pipelines = this._pipelines!.map((res) => - res === pipeline ? updated : res - ); - }, + ...(pipeline && "id" in pipeline + ? { + updatePipeline: async (values) => { + const updated = await updateAssistPipeline( + this.hass, + pipeline.id, + values + ); + const pipelineToUpdate = pipeline as AssistPipeline; + this._pipelines = this._pipelines!.map((res) => + res.id === pipelineToUpdate.id ? updated : res + ); + }, + } + : {}), }); } 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 5a65b3d96b..d044b45144 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 @@ -48,7 +48,11 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { this._error = undefined; this._cloudActive = this._params.cloudActiveSubscription; - if (this._params.pipeline) { + if ( + this._params.pipeline && + "id" in this._params.pipeline && + this._params.pipeline.id + ) { this._data = { prefer_local_intents: false, ...this._params.pipeline }; this._hideWakeWord = @@ -79,11 +83,15 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { } } this._data = { - language: ( - this.hass.config.language || this.hass.locale.language - ).substring(0, 2), - stt_engine: sstDefault, - tts_engine: ttsDefault, + ...(this._params.pipeline || {}), + language: + this._params.pipeline?.language || + (this.hass.config.language || this.hass.locale.language).substring( + 0, + 2 + ), + stt_engine: this._params.pipeline?.stt_engine || sstDefault, + tts_engine: this._params.pipeline?.tts_engine || ttsDefault, }; } @@ -112,11 +120,17 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { return nothing; } - const title = this._params.pipeline?.id - ? this._params.pipeline.name - : this.hass.localize( - "ui.panel.config.voice_assistants.assistants.pipeline.detail.add_assistant_title" - ); + const isExistingPipeline = + this._params.pipeline && + "id" in this._params.pipeline && + !!this._params.pipeline.id; + + const title = + isExistingPipeline && this._params.pipeline?.name + ? this._params.pipeline.name + : this.hass.localize( + "ui.panel.config.voice_assistants.assistants.pipeline.detail.add_assistant_title" + ); return html` - ${this._params.pipeline?.id + ${isExistingPipeline ? this.hass.localize( "ui.panel.config.voice_assistants.assistants.pipeline.detail.update_assistant_action" ) @@ -263,7 +277,12 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement { wake_word_entity: data.wake_word_entity ?? null, wake_word_id: data.wake_word_id ?? null, }; - if (this._params!.pipeline?.id) { + if ( + this._params!.pipeline && + "id" in this._params!.pipeline && + !!this._params!.pipeline.id && + this._params!.updatePipeline + ) { await this._params!.updatePipeline(values); } else if (this._params!.createPipeline) { await this._params!.createPipeline(values); diff --git a/src/panels/config/voice-assistants/show-dialog-voice-assistant-pipeline-detail.ts b/src/panels/config/voice-assistants/show-dialog-voice-assistant-pipeline-detail.ts index 887763e08d..a43983c360 100644 --- a/src/panels/config/voice-assistants/show-dialog-voice-assistant-pipeline-detail.ts +++ b/src/panels/config/voice-assistants/show-dialog-voice-assistant-pipeline-detail.ts @@ -6,9 +6,9 @@ import type { export interface VoiceAssistantPipelineDetailsDialogParams { cloudActiveSubscription?: boolean; - pipeline?: AssistPipeline; + pipeline?: AssistPipeline | Omit; hideWakeWord?: boolean; - updatePipeline: (updates: AssistPipelineMutableParams) => Promise; + updatePipeline?: (updates: AssistPipelineMutableParams) => Promise; createPipeline?: (values: AssistPipelineMutableParams) => Promise; } diff --git a/src/translations/en.json b/src/translations/en.json index 736384f565..057620a1ed 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3688,6 +3688,10 @@ }, "no_cloud_message": "You should have an active cloud subscription to use cloud speech services.", "no_cloud_action": "Subscribe" + }, + "duplicate": { + "error_pipeline_not_found": "Pipeline not found", + "name": "{name} (Copy)" } }, "cloud": {