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

20241127.2 #23104

Merged
merged 7 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "home-assistant-frontend"
version = "20241127.1"
version = "20241127.2"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
Expand Down
19 changes: 19 additions & 0 deletions src/data/hassio/supervisor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,25 @@ export const fetchHassioLogsFollow = async (
signal
);

export const fetchHassioLogsFollowSkip = async (
hass: HomeAssistant,
provider: string,
signal: AbortSignal,
cursor: string,
skipLines: number,
lines = 100,
boot = 0
) =>
hass.callApiRaw(
"GET",
`hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs${boot !== 0 ? `/boots/${boot}` : ""}/follow`,
undefined,
{
Range: `entries=${cursor}:${skipLines}:${lines}`,
},
signal
);

export const getHassioLogDownloadUrl = (provider: string) =>
`/api/hassio/${
provider.includes("_") ? `addons/${provider}` : provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {

@state() private _detailState?: string;

@state() private _error?: string;

@state() private _localTts?: EntityRegistryDisplayEntry[];

@state() private _localStt?: EntityRegistryDisplayEntry[];
Expand All @@ -62,6 +64,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
alt="Casita Home Assistant error logo"
/>
<h1>Failed to install add-ons</h1>
<p>${this._error}</p>
<p>
We could not automatically install a local TTS and STT provider
for you. Read the documentation to learn how to install them.
Expand Down Expand Up @@ -179,8 +182,9 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
}
this._detailState = "Creating assistant";
await this._findEntitiesAndCreatePipeline();
} catch (e) {
} catch (e: any) {
this._state = "ERROR";
this._error = e.message;
}
}

Expand All @@ -199,11 +203,13 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
private async _setupConfigEntry(addon: string) {
const configFlow = await createConfigFlow(this.hass, "wyoming");
const step = await handleConfigFlowStep(this.hass, configFlow.flow_id, {
host: `core_${addon}`,
host: `core-${addon}`,
port: addon === "piper" ? 10200 : 10300,
});
if (step.type !== "create_entry") {
throw new Error("Failed to create entry");
throw new Error(
`Failed to create entry for ${addon}${"errors" in step ? `: ${step.errors.base}` : ""}`
);
}
}

Expand Down Expand Up @@ -321,7 +327,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
this._findLocalEntities();
if (!this._localTts?.length || !this._localStt?.length) {
if (tryNo > 3) {
throw new Error("Timeout searching for local TTS and STT entities");
throw new Error("Could not find local TTS and STT entities");
}
await new Promise<void>((resolve) => {
setTimeout(resolve, 2000);
Expand Down
13 changes: 7 additions & 6 deletions src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,13 @@ export class HaVoiceCommandDialog extends LitElement {

const controlHA = !this._pipeline
? false
: this.hass.states[this._pipeline?.conversation_engine]
? supportsFeature(
this.hass.states[this._pipeline?.conversation_engine],
ConversationEntityFeature.CONTROL
)
: true;
: this._pipeline.prefer_local_intents ||
(this.hass.states[this._pipeline.conversation_engine]
? supportsFeature(
this.hass.states[this._pipeline.conversation_engine],
ConversationEntityFeature.CONTROL
)
: true);
const supportsMicrophone = AudioRecorder.isSupported;
const supportsSTT = this._pipeline?.stt_engine;

Expand Down
90 changes: 61 additions & 29 deletions src/panels/config/logs/error-log-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
fetchHassioBoots,
fetchHassioLogs,
fetchHassioLogsFollow,
fetchHassioLogsFollowSkip,
fetchHassioLogsLegacy,
getHassioLogDownloadLinesUrl,
getHassioLogDownloadUrl,
Expand Down Expand Up @@ -428,46 +429,66 @@ class ErrorLogCard extends LitElement {
}
}

private async _loadLogs(): Promise<void> {
private async _loadLogs(retry = false): Promise<void> {
this._error = undefined;
this._loadingState = "loading";
this._loadingPrevState = undefined;
this._firstCursor = undefined;
this._numberOfLines = 0;
this._ansiToHtmlElement?.clear();
this._numberOfLines = retry ? (this._numberOfLines ?? 0) : 0;

if (!retry) {
this._loadingPrevState = undefined;
this._firstCursor = undefined;
this._ansiToHtmlElement?.clear();
}

const streamLogs =
this._streamSupported &&
isComponentLoaded(this.hass, "hassio") &&
this.provider;

try {
if (this._logStreamAborter) {
this._logStreamAborter.abort();
this._logStreamAborter = undefined;
}

if (
this._streamSupported &&
isComponentLoaded(this.hass, "hassio") &&
this.provider
) {
if (streamLogs) {
this._logStreamAborter = new AbortController();

// check if there are any logs at all
const testResponse = await fetchHassioLogs(
this.hass,
this.provider,
`entries=:-1:`,
this._boot
);
const testLogs = await testResponse.text();
if (!testLogs.trim()) {
this._loadingState = "empty";
if (!retry) {
// check if there are any logs at all
const testResponse = await fetchHassioLogs(
this.hass,
this.provider!,
`entries=:-1:`,
this._boot
);
const testLogs = await testResponse.text();
if (!testLogs.trim()) {
this._loadingState = "empty";
}
}

const response = await fetchHassioLogsFollow(
this.hass,
this.provider,
this._logStreamAborter.signal,
NUMBER_OF_LINES,
this._boot
);
let response: Response;

if (retry && this._firstCursor) {
response = await fetchHassioLogsFollowSkip(
this.hass,
this.provider!,
this._logStreamAborter.signal,
this._firstCursor,
this._numberOfLines,
NUMBER_OF_LINES,
this._boot
);
} else {
response = await fetchHassioLogsFollow(
this.hass,
this.provider!,
this._logStreamAborter.signal,
NUMBER_OF_LINES,
this._boot
);
}

if (response.headers.has("X-First-Cursor")) {
this._firstCursor = response.headers.get("X-First-Cursor")!;
Expand Down Expand Up @@ -524,14 +545,17 @@ class ErrorLogCard extends LitElement {

if (!this._downloadSupported) {
const downloadUrl = getHassioLogDownloadLinesUrl(
this.provider,
this.provider!,
this._numberOfLines,
this._boot
);
getSignedPath(this.hass, downloadUrl).then((signedUrl) => {
this._logsFileLink = signedUrl.path;
});
}

// first chunk loads successfully, reset retry param
retry = false;
}
}
} else {
Expand All @@ -554,6 +578,13 @@ class ErrorLogCard extends LitElement {
if (err.name === "AbortError") {
return;
}

// The stream can fail if the connection is lost or firefox service worker intercept the connection
if (!retry && streamLogs) {
this._loadLogs(true);
return;
}

this._error = (this.localizeFunc || this.hass.localize)(
"ui.panel.config.logs.failed_get_logs",
{
Expand Down Expand Up @@ -590,9 +621,10 @@ class ErrorLogCard extends LitElement {
private _handleConnectionStatus = (ev: HASSDomEvent<ConnectionStatus>) => {
if (ev.detail === "disconnected" && this._logStreamAborter) {
this._logStreamAborter.abort();
this._loadingState = "loading";
}
if (ev.detail === "connected") {
this._loadLogs();
this._loadLogs(true);
}
};

Expand Down
33 changes: 28 additions & 5 deletions src/panels/config/scene/ha-scene-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
mdiContentSave,
mdiDelete,
mdiDotsVertical,
mdiEye,
mdiInformationOutline,
mdiMotionPlayOutline,
mdiPlay,
mdiTag,
} from "@mdi/js";
Expand Down Expand Up @@ -204,6 +206,14 @@ export class HaSceneEditor extends SubscribeMixin(
}
);

public connectedCallback() {
super.connectedCallback();
if (!this.sceneId) {
this._mode = "live";
this._subscribeEvents();
}
}

public disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubscribeEvents) {
Expand Down Expand Up @@ -387,15 +397,22 @@ export class HaSceneEditor extends SubscribeMixin(
alert-type="info"
.narrow=${this.narrow}
.title=${this.hass.localize(
`ui.panel.config.scene.editor.${this._mode === "live" ? "live_preview" : "review_mode"}`
`ui.panel.config.scene.editor.${this._mode === "live" ? "live_edit" : "review_mode"}`
)}
>
${this.hass.localize(
`ui.panel.config.scene.editor.${this._mode === "live" ? "live_preview_detail" : "review_mode_detail"}`
`ui.panel.config.scene.editor.${this._mode === "live" ? "live_edit_detail" : "review_mode_detail"}`
)}
<span slot="icon">
<ha-svg-icon
.path=${this._mode === "live"
? mdiMotionPlayOutline
: mdiEye}
></ha-svg-icon>
</span>
<ha-button slot="action" @click=${this._toggleLiveMode}>
${this.hass.localize(
`ui.panel.config.scene.editor.${this._mode === "live" ? "back_to_review_mode" : "live_preview"}`
`ui.panel.config.scene.editor.${this._mode === "live" ? "switch_to_review_mode" : "live_edit"}`
)}
</ha-button>
</ha-alert>
Expand Down Expand Up @@ -542,6 +559,7 @@ export class HaSceneEditor extends SubscribeMixin(
}
return html`
<ha-list-item
class="entity"
hasMeta
.graphic=${this._mode === "live"
? "icon"
Expand Down Expand Up @@ -759,13 +777,15 @@ export class HaSceneEditor extends SubscribeMixin(
text: this.hass.localize(
"ui.panel.config.scene.editor.enter_live_mode_unsaved"
),
confirmText: this.hass!.localize("ui.common.continue"),
destructive: true,
confirmText: this.hass!.localize(
"ui.panel.config.scene.editor.save_before_live"
),
dismissText: this.hass!.localize("ui.common.cancel"),
});
if (!result) {
return;
}
await this._saveScene();
}

