mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Voice assistant (#15841)
* Add basic debug panel for voice assistant pipelines * Add more info to start event * Copy on change * Use latest data model * Apply suggestions from code review Co-authored-by: Paul Bottein <paul.bottein@gmail.com> * Fix CSS * Also use ha-button --------- Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
parent
c5be2acd46
commit
db08c5029b
@ -44,7 +44,7 @@ interface IntentResultError extends IntentResultBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConversationResult {
|
export interface ConversationResult {
|
||||||
conversation_id: string | null;
|
conversation_id: string | null;
|
||||||
response:
|
response:
|
||||||
| IntentResultActionDone
|
| IntentResultActionDone
|
||||||
|
153
src/data/voice_assistant.ts
Normal file
153
src/data/voice_assistant.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import { ConversationResult } from "./conversation";
|
||||||
|
|
||||||
|
interface PipelineEventBase {
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineRunStartEvent extends PipelineEventBase {
|
||||||
|
type: "run-start";
|
||||||
|
data: {
|
||||||
|
pipeline: string;
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface PipelineRunFinishEvent extends PipelineEventBase {
|
||||||
|
type: "run-finish";
|
||||||
|
data: Record<string, never>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineErrorEvent extends PipelineEventBase {
|
||||||
|
type: "error";
|
||||||
|
data: {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||||
|
type: "stt-start";
|
||||||
|
data: Record<string, never>;
|
||||||
|
}
|
||||||
|
interface PipelineSTTFinishEvent extends PipelineEventBase {
|
||||||
|
type: "stt-finish";
|
||||||
|
data: Record<string, never>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineIntentStartEvent extends PipelineEventBase {
|
||||||
|
type: "intent-start";
|
||||||
|
data: {
|
||||||
|
engine: string;
|
||||||
|
intent_input: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface PipelineIntentFinishEvent extends PipelineEventBase {
|
||||||
|
type: "intent-finish";
|
||||||
|
data: {
|
||||||
|
intent_output: ConversationResult;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipelineTTSStartEvent extends PipelineEventBase {
|
||||||
|
type: "tts-start";
|
||||||
|
data: Record<string, never>;
|
||||||
|
}
|
||||||
|
interface PipelineTTSFinishEvent extends PipelineEventBase {
|
||||||
|
type: "tts-finish";
|
||||||
|
data: Record<string, never>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PipelineRunEvent =
|
||||||
|
| PipelineRunStartEvent
|
||||||
|
| PipelineRunFinishEvent
|
||||||
|
| PipelineErrorEvent
|
||||||
|
| PipelineSTTStartEvent
|
||||||
|
| PipelineSTTFinishEvent
|
||||||
|
| PipelineIntentStartEvent
|
||||||
|
| PipelineIntentFinishEvent
|
||||||
|
| PipelineTTSStartEvent
|
||||||
|
| PipelineTTSFinishEvent;
|
||||||
|
|
||||||
|
interface PipelineRunOptions {
|
||||||
|
pipeline?: string;
|
||||||
|
intent_input?: string;
|
||||||
|
conversation_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineRun {
|
||||||
|
init_options: PipelineRunOptions;
|
||||||
|
events: PipelineRunEvent[];
|
||||||
|
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
|
||||||
|
run: PipelineRunStartEvent["data"];
|
||||||
|
error?: PipelineErrorEvent["data"];
|
||||||
|
stt?: PipelineSTTStartEvent["data"] & Partial<PipelineSTTFinishEvent["data"]>;
|
||||||
|
intent?: PipelineIntentStartEvent["data"] &
|
||||||
|
Partial<PipelineIntentFinishEvent["data"]>;
|
||||||
|
tts?: PipelineTTSStartEvent["data"] & Partial<PipelineTTSFinishEvent["data"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const runPipelineFromText = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (event: PipelineRun) => void,
|
||||||
|
options: PipelineRunOptions = {}
|
||||||
|
) => {
|
||||||
|
let run: PipelineRun | undefined;
|
||||||
|
|
||||||
|
const unsubProm = hass.connection.subscribeMessage<PipelineRunEvent>(
|
||||||
|
(updateEvent) => {
|
||||||
|
if (updateEvent.type === "run-start") {
|
||||||
|
run = {
|
||||||
|
init_options: options,
|
||||||
|
stage: "ready",
|
||||||
|
run: updateEvent.data,
|
||||||
|
error: undefined,
|
||||||
|
stt: undefined,
|
||||||
|
intent: undefined,
|
||||||
|
tts: undefined,
|
||||||
|
events: [updateEvent],
|
||||||
|
};
|
||||||
|
callback(run);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
"Received unexpected event before receiving session",
|
||||||
|
updateEvent
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
run.events.push(updateEvent);
|
||||||
|
|
||||||
|
if (updateEvent.type === "stt-start") {
|
||||||
|
run = { ...run, stage: "stt", stt: updateEvent.data };
|
||||||
|
} else if (updateEvent.type === "stt-finish") {
|
||||||
|
run = { ...run, stt: { ...run.stt!, ...updateEvent.data } };
|
||||||
|
} else if (updateEvent.type === "intent-start") {
|
||||||
|
run = { ...run, stage: "intent", intent: updateEvent.data };
|
||||||
|
} else if (updateEvent.type === "intent-finish") {
|
||||||
|
run = { ...run, intent: { ...run.intent!, ...updateEvent.data } };
|
||||||
|
} else if (updateEvent.type === "tts-start") {
|
||||||
|
run = { ...run, stage: "tts", tts: updateEvent.data };
|
||||||
|
} else if (updateEvent.type === "tts-finish") {
|
||||||
|
run = { ...run, tts: { ...run.tts!, ...updateEvent.data } };
|
||||||
|
} else if (updateEvent.type === "run-finish") {
|
||||||
|
run = { ...run, stage: "done" };
|
||||||
|
unsubProm.then((unsub) => unsub());
|
||||||
|
} else if (updateEvent.type === "error") {
|
||||||
|
run = { ...run, stage: "error", error: updateEvent.data };
|
||||||
|
unsubProm.then((unsub) => unsub());
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(run);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
type: "voice_assistant/run",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubProm;
|
||||||
|
};
|
@ -353,6 +353,13 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
tag: "ha-config-areas",
|
tag: "ha-config-areas",
|
||||||
load: () => import("./areas/ha-config-areas"),
|
load: () => import("./areas/ha-config-areas"),
|
||||||
},
|
},
|
||||||
|
voice_assistant: {
|
||||||
|
tag: "assist-pipeline-debug",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
"./integrations/integration-panels/voice_assistant/assist/assist-pipeline-debug"
|
||||||
|
),
|
||||||
|
},
|
||||||
automation: {
|
automation: {
|
||||||
tag: "ha-config-automation",
|
tag: "ha-config-automation",
|
||||||
load: () => import("./automation/ha-config-automation"),
|
load: () => import("./automation/ha-config-automation"),
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import "../../../../../../components/ha-card";
|
||||||
|
import "../../../../../../components/ha-button";
|
||||||
|
import "../../../../../../components/ha-textfield";
|
||||||
|
import {
|
||||||
|
PipelineRun,
|
||||||
|
runPipelineFromText,
|
||||||
|
} from "../../../../../../data/voice_assistant";
|
||||||
|
import "../../../../../../layouts/hass-subpage";
|
||||||
|
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||||
|
import { haStyle } from "../../../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../../../types";
|
||||||
|
|
||||||
|
@customElement("assist-pipeline-debug")
|
||||||
|
export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@query("#run-input", true)
|
||||||
|
private _newRunInput!: HTMLInputElement;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private _pipelineRun?: PipelineRun;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.hass=${this.hass}
|
||||||
|
header="Assist Pipeline"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<ha-card header="Run pipeline" class="run-pipeline-card">
|
||||||
|
<div class="card-content">
|
||||||
|
<ha-textfield
|
||||||
|
id="run-input"
|
||||||
|
label="Input"
|
||||||
|
value="Are the lights on?"
|
||||||
|
></ha-textfield>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-button
|
||||||
|
@click=${this._runPipeline}
|
||||||
|
.disabled=${this._pipelineRun &&
|
||||||
|
!["error", "done"].includes(this._pipelineRun.stage)}
|
||||||
|
>
|
||||||
|
Run
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
${this._pipelineRun
|
||||||
|
? html`
|
||||||
|
<ha-card heading="Pipeline Run">
|
||||||
|
<div class="card-content">
|
||||||
|
<pre>${JSON.stringify(this._pipelineRun, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _runPipeline(): void {
|
||||||
|
this._pipelineRun = undefined;
|
||||||
|
runPipelineFromText(
|
||||||
|
this.hass,
|
||||||
|
(run) => {
|
||||||
|
this._pipelineRun = run;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
intent_input: this._newRunInput.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding: 24px 0 32px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.run-pipeline-card ha-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"assist-pipeline-debug": AssistPipelineDebug;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user