Compare commits

...

1 Commits

Author SHA1 Message Date
Aidan Timson
77e4329b0d Add ability to duplicate voice assistant 2025-12-11 16:03:42 +00:00
4 changed files with 89 additions and 27 deletions

View File

@@ -1,6 +1,7 @@
import {
mdiBug,
mdiCommentProcessingOutline,
mdiContentDuplicate,
mdiDotsVertical,
mdiHelpCircle,
mdiPlus,
@@ -189,6 +190,17 @@ export class AssistPref extends LitElement {
)}
<ha-svg-icon slot="graphic" .path=${mdiBug}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
.id=${pipeline.id}
@request-selected=${this._duplicatePipeline}
>
${this.hass.localize("ui.common.duplicate")}
<ha-svg-icon
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</ha-list-item>
<ha-list-item
class="danger"
graphic="icon"
@@ -294,6 +306,27 @@ export class AssistPref extends LitElement {
navigate(`/config/voice-assistants/debug/${id}`);
}
private async _duplicatePipeline(ev) {
const id = ev.currentTarget.id as string;
const pipeline = this._pipelines.find((res) => 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: `${pipeline.name} (Copy)`,
};
this._openDialog(newPipeline);
}
private async _deletePipeline(ev) {
const id = ev.currentTarget.id as string;
if (this._preferred === id) {
@@ -337,7 +370,9 @@ export class AssistPref extends LitElement {
this._openDialog();
}
private async _openDialog(pipeline?: AssistPipeline): Promise<void> {
private async _openDialog(
pipeline?: AssistPipeline | Omit<AssistPipeline, "id">
): Promise<void> {
showVoiceAssistantPipelineDetailDialog(this, {
cloudActiveSubscription:
this.cloudStatus?.logged_in && this.cloudStatus.active_subscription,
@@ -346,16 +381,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
);
},
}
: {}),
});
}

View File

@@ -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`
<ha-dialog
@@ -166,7 +180,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.supportedLanguages=${this._supportedLanguages}
keys="name,language"
@value-changed=${this._valueChanged}
?dialogInitialFocus=${!this._params.pipeline?.id}
?dialogInitialFocus=${!isExistingPipeline}
></assist-pipeline-detail-config>
<assist-pipeline-detail-conversation
.hass=${this.hass}
@@ -217,7 +231,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.loading=${this._submitting}
dialogInitialFocus
>
${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);

View File

@@ -6,9 +6,9 @@ import type {
export interface VoiceAssistantPipelineDetailsDialogParams {
cloudActiveSubscription?: boolean;
pipeline?: AssistPipeline;
pipeline?: AssistPipeline | Omit<AssistPipeline, "id">;
hideWakeWord?: boolean;
updatePipeline: (updates: AssistPipelineMutableParams) => Promise<unknown>;
updatePipeline?: (updates: AssistPipelineMutableParams) => Promise<unknown>;
createPipeline?: (values: AssistPipelineMutableParams) => Promise<unknown>;
}

View File

@@ -3688,6 +3688,9 @@
},
"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"
}
},
"cloud": {