Compare commits

...

17 Commits

Author SHA1 Message Date
Aidan Timson
8af0eda5d1 Naming 2026-01-20 13:55:03 +00:00
Aidan Timson
c45217fd76 Space tokens 2026-01-20 13:50:46 +00:00
Aidan Timson
e440986ab3 Include title if empty 2026-01-20 12:32:16 +00:00
Aidan Timson
7843d08193 Rename 2026-01-20 12:28:04 +00:00
Aidan Timson
c0e8cb8c97 Refactor to default config 2026-01-20 12:26:37 +00:00
Aidan Timson
3b3ffd339e List formatting 2026-01-20 12:19:32 +00:00
Aidan Timson
1a0f1e9921 Fix area inspirations 2026-01-20 11:56:31 +00:00
Aidan Timson
ee7d18c5ae Add floor inspirations, move entities and floor inspirations to new file 2026-01-20 11:16:45 +00:00
Aidan Timson
59c9f67256 Fix optional chaining 2026-01-20 10:57:51 +00:00
Aidan Timson
21d0ef14d5 Add floor suggestions 2026-01-20 10:43:59 +00:00
Aidan Timson
d6b8a61013 Type 2026-01-20 10:35:05 +00:00
Aidan Timson
02105a782e Use proper context for names 2026-01-20 10:29:56 +00:00
Aidan Timson
e33c8f74ba Make name optional, restructure prompt to suit 2026-01-20 10:19:05 +00:00
Aidan Timson
0c3706772f Use default 2026-01-20 10:00:33 +00:00
Aidan Timson
587cd9c293 Dont update name in edit mode 2026-01-20 09:47:37 +00:00
Aidan Timson
f11750295e Add suggestion button to area dialog 2026-01-20 09:46:19 +00:00
Aidan Timson
6381fcc47f Make docstrings generic 2026-01-20 09:44:56 +00:00
6 changed files with 403 additions and 177 deletions

View File

