mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-13 04:50:29 +00:00
* also assign category * Extract Suggest AI button * Add sick animation * Show AI Task pref but disabled if not done loading * Lint * Update progress wording * Define better interface * Add My panel * Adjust instructions to params.domain * Mention sentence capitalization * Update label when failure * Keep width during suggestion
202 lines
5.1 KiB
TypeScript
202 lines
5.1 KiB
TypeScript
import type { PropertyValues } from "lit";
|
|
import { html, css, 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 "./ha-svg-icon";
|
|
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;
|
|
}
|
|
}
|
|
|
|
export interface SuggestWithAIGenerateTask {
|
|
type: "data";
|
|
task: GenDataTask;
|
|
}
|
|
|
|
@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!: () => SuggestWithAIGenerateTask;
|
|
|
|
@state()
|
|
private _aiPrefs?: AITaskPreferences;
|
|
|
|
@state()
|
|
private _state: {
|
|
status: "idle" | "suggesting" | "error" | "done";
|
|
suggestionIndex: 1 | 2 | 3;
|
|
} = {
|
|
status: "idle",
|
|
suggestionIndex: 1,
|
|
};
|
|
|
|
@state()
|
|
private _minWidth?: string;
|
|
|
|
private _intervalId?: number;
|
|
|
|
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;
|
|
}
|
|
|
|
let label: string;
|
|
switch (this._state.status) {
|
|
case "error":
|
|
label = this.hass.localize("ui.components.suggest_with_ai.error");
|
|
break;
|
|
case "done":
|
|
label = this.hass.localize("ui.components.suggest_with_ai.done");
|
|
break;
|
|
case "suggesting":
|
|
label = this.hass.localize(
|
|
`ui.components.suggest_with_ai.suggesting_${this._state.suggestionIndex}`
|
|
);
|
|
break;
|
|
default:
|
|
label = this.hass.localize("ui.components.suggest_with_ai.label");
|
|
}
|
|
|
|
return html`
|
|
<ha-assist-chip
|
|
@click=${this._suggest}
|
|
.label=${label}
|
|
?active=${this._state.status === "suggesting"}
|
|
class=${this._state.status === "error"
|
|
? "error"
|
|
: this._state.status === "done"
|
|
? "done"
|
|
: ""}
|
|
style=${this._minWidth ? `min-width: ${this._minWidth}` : ""}
|
|
>
|
|
<ha-svg-icon slot="icon" .path=${mdiStarFourPoints}></ha-svg-icon>
|
|
</ha-assist-chip>
|
|
`;
|
|
}
|
|
|
|
private async _suggest() {
|
|
if (!this.generateTask || this._state.status === "suggesting") {
|
|
return;
|
|
}
|
|
|
|
// Capture current width before changing state
|
|
const chip = this.shadowRoot?.querySelector("ha-assist-chip");
|
|
if (chip) {
|
|
this._minWidth = `${chip.offsetWidth}px`;
|
|
}
|
|
|
|
// Reset to suggesting state
|
|
this._state = {
|
|
status: "suggesting",
|
|
suggestionIndex: 1,
|
|
};
|
|
|
|
try {
|
|
// Start cycling through suggestion texts
|
|
this._intervalId = window.setInterval(() => {
|
|
this._state = {
|
|
...this._state,
|
|
suggestionIndex: ((this._state.suggestionIndex % 3) + 1) as 1 | 2 | 3,
|
|
};
|
|
}, 3000);
|
|
|
|
const info = await this.generateTask();
|
|
let result: GenDataTaskResult;
|
|
if (info.type === "data") {
|
|
result = await generateDataAITask(this.hass, info.task);
|
|
} else {
|
|
throw new Error("Unsupported task type");
|
|
}
|
|
|
|
fireEvent(this, "suggestion", result);
|
|
|
|
// Show success state
|
|
this._state = {
|
|
...this._state,
|
|
status: "done",
|
|
};
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("Error generating AI suggestion:", error);
|
|
|
|
this._state = {
|
|
...this._state,
|
|
status: "error",
|
|
};
|
|
} finally {
|
|
if (this._intervalId) {
|
|
clearInterval(this._intervalId);
|
|
this._intervalId = undefined;
|
|
}
|
|
setTimeout(() => {
|
|
this._state = {
|
|
...this._state,
|
|
status: "idle",
|
|
};
|
|
this._minWidth = undefined;
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
static styles = css`
|
|
ha-assist-chip[active] {
|
|
animation: pulse-glow 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
ha-assist-chip.error {
|
|
box-shadow: 0 0 12px 4px rgba(var(--rgb-error-color), 0.8);
|
|
}
|
|
|
|
ha-assist-chip.done {
|
|
box-shadow: 0 0 12px 4px rgba(var(--rgb-primary-color), 0.8);
|
|
}
|
|
|
|
@keyframes pulse-glow {
|
|
0% {
|
|
box-shadow: 0 0 0 0 rgba(var(--rgb-primary-color), 0);
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 8px 2px rgba(var(--rgb-primary-color), 0.6);
|
|
}
|
|
100% {
|
|
box-shadow: 0 0 0 0 rgba(var(--rgb-primary-color), 0);
|
|
}
|
|
}
|
|
`;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-suggest-with-ai-button": HaSuggestWithAIButton;
|
|
}
|
|
}
|