this._entities.forEach((entity) => this._storeState(entity));
Expand Down Expand Up @@ -1309,6 +1329,9 @@ export class HaSceneEditor extends SubscribeMixin(
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
ha-list-item.entity {
padding-right: 28px;
}
`,
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class HuiHistoryGraphCardEditor
{
name: "hours_to_show",
default: DEFAULT_HOURS_TO_SHOW,
selector: { number: { min: 1, mode: "box" } },
selector: { number: { min: 0, step: "any", mode: "box" } },
},
],
},
Expand Down
11 changes: 6 additions & 5 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3878,11 +3878,12 @@
},
"editor": {
"review_mode": "Review Mode",
"review_mode_detail": "You can adjust the scene's details and remove devices or entities. To fully edit, switch to Live Preview, which will apply the scene.",
"live_preview": "Live Preview",
"live_preview_detail": "In Live Preview, all changes to this scene are applied in real-time to your devices and entities.",
"enter_live_mode_unsaved": "You have unsaved changes to this scene. Continuing to live preview will apply the saved scene, which may overwrite your unsaved changes. Consider if you would like to save the scene first before activating it.",
"back_to_review_mode": "Back to review mode",
"review_mode_detail": "You can adjust the scene's details and remove devices or entities. To fully edit, switch to Live Edit, which will apply the scene.",
"live_edit": "Live Edit",
"live_edit_detail": "In Live Edit, all changes to this scene are applied in real-time to your devices and entities.",
"enter_live_mode_unsaved": "Before proceeding to Live Edit, please save your current changes.",
"save_before_live": "Save and Live Edit",
"switch_to_review_mode": "Switch to review mode",
"default_name": "New scene",
"load_error_not_editable": "Only scenes in scenes.yaml are editable.",
"load_error_unknown": "Error loading scene ({err_no}).",
Expand Down
Loading