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": {