diff --git a/src/components/ha-suggest-with-ai-button.ts b/src/components/ha-suggest-with-ai-button.ts
new file mode 100644
index 0000000000..ff63e5f36e
--- /dev/null
+++ b/src/components/ha-suggest-with-ai-button.ts
@@ -0,0 +1,86 @@
+import type { PropertyValues } from "lit";
+import { html, LitElement, nothing } from "lit";
+import { mdiStarFourPoints } from "@mdi/js";
+
+import { customElement, state, property } from "lit/decorators";
+import type {
+ AITaskPreferences,
+ GenDataTask,
+ GenDataTaskResult,
+} from "../data/ai_task";
+import { fetchAITaskPreferences, generateDataAITask } from "../data/ai_task";
+import "./chips/ha-assist-chip";
+import type { HomeAssistant } from "../types";
+import { fireEvent } from "../common/dom/fire_event";
+import { isComponentLoaded } from "../common/config/is_component_loaded";
+
+declare global {
+ interface HASSDomEvents {
+ suggestion: GenDataTaskResult;
+ }
+}
+
+@customElement("ha-suggest-with-ai-button")
+export class HaSuggestWithAIButton extends LitElement {
+ @property({ attribute: false })
+ public hass!: HomeAssistant;
+
+ @property({ attribute: "task-type" })
+ public taskType!: "data";
+
+ @property({ attribute: false })
+ generateTask!: () => GenDataTask;
+
+ @state()
+ private _aiPrefs?: AITaskPreferences;
+
+ @state()
+ private _suggesting = false;
+
+ protected firstUpdated(_changedProperties: PropertyValues): void {
+ super.firstUpdated(_changedProperties);
+ if (!this.hass || !isComponentLoaded(this.hass, "ai_task")) {
+ return;
+ }
+ fetchAITaskPreferences(this.hass).then((prefs) => {
+ this._aiPrefs = prefs;
+ });
+ }
+
+ render() {
+ if (!this._aiPrefs || !this._aiPrefs.gen_data_entity_id) {
+ return nothing;
+ }
+ return html`
+
+
+
+ `;
+ }
+
+ private async _suggest() {
+ if (!this.generateTask || this._suggesting) {
+ return;
+ }
+ try {
+ this._suggesting = true;
+ const task = await this.generateTask();
+ const result = await generateDataAITask(this.hass, task);
+ fireEvent(this, "suggestion", result);
+ } finally {
+ this._suggesting = false;
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-suggest-with-ai-button": HaSuggestWithAIButton;
+ }
+}
diff --git a/src/data/ai_task.ts b/src/data/ai_task.ts
index c89f6607fe..83f0293e7a 100644
--- a/src/data/ai_task.ts
+++ b/src/data/ai_task.ts
@@ -5,6 +5,13 @@ export interface AITaskPreferences {
gen_data_entity_id: string | null;
}
+export interface GenDataTask {
+ task_name: string;
+ entity_id?: string;
+ instructions: string;
+ structure?: AITaskStructure;
+}
+
export interface GenDataTaskResult {
conversation_id: string;
data: T;
@@ -34,12 +41,7 @@ export const saveAITaskPreferences = (
export const generateDataAITask = async (
hass: HomeAssistant,
- task: {
- task_name: string;
- entity_id?: string;
- instructions: string;
- structure?: AITaskStructure;
- }
+ task: GenDataTask
): Promise> => {
const result = await hass.callService>(
"ai_task",
diff --git a/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts b/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts
index c888f0956d..7eb1b3f89e 100644
--- a/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts
+++ b/src/panels/config/automation/automation-save-dialog/dialog-automation-save.ts
@@ -11,6 +11,7 @@ import "../../../../components/ha-icon-picker";
import "../../../../components/ha-textarea";
import "../../../../components/ha-textfield";
import "../../../../components/ha-labels-picker";
+import "../../../../components/ha-suggest-with-ai-button";
import "../../category/ha-category-picker";
import "../../../../components/ha-expansion-panel";
import "../../../../components/chips/ha-chip-set";
@@ -27,6 +28,8 @@ import type {
import { supportsMarkdownHelper } from "../../../../common/translations/markdown_support";
import {
fetchAITaskPreferences,
+ GenDataTask,
+ GenDataTaskResult,
generateDataAITask,
} from "../../../../data/ai_task";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
@@ -48,8 +51,6 @@ class DialogAutomationSave extends LitElement implements HassDialog {
@state() private _entryUpdates!: EntityRegistryUpdate;
- @state() private _canSuggest = false;
-
private _params!: SaveDialogParams;
@state() private _newName?: string;
@@ -94,15 +95,6 @@ class DialogAutomationSave extends LitElement implements HassDialog {
return true;
}
- protected firstUpdated(changedProperties: PropertyValues): void {
- super.firstUpdated(changedProperties);
- if (isComponentLoaded(this.hass, "ai_task")) {
- fetchAITaskPreferences(this.hass).then((prefs) => {
- this._canSuggest = prefs.gen_data_entity_id !== null;
- });
- }
- }
-
protected _renderOptionalChip(id: string, label: string) {
if (this._visibleOptionals.includes(id)) {
return nothing;
@@ -272,21 +264,12 @@ class DialogAutomationSave extends LitElement implements HassDialog {
.path=${mdiClose}
>
${this._params.title || title}
- ${this._canSuggest
- ? html`
-
-
-
- `
- : nothing}
+
${this._error
? html`
Object.fromEntries(labs.map((lab) => [lab.label_id, lab.name]))
),
@@ -362,6 +345,10 @@ class DialogAutomationSave extends LitElement implements HassDialog {
Object.fromEntries(cats.map((cat) => [cat.category_id, cat.name]))
),
]);
+ }
+
+ private _generateTask = async (): Promise => {
+ const [labels, entities, categories] = await this._getSuggestData();
const automationInspiration: string[] = [];
for (const automation of Object.values(this.hass.states)) {
@@ -391,12 +378,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
automationInspiration.push(inspiration);
}
- const result = await generateDataAITask<{
- name: string;
- description: string | undefined;
- labels: string[] | undefined;
- category: string | undefined;
- }>(this.hass, {
+ return {
task_name: "frontend:automation:save",
instructions: `Suggest in language "${this.hass.language}" a name, description, category and labels for the following Home Assistant automation.
@@ -454,7 +436,22 @@ ${dump(this._params.config)}
},
},
},
- });
+ };
+ };
+
+ private async _handleSuggestion(
+ event: CustomEvent<
+ GenDataTaskResult<{
+ name: string;
+ description?: string;
+ category?: string;
+ labels?: string[];
+ }>
+ >
+ ) {
+ const result = event.detail;
+ const [labels, _entities, categories] = await this._getSuggestData();
+
this._newName = result.data.name;
if (result.data.description) {
this._newDescription = result.data.description;
@@ -463,6 +460,7 @@ ${dump(this._params.config)}
}
}
if (result.data.category) {
+ // TODO search up category ID
this._entryUpdates = {
...this._entryUpdates,
category: result.data.category,
@@ -570,7 +568,7 @@ ${dump(this._params.config)}
--mdc-theme-primary: var(--error-color);
}
- #suggest {
+ ha-suggest-with-ai-button {
margin: 8px 16px;
}
`,
diff --git a/src/translations/en.json b/src/translations/en.json
index 4d556e38d7..c999d3242e 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -437,7 +437,8 @@
"append": "Append",
"supports_markdown": "Supports {markdown_help_link}",
"markdown": "Markdown",
- "suggest_ai": "Suggest with AI"
+ "suggest_ai": "Suggest with AI",
+ "suggesting_ai": "Suggesting…"
},
"components": {