@@ -16,8 +16,11 @@ import "../../../components/ha-labels-picker";
import "../../../components/ha-picture-upload";
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
import "../../../components/ha-settings-row";
import "../../../components/ha-suggest-with-ai-button";
import type { SuggestWithAIGenerateTask } from "../../../components/ha-suggest-with-ai-button";
import "../../../components/ha-textfield";
import "../../../components/ha-wa-dialog";
import type { GenDataTaskResult } from "../../../data/ai_task";
import type {
AreaRegistryEntry,
AreaRegistryEntryMutableParams,
@@ -27,11 +30,19 @@ import {
SENSOR_DEVICE_CLASS_HUMIDITY,
SENSOR_DEVICE_CLASS_TEMPERATURE,
} from "../../../data/sensor";
import { fetchLabelRegistry } from "../../../data/label/label_registry";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
import {
type MetadataSuggestionInclude,
type MetadataSuggestionResult,
generateMetadataSuggestionTask,
processMetadataSuggestion,
} from "../common/suggest-metadata-ai";
import { buildAreaMetadataInspirations } from "../common/suggest-metadata-inspirations";
import type { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
const cropOptions: CropOptions = {
@@ -75,6 +86,12 @@ class DialogAreaDetail
@state() private _open = false;
@state() private _suggestionInclude: MetadataSuggestionInclude = {
name: true,
labels: true,
floor: true,
};
public async showDialog(
params: AreaRegistryDetailDialogParams
): Promise<void> {
@@ -242,6 +259,70 @@ class DialogAreaDetail
`;
}
private _generateTask = async (): Promise<SuggestWithAIGenerateTask> => {
this._suggestionInclude = {
...this._suggestionInclude,
name: this._name.trim() === "",
};
return generateMetadataSuggestionTask<{
name: string;
aliases: string[];
labels: string[];
floor: string | null;
temperature_entity: string | null;
humidity_entity: string | null;
}>(
this.hass.connection,
this.hass.language,
"area",
{
name: this._name,
aliases: this._aliases,
labels: this._labels.length
? (await fetchLabelRegistry(this.hass.connection))
.filter((label) => this._labels.includes(label.label_id))
.map((label) => label.name)
: [],
floor: this._floor ? this.hass.floors?.[this._floor]?.name : null,
temperature_entity: this._temperatureEntity
? (this.hass.states[this._temperatureEntity]?.attributes
?.friendly_name ?? null)
: null,
humidity_entity: this._humidityEntity
? (this.hass.states[this._humidityEntity]?.attributes
?.friendly_name ?? null)
: null,
},
await buildAreaMetadataInspirations(this.hass.connection),
this._suggestionInclude
);
};
private async _handleSuggestion(
event: CustomEvent<GenDataTaskResult<MetadataSuggestionResult>>
) {
const result = event.detail;
const processed = await processMetadataSuggestion(
this.hass.connection,
"area",
result,
this._suggestionInclude
);
if (processed.name) {
this._name = processed.name;
}
if (processed.labels?.length) {
this._labels = processed.labels;
}
if (processed.floor) {
this._floor = processed.floor;
}
}
protected render() {
if (!this._params) {
return nothing;
@@ -259,6 +340,12 @@ class DialogAreaDetail
: this.hass.localize("ui.panel.config.areas.editor.create_area")}
@closed=${this._dialogClosed}
>
<ha-suggest-with-ai-button
slot="headerActionItems"
.hass=${this.hass}
.generateTask=${this._generateTask}
@suggestion=${this._handleSuggestion}
></ha-suggest-with-ai-button>
<div>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
@@ -423,13 +510,16 @@ class DialogAreaDetail
ha-picture-upload,
ha-expansion-panel {
display: block;
margin-bottom: 16px;
margin-bottom: var(--ha-space-4);
}
.content {
padding: 12px;
padding: var(--ha-space-3);
}
.description {
margin: 0 0 16px 0;
margin: 0 0 var(--ha-space-4) 0;
}
ha-suggest-with-ai-button {
margin: var(--ha-space-2) var(--ha-space-4);
}
`,
];

View File

@@ -33,10 +33,10 @@ import type {
} from "./show-dialog-automation-save";
import {
type MetadataSuggestionResult,
SUGGESTION_INCLUDE_ALL,
generateMetadataSuggestionTask,
processMetadataSuggestion,
} from "../../common/suggest-metadata-ai";
import { buildEntityMetadataInspirations } from "../../common/suggest-metadata-inspirations";
@customElement("ha-dialog-automation-save")
class DialogAutomationSave extends LitElement implements HassDialog {
@@ -341,10 +341,14 @@ class DialogAutomationSave extends LitElement implements HassDialog {
}
return generateMetadataSuggestionTask<AutomationConfig | ScriptConfig>(
this.hass.connection,
this.hass.states,
this.hass.language,
this._params.domain,
this._params.config
this._params.config,
await buildEntityMetadataInspirations(
this.hass.connection,
this.hass.states,
this._params.domain
)
);
};
@@ -358,11 +362,12 @@ class DialogAutomationSave extends LitElement implements HassDialog {
const processed = await processMetadataSuggestion(
this.hass.connection,
this._params.domain,
result,
SUGGESTION_INCLUDE_ALL
result
);
this._newName = processed.name;
if (processed.name) {
this._newName = processed.name;
}
if (processed.description) {
this._newDescription = processed.description;
@@ -432,7 +437,8 @@ class DialogAutomationSave extends LitElement implements HassDialog {
haStyleDialog,
css`
ha-wa-dialog {
--dialog-content-padding: 0 24px 24px 24px;
--dialog-content-padding: 0 var(--ha-space-6) var(--ha-space-6)
var(--ha-space-6);
}
ha-textfield,
@@ -448,15 +454,15 @@ class DialogAutomationSave extends LitElement implements HassDialog {
ha-labels-picker,
ha-area-picker,
ha-chip-set:has(> ha-assist-chip) {
margin-top: 16px;
margin-top: var(--ha-space-4);
}
ha-alert {
display: block;
margin-bottom: 16px;
margin-bottom: var(--ha-space-4);
}
ha-suggest-with-ai-button {
margin: 8px 16px;
margin: var(--ha-space-2) var(--ha-space-4);
}
`,
];

View File

@@ -1,163 +1,84 @@
import { dump } from "js-yaml";
import { computeDomain } from "../../../common/entity/compute_domain";
import { subscribeOne } from "../../../common/util/subscribe-one";
import type { AITaskStructure, GenDataTaskResult } from "../../../data/ai_task";
import { fetchCategoryRegistry } from "../../../data/category_registry";
import {
subscribeEntityRegistry,
type EntityRegistryEntry,
} from "../../../data/entity/entity_registry";
import { subscribeLabelRegistry } from "../../../data/label/label_registry";
import type { HomeAssistant } from "../../../types";
import type { SuggestWithAIGenerateTask } from "../../../components/ha-suggest-with-ai-button";
import {
fetchCategories,
fetchFloors,
fetchLabels,
} from "./suggest-metadata-helpers";
export interface MetadataSuggestionResult {
name: string;
name?: string;
description?: string;
category?: string;
labels?: string[];
floor?: string;
}
export type MetadataSuggestionDomain = "automation" | "script" | "scene";
export type MetadataSuggestionDomain =
| "automation"
| "script"
| "scene"
| "area";
export interface MetadataSuggestionInclude {
name: boolean;
description?: boolean;
categories?: boolean;
labels?: boolean;
floor?: boolean;
}
type Categories = Record<string, string>;
type Entities = Record<string, EntityRegistryEntry>;
type Labels = Record<string, string>;
export const SUGGESTION_INCLUDE_ALL: MetadataSuggestionInclude = {
export const SUGGESTION_INCLUDE_DEFAULT: MetadataSuggestionInclude = {
name: true,
description: true,
categories: true,
labels: true,
} as const;
const tryCatchEmptyObject = <T>(promise: Promise<T>): Promise<T> =>
promise.catch((err) => {
// eslint-disable-next-line no-console
console.error("Error fetching data for suggestion: ", err);
return {} as T;
});
const fetchCategories = (
connection: HomeAssistant["connection"],
domain: MetadataSuggestionDomain
): Promise<Categories> =>
tryCatchEmptyObject<Categories>(
fetchCategoryRegistry(connection, domain).then((cats) =>
Object.fromEntries(cats.map((cat) => [cat.category_id, cat.name]))
)
);
const fetchEntities = (
connection: HomeAssistant["connection"]
): Promise<Entities> =>
tryCatchEmptyObject<Entities>(
subscribeOne(connection, subscribeEntityRegistry).then((ents) =>
Object.fromEntries(ents.map((ent) => [ent.entity_id, ent]))
)
);
const fetchLabels = (
connection: HomeAssistant["connection"]
): Promise<Labels> =>
tryCatchEmptyObject<Labels>(
subscribeOne(connection, subscribeLabelRegistry).then((labs) =>
Object.fromEntries(labs.map((lab) => [lab.label_id, lab.name]))
)
);
function buildMetadataInspirations(
domain: MetadataSuggestionDomain,
states: HomeAssistant["states"],
entities: Entities,
categories?: Categories,
labels?: Labels
): string[] {
const inspirations: string[] = [];
for (const entityId of Object.keys(entities)) {
const entityEntry = entities[entityId];
if (!entityEntry || computeDomain(entityId) !== domain) {
continue;
}
const entity = states[entityId];
if (
!entity ||
entity.attributes.restored ||
!entity.attributes.friendly_name
) {
continue;
}
let inspiration = `- ${entity.attributes.friendly_name}`;
// Get the category for this domain
if (categories && categories[entityEntry.categories[domain]]) {
inspiration += ` (category: ${categories[entityEntry.categories[domain]]})`;
}
if (labels && entityEntry.labels.length) {
inspiration += ` (labels: ${entityEntry.labels
.map((label) => labels[label])
.join(", ")})`;
}
inspirations.push(inspiration);
}
return inspirations;
}
// Always English to format lists in the prompt
const PROMPT_LIST_FORMAT = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
/**
* Generates an AI task for suggesting metadata
* for automations or scripts based on their configuration.
* Generates an AI task for suggesting metadata based on their configuration.
*
* @param connection - Home Assistant connection
* @param states - Current state objects
* @param language - User's language preference
* @param domain - The domain to suggest metadata for (automation, script)
* @param domain - The domain to suggest metadata for
* @param config - The configuration to suggest metadata for
* @param inspirations - Existing entries to use as inspiration
* @param include - The metadata fields to include in the suggestion
* @returns Promise resolving to the AI task structure
*/
export async function generateMetadataSuggestionTask<T>(
connection: HomeAssistant["connection"],
states: HomeAssistant["states"],
language: HomeAssistant["language"],
domain: MetadataSuggestionDomain,
config: T,
include = SUGGESTION_INCLUDE_ALL
inspirations: string[] = [],
include = SUGGESTION_INCLUDE_DEFAULT
): Promise<SuggestWithAIGenerateTask> {
const [categories, entities, labels] = await Promise.all([
const [categories, floors] = await Promise.all([
include.categories
? fetchCategories(connection, domain)
: Promise.resolve(undefined),
fetchEntities(connection),
include.labels ? fetchLabels(connection) : Promise.resolve(undefined),
include.floor ? fetchFloors(connection) : Promise.resolve(undefined),
]);
const inspirations = buildMetadataInspirations(
domain,
states,
entities,
categories,
labels
);
const structure: AITaskStructure = {
name: {
description: `The name of the ${domain}`,
required: true,
selector: {
text: {},
...(include.name && {
name: {
description: `The name of the ${domain}`,
required: true,
selector: {
text: {},
},
},
},
}),
...(include.description && {
description: {
description: `A short description of the ${domain}`,
@@ -193,49 +114,83 @@ export async function generateMetadataSuggestionTask<T>(
},
},
}),
...(include.floor &&
floors && {
floor: {
description: `The floor of the ${domain}`,
required: false,
selector: {
select: {
options: Object.values(floors).map((floor) => ({
value: floor.floor_id,
label: floor.name,
})),
},
},
},
}),
};
const categoryLabelText: string[] = [];
if (include.categories) {
categoryLabelText.push("category");
}
if (include.labels) {
categoryLabelText.push("labels");
}
const categoryLabelString =
categoryLabelText.length > 0 ? `, ${categoryLabelText.join(" and ")}` : "";
const requestedParts = [
include.name ? "a name" : null,
include.description ? "a description" : null,
include.categories ? "a category" : null,
include.labels ? "labels" : null,
include.floor ? "a floor" : null,
].filter((entry): entry is string => entry !== null);
const categoryLabels: string[] = [
include.categories ? "category" : null,
include.labels ? "labels" : null,
include.floor ? "floor" : null,
].filter((entry): entry is string => entry !== null);
const categoryLabelsText = PROMPT_LIST_FORMAT.format(categoryLabels);
const requestedPartsText = requestedParts.length
? PROMPT_LIST_FORMAT.format(requestedParts)
: "suggestions";
return {
type: "data",
task: {
task_name: `frontend__${domain}__save`,
instructions: `Suggest in language "${language}" a name${include.description ? ", description" : ""}${categoryLabelString} for the following Home Assistant ${domain}.
The name should be relevant to the ${domain}'s purpose.
${
inspirations.length
? `The name should be in same style and sentence capitalization as existing ${domain}s.${
include.categories || include.labels
? `
Suggest ${categoryLabelText.join(" and ")} if relevant to the ${domain}'s purpose.
Only suggest ${categoryLabelText.join(" and ")} that are already used by existing ${domain}s.`
: ""
}`
: `The name should be short, descriptive, sentence case, and written in the language ${language}.`
}${
include.description
? `
If the ${domain} contains 5+ steps, include a short description.`
: ""
}
For inspiration, here are existing ${domain}s:
${inspirations.join("\n")}
The ${domain} configuration is as follows:
${dump(config)}
`,
instructions: [
`Suggest in language "${language}" ${requestedPartsText} for the following Home Assistant ${domain}.`,
"",
include.name
? `The name should be relevant to the ${domain}'s purpose.`
: `The suggestions should be relevant to the ${domain}'s purpose.`,
...(inspirations.length
? [
...(include.name
? [
`The name should be in same style and sentence capitalization as existing ${domain}s.`,
]
: []),
...(include.categories || include.labels || include.floor
? [
`Suggest ${categoryLabelsText} if relevant to the ${domain}'s purpose.`,
`Only suggest ${categoryLabelsText} that are already used by existing ${domain}s.`,
]
: []),
]
: include.name
? [
`The name should be short, descriptive, sentence case, and written in the language ${language}.`,
]
: []),
...(include.description
? [`If the ${domain} contains 5+ steps, include a short description.`]
: []),
"",
`For inspiration, here are existing ${domain}s:`,
inspirations.join("\n"),
"",
`The ${domain} configuration is as follows:`,
"",
`${dump(config)}`,
].join("\n"),
structure,
},
};
@@ -243,7 +198,7 @@ ${dump(config)}
/**
* Processes the result of an AI task for suggesting metadata
* for automations or scripts based on their configuration.
* based on their configuration.
*
* @param connection - Home Assistant connection
* @param domain - The domain of the ${domain}
@@ -255,17 +210,18 @@ export async function processMetadataSuggestion(
connection: HomeAssistant["connection"],
domain: MetadataSuggestionDomain,
result: GenDataTaskResult<MetadataSuggestionResult>,
include: MetadataSuggestionInclude
include = SUGGESTION_INCLUDE_DEFAULT
): Promise<MetadataSuggestionResult> {
const [categories, labels] = await Promise.all([
const [categories, labels, floors] = await Promise.all([
include.categories
? fetchCategories(connection, domain)
: Promise.resolve(undefined),
include.labels ? fetchLabels(connection) : Promise.resolve(undefined),
include.floor ? fetchFloors(connection) : Promise.resolve(undefined),
]);
const processed: MetadataSuggestionResult = {
name: result.data.name,
name: include.name ? result.data.name : undefined,
description: include.description ? result.data.description : undefined,
};
@@ -302,5 +258,17 @@ export async function processMetadataSuggestion(
}
}
if (include.floor && floors && result.data.floor) {
const floorId =
result.data.floor in floors
? result.data.floor
: Object.entries(floors).find(
([, floor]) => floor.name === result.data.floor
)?.[0];
if (floorId) {
processed.floor = floorId;
}
}
return processed;
}

View File

@@ -0,0 +1,72 @@
import { subscribeOne } from "../../../common/util/subscribe-one";
import { subscribeAreaRegistry } from "../../../data/area/area_registry";
import type { AreaRegistryEntry } from "../../../data/area/area_registry";
import { fetchCategoryRegistry } from "../../../data/category_registry";
import {
subscribeEntityRegistry,
type EntityRegistryEntry,
} from "../../../data/entity/entity_registry";
import { subscribeFloorRegistry } from "../../../data/ws-floor_registry";
import type { FloorRegistryEntry } from "../../../data/floor_registry";
import { subscribeLabelRegistry } from "../../../data/label/label_registry";
import type { HomeAssistant } from "../../../types";
import type { MetadataSuggestionDomain } from "./suggest-metadata-ai";
export type Categories = Record<string, string>;
export type Entities = Record<string, EntityRegistryEntry>;
export type Labels = Record<string, string>;
export type Floors = Record<string, FloorRegistryEntry>;
export type Areas = Record<string, AreaRegistryEntry>;
const tryCatchEmptyObject = <T>(promise: Promise<T>): Promise<T> =>
promise.catch((err) => {
// eslint-disable-next-line no-console
console.error("Error fetching data for suggestion: ", err);
return {} as T;
});
export const fetchCategories = (
connection: HomeAssistant["connection"],
domain: MetadataSuggestionDomain
): Promise<Categories> =>
tryCatchEmptyObject<Categories>(
fetchCategoryRegistry(connection, domain).then((cats) =>
Object.fromEntries(cats.map((cat) => [cat.category_id, cat.name]))
)
);
export const fetchLabels = (
connection: HomeAssistant["connection"]
): Promise<Labels> =>
tryCatchEmptyObject<Labels>(
subscribeOne(connection, subscribeLabelRegistry).then((labs) =>
Object.fromEntries(labs.map((lab) => [lab.label_id, lab.name]))
)
);
export const fetchFloors = (
connection: HomeAssistant["connection"]
): Promise<Floors> =>
tryCatchEmptyObject<Floors>(
subscribeOne(connection, subscribeFloorRegistry).then((floors) =>
Object.fromEntries(floors.map((floor) => [floor.floor_id, floor]))
)
);
export const fetchAreas = (
connection: HomeAssistant["connection"]
): Promise<Areas> =>
tryCatchEmptyObject<Areas>(
subscribeOne(connection, subscribeAreaRegistry).then((areas) =>
Object.fromEntries(areas.map((area) => [area.area_id, area]))
)
);
export const fetchEntities = (
connection: HomeAssistant["connection"]
): Promise<Entities> =>
tryCatchEmptyObject<Entities>(
subscribeOne(connection, subscribeEntityRegistry).then((ents) =>
Object.fromEntries(ents.map((ent) => [ent.entity_id, ent]))
)
);

View File

@@ -0,0 +1,82 @@
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HomeAssistant } from "../../../types";
import type { MetadataSuggestionDomain } from "./suggest-metadata-ai";
import {
fetchAreas,
fetchCategories,
fetchEntities,
fetchFloors,
fetchLabels,
} from "./suggest-metadata-helpers";
export const buildEntityMetadataInspirations = async (
connection: HomeAssistant["connection"],
states: HomeAssistant["states"],
domain: MetadataSuggestionDomain
): Promise<string[]> => {
const [categories, entities, labels] = await Promise.all([
fetchCategories(connection, domain),
fetchEntities(connection),
fetchLabels(connection),
]);
return Object.values(entities).reduce<string[]>((inspirations, entry) => {
if (!entry || computeDomain(entry.entity_id) !== domain) {
return inspirations;
}
const entity = states[entry.entity_id];
if (
!entity ||
entity.attributes.restored ||
!entity.attributes.friendly_name
) {
return inspirations;
}
let inspiration = `- ${entity.attributes.friendly_name}`;
const category = entry.categories[domain];
if (category && categories[category]) {
inspiration += ` (category: ${categories[category]})`;
}
if (entry.labels.length) {
const labelNames = entry.labels
.map((labelId) => labels[labelId])
.filter(Boolean);
if (labelNames.length) {
inspiration += ` (labels: ${labelNames.join(", ")})`;
}
}
inspirations.push(inspiration);
return inspirations;
}, []);
};
export const buildAreaMetadataInspirations = async (
connection: HomeAssistant["connection"]
): Promise<string[]> => {
const [labels, floors, areas] = await Promise.all([
fetchLabels(connection),
fetchFloors(connection),
fetchAreas(connection),
]);
return Object.values(areas).reduce<string[]>((inspirations, area) => {
if (!area.floor_id) {
return inspirations;
}
const floorName = floors[area.floor_id]?.name;
const labelNames = area.labels
.map((labelId) => labels[labelId])
.filter(Boolean);
inspirations.push(
`- ${area.name} (${floorName ? `floor: ${floorName}` : "no floor"}${labelNames.length ? `, labels: ${labelNames.join(", ")}` : ""})`
);
return inspirations;
}, []);
};

View File

@@ -33,9 +33,11 @@ import {
generateMetadataSuggestionTask,
processMetadataSuggestion,
} from "../../common/suggest-metadata-ai";
import { buildEntityMetadataInspirations } from "../../common/suggest-metadata-inspirations";
import type { SceneConfig } from "../../../../data/scene";
const SUGGESTION_CONFIG: MetadataSuggestionInclude = {
description: false,
const SUGGESTION_INCLUDE: MetadataSuggestionInclude = {
name: true,
categories: true,
labels: true,
};
@@ -281,13 +283,17 @@ class DialogSceneSave extends LitElement {
}
private _generateTask = async (): Promise<SuggestWithAIGenerateTask> =>
generateMetadataSuggestionTask(
generateMetadataSuggestionTask<SceneConfig>(
this.hass.connection,
this.hass.states,
this.hass.language,
"scene",
this._params.config,
SUGGESTION_CONFIG
await buildEntityMetadataInspirations(
this.hass.connection,
this.hass.states,
"scene"
),
SUGGESTION_INCLUDE
);
private async _handleSuggestion(
@@ -298,12 +304,14 @@ class DialogSceneSave extends LitElement {
this.hass.connection,
"scene",
result,
SUGGESTION_CONFIG
SUGGESTION_INCLUDE
);
this._newName = processed.name;
if (this._error && this._newName.trim()) {
this._error = false;
if (processed.name) {
this._newName = processed.name;
if (this._error && this._newName.trim()) {
this._error = false;
}
}
if (processed.category) {