Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MVP voice assist flow #22061

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/static/icons/casita/loading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/icons/casita/loving.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/icons/casita/normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/icons/casita/sad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/icons/casita/sleeping.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/icons/casita/smiling.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions src/data/assist_satellite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { HassEntity } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { supportsFeature } from "../common/entity/supports-feature";

export const enum AssistSatelliteEntityFeature {
ANNOUNCE = 1,
}
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Convert const enum to regular enum for better compatibility

The const keyword on the enum declaration can lead to compatibility issues with bundlers and the 'isolatedModules' mode in TypeScript, as pointed out by the static analysis tool.

To address this, please change the enum declaration to a regular enum:

export enum AssistSatelliteEntityFeature {
  ANNOUNCE = 1,
}

This change ensures better compatibility while maintaining the same functionality.

Tools
Biome

[error] 6-8: The enum declaration should not be const

Const enums are not supported by bundlers and are incompatible with the 'isolatedModules' mode. Their use can lead to import inexistent values.
See TypeScript Docs for more details.
Safe fix: Turn the const enum into a regular enum.

(lint/suspicious/noConstEnum)


export interface WakeWordInterceptMessage {
wake_word_phrase: string;
}

export interface WakeWordOption {
id: string;
wake_word: string;
trained_languages: string[];
}

export interface AssistSatelliteConfiguration {
active_wake_words: string[];
available_wake_words: WakeWordOption[];
max_active_wake_words: number;
pipeline_entity_id: string;
vad_entity_id: string;
}

export const interceptWakeWord = (
hass: HomeAssistant,
entity_id: string,
callback: (result: WakeWordInterceptMessage) => void
) =>
hass.connection.subscribeMessage(callback, {
type: "assist_satellite/intercept_wake_word",
entity_id,
});

export const testAssistSatelliteConnection = (
hass: HomeAssistant,
entity_id: string
) =>
hass.callWS<{
status: "success" | "timeout";
}>({
type: "assist_satellite/test_connection",
entity_id,
});

export const assistSatelliteAnnounce = (
hass: HomeAssistant,
entity_id: string,
message: string
) =>
hass.callService("assist_satellite", "announce", { message }, { entity_id });

export const fetchAssistSatelliteConfiguration = (
hass: HomeAssistant,
entity_id: string
) =>
hass.callWS<AssistSatelliteConfiguration>({
type: "assist_satellite/get_configuration",
entity_id,
});

export const setWakeWords = (
hass: HomeAssistant,
entity_id: string,
wake_word_ids: string[]
) =>
hass.callWS({
type: "assist_satellite/set_wake_words",
entity_id,
wake_word_ids,
});

export const assistSatelliteSupportsSetupFlow = (
assistSatelliteEntity: HassEntity | undefined
) =>
assistSatelliteEntity &&
supportsFeature(assistSatelliteEntity, AssistSatelliteEntityFeature.ANNOUNCE);
35 changes: 35 additions & 0 deletions src/dialogs/config-flow/step-flow-create-entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-area-picker";
import { DataEntryFlowStepCreateEntry } from "../../data/data_entry_flow";
Expand All @@ -9,10 +10,14 @@ import {
DeviceRegistryEntry,
updateDeviceRegistryEntry,
} from "../../data/device_registry";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types";
import { showAlertDialog } from "../generic/show-dialog-box";
import { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
import { computeDomain } from "../../common/entity/compute_domain";
import { showVoiceAssistantSetupDialog } from "../voice-assistant-setup/show-voice-assistant-setup-dialog";
import { assistSatelliteSupportsSetupFlow } from "../../data/assist_satellite";

@customElement("step-flow-create-entry")
class StepFlowCreateEntry extends LitElement {
Expand All @@ -24,9 +29,39 @@ class StepFlowCreateEntry extends LitElement {

@property({ attribute: false }) public devices!: DeviceRegistryEntry[];

private _deviceEntities = memoizeOne(
(
deviceId: string,
entities: EntityRegistryDisplayEntry[]
): EntityRegistryDisplayEntry[] =>
entities.filter((entity) => entity.device_id === deviceId)
);
piitaya marked this conversation as resolved.
Show resolved Hide resolved

protected render(): TemplateResult {
const localize = this.hass.localize;

if (this.devices.length === 1) {
// integration_type === "device"
const deviceEntities = this._deviceEntities(
this.devices[0].id,
Object.values(this.hass.entities)
);
piitaya marked this conversation as resolved.
Show resolved Hide resolved
const assistSatellite = deviceEntities.find(
(entity) => computeDomain(entity.entity_id) === "assist_satellite"
);
if (
assistSatellite &&
assistSatelliteSupportsSetupFlow(
this.hass.states[assistSatellite.entity_id]
)
) {
this._flowDone();
showVoiceAssistantSetupDialog(this, {
deviceId: this.devices[0].id,
});
}
}
bramkragten marked this conversation as resolved.
Show resolved Hide resolved

return html`
<h2>${localize("ui.panel.config.integrations.config_flow.success")}!</h2>
<div class="content">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fireEvent } from "../../common/dom/fire_event";

const loadVoiceAssistantSetupDialog = () =>
import("./voice-assistant-setup-dialog");

export interface VoiceAssistantSetupDialogParams {
deviceId: string;
}

export const showVoiceAssistantSetupDialog = (
element: HTMLElement,
dialogParams: VoiceAssistantSetupDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-voice-assistant-setup-dialog",
dialogImport: loadVoiceAssistantSetupDialog,
dialogParams: dialogParams,
});
};
36 changes: 36 additions & 0 deletions src/dialogs/voice-assistant-setup/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { css } from "lit";
import { haStyle } from "../../resources/styles";

export const AssistantSetupStyles = [
haStyle,
css`
:host {
align-items: center;
text-align: center;
min-height: 300px;
max-width: 500px;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
padding: 24px;
box-sizing: border-box;
}
.content {
flex: 1;
}
.content img {
width: 120px;
margin-top: 68px;
margin-bottom: 68px;
}
.footer {
width: 100%;
display: flex;
flex-direction: column;
}
.footer ha-button {
width: 100%;
}
`,
];
Loading
Loading