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 { import {
mdiBug, mdiBug,
mdiCommentProcessingOutline, mdiCommentProcessingOutline,
mdiContentDuplicate,
mdiDotsVertical, mdiDotsVertical,
mdiHelpCircle, mdiHelpCircle,
mdiPlus, mdiPlus,
@@ -189,6 +190,17 @@ export class AssistPref extends LitElement {
)} )}
<ha-svg-icon slot="graphic" .path=${mdiBug}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiBug}></ha-svg-icon>
</ha-list-item> </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 <ha-list-item
class="danger" class="danger"
graphic="icon" graphic="icon"
@@ -294,6 +306,27 @@ export class AssistPref extends LitElement {
navigate(`/config/voice-assistants/debug/${id}`); 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) { private async _deletePipeline(ev) {
const id = ev.currentTarget.id as string; const id = ev.currentTarget.id as string;
if (this._preferred === id) { if (this._preferred === id) {
@@ -337,7 +370,9 @@ export class AssistPref extends LitElement {
this._openDialog(); this._openDialog();
} }
private async _openDialog(pipeline?: AssistPipeline): Promise<void> { private async _openDialog(
pipeline?: AssistPipeline | Omit<AssistPipeline, "id">
): Promise<void> {
showVoiceAssistantPipelineDetailDialog(this, { showVoiceAssistantPipelineDetailDialog(this, {
cloudActiveSubscription: cloudActiveSubscription:
this.cloudStatus?.logged_in && this.cloudStatus.active_subscription, this.cloudStatus?.logged_in && this.cloudStatus.active_subscription,
@@ -346,16 +381,21 @@ export class AssistPref extends LitElement {
const created = await createAssistPipeline(this.hass!, values); const created = await createAssistPipeline(this.hass!, values);
this._pipelines = this._pipelines!.concat(created); this._pipelines = this._pipelines!.concat(created);
}, },
updatePipeline: async (values) => { ...(pipeline && "id" in pipeline
const updated = await updateAssistPipeline( ? {
this.hass!, updatePipeline: async (values) => {
pipeline!.id, const updated = await updateAssistPipeline(
values this.hass!,
); pipeline.id,
this._pipelines = this._pipelines!.map((res) => values
res === pipeline ? updated : res );
); 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._error = undefined;
this._cloudActive = this._params.cloudActiveSubscription; 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._data = { prefer_local_intents: false, ...this._params.pipeline };
this._hideWakeWord = this._hideWakeWord =
@@ -79,11 +83,15 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
} }
} }
this._data = { this._data = {
language: ( ...(this._params.pipeline || {}),
this.hass.config.language || this.hass.locale.language language:
).substring(0, 2), this._params.pipeline?.language ||
stt_engine: sstDefault, (this.hass.config.language || this.hass.locale.language).substring(
tts_engine: ttsDefault, 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; return nothing;
} }
const title = this._params.pipeline?.id const isExistingPipeline =
? this._params.pipeline.name this._params.pipeline &&
: this.hass.localize( "id" in this._params.pipeline &&
"ui.panel.config.voice_assistants.assistants.pipeline.detail.add_assistant_title" !!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` return html`
<ha-dialog <ha-dialog
@@ -166,7 +180,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.supportedLanguages=${this._supportedLanguages} .supportedLanguages=${this._supportedLanguages}
keys="name,language" keys="name,language"
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
?dialogInitialFocus=${!this._params.pipeline?.id} ?dialogInitialFocus=${!isExistingPipeline}
></assist-pipeline-detail-config> ></assist-pipeline-detail-config>
<assist-pipeline-detail-conversation <assist-pipeline-detail-conversation
.hass=${this.hass} .hass=${this.hass}
@@ -217,7 +231,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
.loading=${this._submitting} .loading=${this._submitting}
dialogInitialFocus dialogInitialFocus
> >
${this._params.pipeline?.id ${isExistingPipeline
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.update_assistant_action" "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_entity: data.wake_word_entity ?? null,
wake_word_id: data.wake_word_id ?? 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); await this._params!.updatePipeline(values);
} else if (this._params!.createPipeline) { } else if (this._params!.createPipeline) {
await this._params!.createPipeline(values); await this._params!.createPipeline(values);

View File

@@ -6,9 +6,9 @@ import type {
export interface VoiceAssistantPipelineDetailsDialogParams { export interface VoiceAssistantPipelineDetailsDialogParams {
cloudActiveSubscription?: boolean; cloudActiveSubscription?: boolean;
pipeline?: AssistPipeline; pipeline?: AssistPipeline | Omit<AssistPipeline, "id">;
hideWakeWord?: boolean; hideWakeWord?: boolean;
updatePipeline: (updates: AssistPipelineMutableParams) => Promise<unknown>; updatePipeline?: (updates: AssistPipelineMutableParams) => Promise<unknown>;
createPipeline?: (values: 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_message": "You should have an active cloud subscription to use cloud speech services.",
"no_cloud_action": "Subscribe" "no_cloud_action": "Subscribe"
},
"duplicate": {
"error_pipeline_not_found": "Pipeline not found"
} }
}, },
"cloud": { "cloud": {