@pellux/goodvibes-sdk 0.27.1 → 0.27.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/contracts/artifacts/operator-contract.json +870 -4
- package/dist/_internal/contracts/generated/foundation-metadata.d.ts +2 -2
- package/dist/_internal/contracts/generated/foundation-metadata.js +2 -2
- package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
- package/dist/_internal/contracts/generated/operator-contract.js +870 -4
- package/dist/_internal/contracts/generated/operator-method-ids.d.ts +1 -1
- package/dist/_internal/contracts/generated/operator-method-ids.d.ts.map +1 -1
- package/dist/_internal/contracts/generated/operator-method-ids.js +1 -0
- package/dist/_internal/daemon/knowledge-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-routes.js +27 -0
- package/dist/_internal/platform/control-plane/method-catalog-homegraph.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/method-catalog-homegraph.js +41 -2
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.js +15 -1
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts +1 -0
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +28 -0
- package/dist/_internal/platform/daemon/http/home-graph-routes.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/http/home-graph-routes.js +50 -0
- package/dist/_internal/platform/knowledge/extractors.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/extractors.js +1 -116
- package/dist/_internal/platform/knowledge/home-graph/auto-link.d.ts +27 -0
- package/dist/_internal/platform/knowledge/home-graph/auto-link.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/home-graph/auto-link.js +236 -0
- package/dist/_internal/platform/knowledge/home-graph/extraction-quality.d.ts +2 -0
- package/dist/_internal/platform/knowledge/home-graph/extraction-quality.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/home-graph/extraction-quality.js +52 -0
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts +1 -0
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +14 -8
- package/dist/_internal/platform/knowledge/home-graph/index.d.ts +1 -1
- package/dist/_internal/platform/knowledge/home-graph/index.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/pages.d.ts +11 -0
- package/dist/_internal/platform/knowledge/home-graph/pages.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/home-graph/pages.js +44 -0
- package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts +5 -6
- package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/rendering.js +284 -5
- package/dist/_internal/platform/knowledge/home-graph/search.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/search.js +4 -6
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts +6 -1
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/service.js +60 -6
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts +47 -1
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/index.d.ts +2 -2
- package/dist/_internal/platform/knowledge/index.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/map-filters.d.ts +24 -0
- package/dist/_internal/platform/knowledge/map-filters.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/map-filters.js +199 -0
- package/dist/_internal/platform/knowledge/map.d.ts +16 -1
- package/dist/_internal/platform/knowledge/map.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/map.js +19 -8
- package/dist/_internal/platform/knowledge/pdf-extractor.d.ts +3 -0
- package/dist/_internal/platform/knowledge/pdf-extractor.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/pdf-extractor.js +346 -0
- package/dist/_internal/platform/knowledge/service.d.ts +2 -6
- package/dist/_internal/platform/knowledge/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/types.d.ts +39 -1
- package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { renderKnowledgeMap } from '../map.js';
|
|
2
|
-
import {
|
|
2
|
+
import { countFacet, normalizeStringArray, readString } from '../map-filters.js';
|
|
3
|
+
import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
|
|
4
|
+
import { edgeIsActive, isGeneratedPageSource, readRecord, uniqueStrings } from './helpers.js';
|
|
3
5
|
export function renderRoomPage(state, areaId) {
|
|
4
6
|
const area = areaId
|
|
5
7
|
? findNodeByHaId(state.nodes, 'ha_area', areaId) ?? findNodeByHaId(state.nodes, 'ha_room', areaId)
|
|
@@ -27,33 +29,53 @@ export function renderRoomPage(state, areaId) {
|
|
|
27
29
|
'',
|
|
28
30
|
`Knowledge space: \`${state.spaceId}\``,
|
|
29
31
|
'',
|
|
32
|
+
renderRoomOverview(title, devices, entities, automations, scenes, scripts),
|
|
30
33
|
renderNodeList('Devices', devices),
|
|
31
34
|
renderNodeList('Entities', entities),
|
|
32
35
|
renderNodeList('Automations', automations),
|
|
33
36
|
renderNodeList('Scenes', scenes),
|
|
34
37
|
renderNodeList('Scripts', scripts),
|
|
38
|
+
renderSourceEvidence('Source-Backed Knowledge', sources, state.extractions ?? [], {
|
|
39
|
+
tokens: uniqueStrings([
|
|
40
|
+
title,
|
|
41
|
+
...devices.map((node) => node.title),
|
|
42
|
+
...entities.map((node) => node.title),
|
|
43
|
+
].flatMap(tokenizeEvidence)),
|
|
44
|
+
maxSources: 12,
|
|
45
|
+
maxSnippetsPerSource: 2,
|
|
46
|
+
}),
|
|
35
47
|
renderSourceList('Linked Sources', sources),
|
|
36
48
|
renderIssueList('Open Issues', state.issues.filter((issue) => issue.status === 'open')),
|
|
37
49
|
].filter(Boolean).join('\n');
|
|
38
50
|
}
|
|
39
51
|
export function renderDevicePassportPage(input) {
|
|
52
|
+
const deviceTokens = deviceEvidenceTokens(input.device, input.entities);
|
|
40
53
|
return [
|
|
41
54
|
`# ${input.device.title}`,
|
|
42
55
|
'',
|
|
43
56
|
`Knowledge space: \`${input.spaceId}\``,
|
|
44
57
|
'',
|
|
45
|
-
'##
|
|
58
|
+
'## Overview',
|
|
59
|
+
'',
|
|
60
|
+
renderDeviceOverview(input.device, input.entities, input.sources),
|
|
61
|
+
'',
|
|
62
|
+
'## Home Assistant Profile',
|
|
46
63
|
'',
|
|
47
64
|
renderMetadataField('Manufacturer', input.device.metadata.manufacturer),
|
|
48
65
|
renderMetadataField('Model', input.device.metadata.model),
|
|
49
66
|
renderMetadataField('Area', readHa(input.device, 'areaId')),
|
|
50
67
|
renderMetadataField('Device id', readHa(input.device, 'deviceId')),
|
|
51
68
|
'',
|
|
52
|
-
renderNodeList('Entities', input.entities),
|
|
69
|
+
renderNodeList('Entities Exposed To Home Assistant', input.entities),
|
|
70
|
+
renderSourceEvidence('Source-Backed Features And Notes', input.sources, input.extractions ?? [], {
|
|
71
|
+
tokens: deviceTokens,
|
|
72
|
+
maxSources: 8,
|
|
73
|
+
maxSnippetsPerSource: 4,
|
|
74
|
+
}),
|
|
53
75
|
renderSourceList('Sources', input.sources),
|
|
54
76
|
renderIssueList('Issues', input.issues),
|
|
55
77
|
input.missingFields.length > 0
|
|
56
|
-
? ['##
|
|
78
|
+
? ['## Open Questions', '', ...input.missingFields.map((field) => `- ${field}`), ''].join('\n')
|
|
57
79
|
: '',
|
|
58
80
|
].filter(Boolean).join('\n');
|
|
59
81
|
}
|
|
@@ -77,11 +99,132 @@ export function renderPacketPage(state, options = {}) {
|
|
|
77
99
|
].filter(Boolean).join('\n');
|
|
78
100
|
}
|
|
79
101
|
export function renderHomeGraphMap(state, options = {}) {
|
|
80
|
-
|
|
102
|
+
const filtered = applyHomeGraphMapFilters(state, options.ha);
|
|
103
|
+
const result = renderKnowledgeMap(filtered, {
|
|
81
104
|
...options,
|
|
82
105
|
title: state.title,
|
|
83
106
|
spaceId: state.spaceId,
|
|
84
107
|
});
|
|
108
|
+
return {
|
|
109
|
+
...result,
|
|
110
|
+
facets: {
|
|
111
|
+
...result.facets,
|
|
112
|
+
homeAssistant: buildHomeAssistantMapFacets(state),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function applyHomeGraphMapFilters(state, input) {
|
|
117
|
+
const filters = normalizeHomeGraphMapFilters(input);
|
|
118
|
+
if (!hasHomeGraphFilters(filters))
|
|
119
|
+
return state;
|
|
120
|
+
const nodes = state.nodes.filter((node) => matchesHomeGraphNode(node, state.edges, filters, state.nodes));
|
|
121
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
122
|
+
const sourceIds = sourceIdsLinkedToNodes(state.edges, nodeIds);
|
|
123
|
+
const sources = state.sources.filter((source) => sourceIds.has(source.id) || matchesHomeGraphSource(source, filters));
|
|
124
|
+
const sourceSet = new Set(sources.map((source) => source.id));
|
|
125
|
+
const issues = state.issues.filter((issue) => ((issue.nodeId && nodeIds.has(issue.nodeId))
|
|
126
|
+
|| (issue.sourceId && sourceSet.has(issue.sourceId))));
|
|
127
|
+
const visibleIds = new Set([...nodeIds, ...sourceSet, ...issues.map((issue) => issue.id)]);
|
|
128
|
+
const edges = state.edges.filter((edge) => visibleIds.has(edge.fromId) && visibleIds.has(edge.toId));
|
|
129
|
+
return { ...state, nodes, sources, issues, edges };
|
|
130
|
+
}
|
|
131
|
+
function buildHomeAssistantMapFacets(state) {
|
|
132
|
+
return {
|
|
133
|
+
objectKinds: countFacet(state.nodes.map((node) => readHomeAssistantRecord(node).objectKind ?? node.kind)),
|
|
134
|
+
entityIds: countFacet(state.nodes.map((node) => readHomeAssistantRecord(node).entityId)),
|
|
135
|
+
deviceIds: countFacet(state.nodes.map((node) => readHomeAssistantRecord(node).deviceId)),
|
|
136
|
+
areaIds: countFacet(state.nodes.map((node) => readHomeAssistantRecord(node).areaId)),
|
|
137
|
+
integrationIds: countFacet(state.nodes.map((node) => readHomeAssistantRecord(node).integrationId)),
|
|
138
|
+
integrationDomains: countFacet(state.nodes.map((node) => readIntegrationDomain(node))),
|
|
139
|
+
domains: countFacet(state.nodes.map((node) => readEntityDomain(node))),
|
|
140
|
+
deviceClasses: countFacet(state.nodes.map((node) => readDeviceClass(node))),
|
|
141
|
+
labels: countFacet(state.nodes.flatMap((node) => normalizeStringArray(node.metadata.labels))),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function normalizeHomeGraphMapFilters(input) {
|
|
145
|
+
return {
|
|
146
|
+
objectKinds: normalizeStringArray(input?.objectKinds),
|
|
147
|
+
entityIds: normalizeStringArray(input?.entityIds),
|
|
148
|
+
deviceIds: normalizeStringArray(input?.deviceIds),
|
|
149
|
+
areaIds: normalizeStringArray(input?.areaIds),
|
|
150
|
+
integrationIds: normalizeStringArray(input?.integrationIds),
|
|
151
|
+
integrationDomains: normalizeStringArray(input?.integrationDomains),
|
|
152
|
+
domains: normalizeStringArray(input?.domains),
|
|
153
|
+
deviceClasses: normalizeStringArray(input?.deviceClasses),
|
|
154
|
+
labels: normalizeStringArray(input?.labels),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function hasHomeGraphFilters(filters) {
|
|
158
|
+
return Object.values(filters).some((value) => value.length > 0);
|
|
159
|
+
}
|
|
160
|
+
function matchesHomeGraphNode(node, edges, filters, nodes) {
|
|
161
|
+
const ha = readHomeAssistantRecord(node);
|
|
162
|
+
return matchesFilter(filters.objectKinds, String(ha.objectKind ?? node.kind), node.kind)
|
|
163
|
+
&& matchesFilter(filters.entityIds, readString(ha.entityId), readString(ha.objectId))
|
|
164
|
+
&& matchesFilter(filters.deviceIds, readString(ha.deviceId), node.kind === 'ha_device' ? readString(ha.objectId) : undefined)
|
|
165
|
+
&& matchesAreaFilter(filters.areaIds, node, edges, nodes)
|
|
166
|
+
&& matchesFilter(filters.integrationIds, readString(ha.integrationId), node.kind === 'ha_integration' ? readString(ha.objectId) : undefined)
|
|
167
|
+
&& matchesFilter(filters.integrationDomains, readIntegrationDomain(node))
|
|
168
|
+
&& matchesFilter(filters.domains, readEntityDomain(node))
|
|
169
|
+
&& matchesFilter(filters.deviceClasses, readDeviceClass(node))
|
|
170
|
+
&& matchesAny(filters.labels, normalizeStringArray(node.metadata.labels));
|
|
171
|
+
}
|
|
172
|
+
function matchesHomeGraphSource(source, filters) {
|
|
173
|
+
return matchesAny(filters.labels, [...source.tags, ...normalizeStringArray(source.metadata.labels)]);
|
|
174
|
+
}
|
|
175
|
+
function matchesAreaFilter(areaIds, node, edges, nodes) {
|
|
176
|
+
if (areaIds.length === 0)
|
|
177
|
+
return true;
|
|
178
|
+
const ha = readHomeAssistantRecord(node);
|
|
179
|
+
if (matchesFilter(areaIds, readString(ha.areaId), node.kind === 'ha_area' || node.kind === 'ha_room' ? readString(ha.objectId) : undefined)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const targetAreaNodeIds = new Set(nodes
|
|
183
|
+
.filter((candidate) => (candidate.kind === 'ha_area' || candidate.kind === 'ha_room')
|
|
184
|
+
&& matchesFilter(areaIds, readString(readHomeAssistantRecord(candidate).objectId), readString(readHomeAssistantRecord(candidate).areaId)))
|
|
185
|
+
.map((candidate) => candidate.id));
|
|
186
|
+
return edges.some((edge) => edgeIsActive(edge)
|
|
187
|
+
&& edge.fromId === node.id
|
|
188
|
+
&& edge.relation === 'located_in'
|
|
189
|
+
&& targetAreaNodeIds.has(edge.toId));
|
|
190
|
+
}
|
|
191
|
+
function sourceIdsLinkedToNodes(edges, nodeIds) {
|
|
192
|
+
const sourceIds = new Set();
|
|
193
|
+
for (const edge of edges) {
|
|
194
|
+
if (edge.fromKind === 'source' && edge.toKind === 'node' && nodeIds.has(edge.toId))
|
|
195
|
+
sourceIds.add(edge.fromId);
|
|
196
|
+
if (edge.fromKind === 'node' && nodeIds.has(edge.fromId) && edge.toKind === 'source')
|
|
197
|
+
sourceIds.add(edge.toId);
|
|
198
|
+
}
|
|
199
|
+
return sourceIds;
|
|
200
|
+
}
|
|
201
|
+
function readHomeAssistantRecord(node) {
|
|
202
|
+
const value = node.metadata.homeAssistant;
|
|
203
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
204
|
+
}
|
|
205
|
+
function readIntegrationDomain(node) {
|
|
206
|
+
const ha = readHomeAssistantRecord(node);
|
|
207
|
+
return readString(ha.integrationId)
|
|
208
|
+
?? readString(node.metadata.integrationDomain)
|
|
209
|
+
?? readString(node.metadata.platform)
|
|
210
|
+
?? readString(node.metadata.domain);
|
|
211
|
+
}
|
|
212
|
+
function readEntityDomain(node) {
|
|
213
|
+
const entityId = readString(readHomeAssistantRecord(node).entityId);
|
|
214
|
+
const fromEntity = entityId?.includes('.') ? entityId.split('.')[0] : undefined;
|
|
215
|
+
return fromEntity ?? readString(node.metadata.domain);
|
|
216
|
+
}
|
|
217
|
+
function readDeviceClass(node) {
|
|
218
|
+
const attributes = node.metadata.attributes && typeof node.metadata.attributes === 'object' && !Array.isArray(node.metadata.attributes)
|
|
219
|
+
? node.metadata.attributes
|
|
220
|
+
: {};
|
|
221
|
+
return readString(node.metadata.deviceClass) ?? readString(node.metadata.device_class) ?? readString(attributes.device_class);
|
|
222
|
+
}
|
|
223
|
+
function matchesFilter(allowed, ...values) {
|
|
224
|
+
return allowed.length === 0 || values.some((value) => value !== undefined && allowed.includes(value));
|
|
225
|
+
}
|
|
226
|
+
function matchesAny(allowed, values) {
|
|
227
|
+
return allowed.length === 0 || values.some((value) => allowed.includes(value));
|
|
85
228
|
}
|
|
86
229
|
function renderNodeList(title, nodes) {
|
|
87
230
|
if (nodes.length === 0)
|
|
@@ -93,6 +236,142 @@ function renderNodeList(title, nodes) {
|
|
|
93
236
|
'',
|
|
94
237
|
].join('\n');
|
|
95
238
|
}
|
|
239
|
+
function renderRoomOverview(title, devices, entities, automations, scenes, scripts) {
|
|
240
|
+
return [
|
|
241
|
+
'## Overview',
|
|
242
|
+
'',
|
|
243
|
+
`${title} currently has ${devices.length} device(s), ${entities.length} entity record(s), ${automations.length} automation(s), ${scenes.length} scene(s), and ${scripts.length} script(s) in the Home Graph.`,
|
|
244
|
+
'',
|
|
245
|
+
].join('\n');
|
|
246
|
+
}
|
|
247
|
+
function renderDeviceOverview(device, entities, sources) {
|
|
248
|
+
const identity = uniqueStrings([
|
|
249
|
+
readMetadataString(device, 'manufacturer'),
|
|
250
|
+
readMetadataString(device, 'model'),
|
|
251
|
+
readHa(device, 'areaId') ? `area ${readHa(device, 'areaId')}` : undefined,
|
|
252
|
+
]);
|
|
253
|
+
const details = identity.length > 0 ? ` ${identity.join(' - ')}.` : '.';
|
|
254
|
+
return `${device.title}${details} The Home Graph links this device to ${entities.length} Home Assistant entity record(s) and ${sources.length} source(s).`;
|
|
255
|
+
}
|
|
256
|
+
function renderSourceEvidence(title, sources, extractions, options) {
|
|
257
|
+
const extractionBySourceId = new Map(extractions.map((extraction) => [extraction.sourceId, extraction]));
|
|
258
|
+
const entries = sources
|
|
259
|
+
.map((source) => ({
|
|
260
|
+
source,
|
|
261
|
+
snippets: sourceEvidenceSnippets(source, extractionBySourceId.get(source.id), options.tokens, options.maxSnippetsPerSource),
|
|
262
|
+
}))
|
|
263
|
+
.filter((entry) => entry.snippets.length > 0)
|
|
264
|
+
.slice(0, options.maxSources);
|
|
265
|
+
if (entries.length === 0) {
|
|
266
|
+
return [
|
|
267
|
+
`## ${title}`,
|
|
268
|
+
'',
|
|
269
|
+
'No linked source-backed details have been extracted yet.',
|
|
270
|
+
'',
|
|
271
|
+
].join('\n');
|
|
272
|
+
}
|
|
273
|
+
return [
|
|
274
|
+
`## ${title}`,
|
|
275
|
+
'',
|
|
276
|
+
...entries.flatMap((entry) => [
|
|
277
|
+
`### ${entry.source.title ?? entry.source.sourceUri ?? entry.source.id}`,
|
|
278
|
+
'',
|
|
279
|
+
...entry.snippets.map((snippet) => `- ${snippet}`),
|
|
280
|
+
'',
|
|
281
|
+
]),
|
|
282
|
+
].join('\n');
|
|
283
|
+
}
|
|
284
|
+
function sourceEvidenceSnippets(source, extraction, tokens, limit) {
|
|
285
|
+
const structure = readRecord(extraction?.structure);
|
|
286
|
+
const searchText = typeof structure.searchText === 'string' ? structure.searchText : undefined;
|
|
287
|
+
const candidates = uniqueStrings([
|
|
288
|
+
extraction?.summary,
|
|
289
|
+
extraction?.excerpt,
|
|
290
|
+
...(extraction?.sections ?? []),
|
|
291
|
+
...evidenceWindows(searchText, tokens),
|
|
292
|
+
...featureSentences(searchText),
|
|
293
|
+
source.summary,
|
|
294
|
+
source.description,
|
|
295
|
+
]).filter((entry) => !isUnusableHomeGraphExtractionText(entry)).slice(0, 80);
|
|
296
|
+
const scored = candidates
|
|
297
|
+
.map((text) => ({ text: clampEvidence(text), score: evidenceScore(text, tokens) }))
|
|
298
|
+
.filter((entry) => entry.text.length > 0)
|
|
299
|
+
.sort((left, right) => right.score - left.score || left.text.length - right.text.length);
|
|
300
|
+
return uniqueStrings(scored.map((entry) => entry.text)).slice(0, limit);
|
|
301
|
+
}
|
|
302
|
+
function evidenceWindows(value, tokens) {
|
|
303
|
+
const text = normalizeWhitespace(value ?? '');
|
|
304
|
+
if (!text)
|
|
305
|
+
return [];
|
|
306
|
+
const lower = text.toLowerCase();
|
|
307
|
+
const windows = [];
|
|
308
|
+
for (const token of tokens) {
|
|
309
|
+
if (token.length < 3)
|
|
310
|
+
continue;
|
|
311
|
+
const index = lower.indexOf(token.toLowerCase());
|
|
312
|
+
if (index < 0)
|
|
313
|
+
continue;
|
|
314
|
+
const start = Math.max(0, index - 180);
|
|
315
|
+
const end = Math.min(text.length, index + 420);
|
|
316
|
+
windows.push(`${start > 0 ? '...' : ''}${text.slice(start, end).trim()}${end < text.length ? '...' : ''}`);
|
|
317
|
+
}
|
|
318
|
+
return windows;
|
|
319
|
+
}
|
|
320
|
+
function featureSentences(value) {
|
|
321
|
+
const text = normalizeWhitespace(value ?? '');
|
|
322
|
+
if (!text)
|
|
323
|
+
return [];
|
|
324
|
+
const keywords = /\b(feature|features|support|supports|capability|capabilities|specification|specifications|mode|modes|hdmi|hdr|dolby|battery|reset|warranty|firmware|voice|remote)\b/i;
|
|
325
|
+
return (text.match(/[^.!?\n]+[.!?]?/g) ?? [])
|
|
326
|
+
.map((entry) => entry.trim())
|
|
327
|
+
.filter((entry) => keywords.test(entry))
|
|
328
|
+
.slice(0, 24);
|
|
329
|
+
}
|
|
330
|
+
function evidenceScore(value, tokens) {
|
|
331
|
+
const lower = value.toLowerCase();
|
|
332
|
+
let score = 0;
|
|
333
|
+
for (const token of tokens) {
|
|
334
|
+
if (token.length >= 3 && lower.includes(token.toLowerCase()))
|
|
335
|
+
score += 10;
|
|
336
|
+
}
|
|
337
|
+
if (/\b(feature|features|support|supports|mode|hdmi|hdr|dolby|voice|remote)\b/i.test(value))
|
|
338
|
+
score += 25;
|
|
339
|
+
return score;
|
|
340
|
+
}
|
|
341
|
+
function deviceEvidenceTokens(device, entities) {
|
|
342
|
+
return uniqueStrings([
|
|
343
|
+
device.title,
|
|
344
|
+
...device.aliases,
|
|
345
|
+
readMetadataString(device, 'manufacturer'),
|
|
346
|
+
readMetadataString(device, 'model'),
|
|
347
|
+
readHa(device, 'deviceId'),
|
|
348
|
+
...entities.flatMap((entity) => [
|
|
349
|
+
entity.title,
|
|
350
|
+
readHa(entity, 'entityId'),
|
|
351
|
+
readMetadataString(entity, 'domain'),
|
|
352
|
+
readMetadataString(entity, 'platform'),
|
|
353
|
+
]),
|
|
354
|
+
].flatMap(tokenizeEvidence));
|
|
355
|
+
}
|
|
356
|
+
function tokenizeEvidence(value) {
|
|
357
|
+
if (!value)
|
|
358
|
+
return [];
|
|
359
|
+
return value.toLowerCase()
|
|
360
|
+
.split(/[^a-z0-9_.:-]+/)
|
|
361
|
+
.map((entry) => entry.trim())
|
|
362
|
+
.filter((entry) => entry.length >= 2 && !['the', 'and', 'home', 'assistant', 'device'].includes(entry));
|
|
363
|
+
}
|
|
364
|
+
function clampEvidence(value) {
|
|
365
|
+
const normalized = normalizeWhitespace(value);
|
|
366
|
+
return normalized.length <= 420 ? normalized : `${normalized.slice(0, 419).trim()}...`;
|
|
367
|
+
}
|
|
368
|
+
function normalizeWhitespace(value) {
|
|
369
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
370
|
+
}
|
|
371
|
+
function readMetadataString(node, key) {
|
|
372
|
+
const value = node.metadata[key];
|
|
373
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
374
|
+
}
|
|
96
375
|
function renderSourceList(title, sources) {
|
|
97
376
|
if (sources.length === 0)
|
|
98
377
|
return '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqGxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAmFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAwMD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { belongsToSpace, edgeIsActive, isGeneratedPageSource, readRecord } from './helpers.js';
|
|
2
|
+
import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
|
|
2
3
|
const MAX_FIELD_CHARS = 4_096;
|
|
3
4
|
const MAX_SECTION_COUNT = 32;
|
|
4
5
|
const MAX_SEARCH_TEXT_CHARS = 64 * 1024;
|
|
@@ -258,12 +259,13 @@ function readSearchText(extraction) {
|
|
|
258
259
|
return undefined;
|
|
259
260
|
const structure = readRecord(extraction.structure);
|
|
260
261
|
const metadata = readRecord(extraction.metadata);
|
|
261
|
-
|
|
262
|
+
const text = firstBoundedText([
|
|
262
263
|
structure.searchText,
|
|
263
264
|
structure.text,
|
|
264
265
|
structure.content,
|
|
265
266
|
metadata.searchText,
|
|
266
267
|
], MAX_SEARCH_TEXT_CHARS);
|
|
268
|
+
return isUnusableHomeGraphExtractionText(text) ? undefined : text;
|
|
267
269
|
}
|
|
268
270
|
function readNodeMetadataText(node) {
|
|
269
271
|
const homeAssistant = readRecord(node.metadata.homeAssistant);
|
|
@@ -520,11 +522,7 @@ function usefulExtractionSummary(extraction) {
|
|
|
520
522
|
return summary && !isLowInformationExtractionText(summary) ? summary : undefined;
|
|
521
523
|
}
|
|
522
524
|
function isLowInformationExtractionText(value) {
|
|
523
|
-
|
|
524
|
-
return normalized.includes('pdf extraction produced limited text')
|
|
525
|
-
|| normalized.includes('no readable text streams')
|
|
526
|
-
|| normalized.includes('no specialized extractor matched')
|
|
527
|
-
|| normalized.includes('has no specialized in-core extractor');
|
|
525
|
+
return isUnusableHomeGraphExtractionText(value);
|
|
528
526
|
}
|
|
529
527
|
function sentenceChunks(value) {
|
|
530
528
|
const text = cleanWhitespace(value ?? '');
|
|
@@ -2,7 +2,7 @@ import type { ArtifactStore } from '../../artifacts/index.js';
|
|
|
2
2
|
import type { KnowledgeStore } from '../store.js';
|
|
3
3
|
import type { KnowledgeEdgeRecord, KnowledgeIssueRecord, KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
|
|
4
4
|
import { type HomeGraphReviewResult } from './review.js';
|
|
5
|
-
import type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphMapInput, HomeGraphMapResult, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphReindexResult, HomeGraphReviewInput, HomeGraphSpaceInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult } from './types.js';
|
|
5
|
+
import type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphMapInput, HomeGraphMapResult, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphPageListResult, HomeGraphReindexResult, HomeGraphReviewInput, HomeGraphSpaceInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult } from './types.js';
|
|
6
6
|
export declare class HomeGraphService {
|
|
7
7
|
private readonly store;
|
|
8
8
|
private readonly artifactStore;
|
|
@@ -41,6 +41,10 @@ export declare class HomeGraphService {
|
|
|
41
41
|
readonly spaceId: string;
|
|
42
42
|
readonly sources: readonly KnowledgeSourceRecord[];
|
|
43
43
|
}>;
|
|
44
|
+
listPages(input?: HomeGraphSpaceInput & {
|
|
45
|
+
readonly limit?: number;
|
|
46
|
+
readonly includeMarkdown?: boolean;
|
|
47
|
+
}): Promise<HomeGraphPageListResult>;
|
|
44
48
|
browse(input?: HomeGraphSpaceInput & {
|
|
45
49
|
readonly limit?: number;
|
|
46
50
|
}): Promise<{
|
|
@@ -68,6 +72,7 @@ export declare class HomeGraphService {
|
|
|
68
72
|
}>;
|
|
69
73
|
private ingestCreatedArtifact;
|
|
70
74
|
private extractArtifact;
|
|
75
|
+
private autoLinkExistingSources;
|
|
71
76
|
private upsertHomeNode;
|
|
72
77
|
private upsertSnapshotObjects;
|
|
73
78
|
private linkSnapshotObjectRelations;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EAEnB,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EAEtB,MAAM,aAAa,CAAC;AAmBrB,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EAEnB,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EAEtB,MAAM,aAAa,CAAC;AAmBrB,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AA+B9E,OAAO,KAAK,EACV,iBAAiB,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,eAAe,EACrF,4BAA4B,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,uBAAuB,EAC5E,kBAAkB,EAAE,mBAAmB,EACjE,iBAAiB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,yBAAyB,EAC1F,uBAAuB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,mBAAmB,EAC1F,sBAAsB,EAAE,eAAe,EAAE,mBAAmB,EAC7D,MAAM,YAAY,CAAC;AAGpB,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa;gBADb,KAAK,EAAE,cAAc,EACrB,aAAa,EAAE,aAAa;IAGzC,MAAM,CAAC,KAAK,GAAE;QAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAqBtH,YAAY,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAkDzE,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA0BzE,UAAU,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6B3E,cAAc,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA8BnF,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoBtE,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAYxE,GAAG,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiC1D,OAAO,CAAC,KAAK,GAAE,mBAAwB,GAAG,OAAO,CAAC,sBAAsB,CAAC;YAsBjE,2BAA2B;IAoCnC,qBAAqB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,6BAA6B,CAAC;IAY9F,gBAAgB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAYrF,cAAc,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAYnF,UAAU,CAAC,KAAK,EAAE,mBAAmB,GAAG;QAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAA;KAAE,CAAC;IAYxG,UAAU,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAMvE,WAAW,CAAC,KAAK,GAAE,mBAAmB,GAAG;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QACxF,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAClB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;KACpD,CAAC;IAMI,SAAS,CAAC,KAAK,GAAE,mBAAmB,GAAG;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAa9I,MAAM,CAAC,KAAK,GAAE,mBAAmB,GAAG;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QACnF,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAClB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;QAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;QAC/C,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;QACnD,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;KAClD,CAAC;IAeI,GAAG,CAAC,KAAK,GAAE,iBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAS/D,WAAW,CAAC,KAAK,GAAE,mBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiBtE,WAAW,CAAC,KAAK,EAAE,mBAAmB,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;KAAE,GAAG,OAAO,CAAC;QAC1F,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAClB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;KACxJ,CAAC;YA+BY,qBAAqB;YAiDrB,eAAe;YAgCf,uBAAuB;YAgBvB,cAAc;YAmBd,qBAAqB;YAkDrB,2BAA2B;YAiB3B,WAAW;YAoBX,oBAAoB;IAIlC,OAAO,CAAC,iBAAiB;YAcX,YAAY;CAkC3B"}
|
|
@@ -5,8 +5,11 @@ import { refreshHomeGraphQualityIssues } from './quality.js';
|
|
|
5
5
|
import { reviewHomeGraphFact } from './review.js';
|
|
6
6
|
import { collectLinkedObjects, inferHomeGraphSourceType, readHomeGraphState, renderAskAnswer, renderHomeGraphState, safeHomeGraphFilename, } from './state.js';
|
|
7
7
|
import { renderHomeGraphMap } from './rendering.js';
|
|
8
|
-
import {
|
|
8
|
+
import { autoLinkHomeGraphSource, autoLinkHomeGraphSources, } from './auto-link.js';
|
|
9
|
+
import { generateAutomaticHomeGraphPages, generateHomeGraphPacket, generateHomeGraphRoomPage, refreshAutomaticHomeGraphPages, refreshHomeGraphDevicePassport, } from './generated-pages.js';
|
|
9
10
|
import { reindexHomeGraphSources } from './reindex.js';
|
|
11
|
+
import { listHomeGraphPages } from './pages.js';
|
|
12
|
+
import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
|
|
10
13
|
import { readHomeGraphSearchState, scoreHomeGraphResults, selectHomeGraphExtractionRepairCandidates, } from './search.js';
|
|
11
14
|
import { resolveReadableHomeGraphSpace } from './space-selection.js';
|
|
12
15
|
import { HOME_GRAPH_CAPABILITIES } from './types.js';
|
|
@@ -61,6 +64,7 @@ export class HomeGraphService {
|
|
|
61
64
|
const beforeNodeIds = new Set(readHomeGraphState(this.store, spaceId).nodes.map((node) => node.id));
|
|
62
65
|
const beforeEdgeIds = new Set(readHomeGraphState(this.store, spaceId).edges.map((edge) => edge.id));
|
|
63
66
|
const groups = await this.upsertSnapshotObjects(spaceId, installationId, input, home.id, source.id);
|
|
67
|
+
await this.autoLinkExistingSources(spaceId, installationId);
|
|
64
68
|
const issues = await this.refreshQualityIssues(spaceId, installationId);
|
|
65
69
|
const generated = await generateAutomaticHomeGraphPages({
|
|
66
70
|
store: this.store,
|
|
@@ -227,13 +231,22 @@ export class HomeGraphService {
|
|
|
227
231
|
await this.store.init();
|
|
228
232
|
const { spaceId, installationId } = resolveReadableHomeGraphSpace(this.store, input);
|
|
229
233
|
const state = readHomeGraphSearchState(this.store, spaceId);
|
|
230
|
-
|
|
234
|
+
const reindex = await reindexHomeGraphSources({
|
|
231
235
|
spaceId,
|
|
232
236
|
sources: state.sources,
|
|
233
237
|
extractionBySourceId: state.extractionBySourceId,
|
|
234
238
|
artifactStore: this.artifactStore,
|
|
235
239
|
extract: (source, artifact) => this.extractArtifact(source, artifact, spaceId, installationId),
|
|
236
240
|
});
|
|
241
|
+
const linked = await this.autoLinkExistingSources(spaceId, installationId);
|
|
242
|
+
const generated = reindex.reparsed > 0 || linked.length > 0
|
|
243
|
+
? await refreshAutomaticHomeGraphPages({ store: this.store, artifactStore: this.artifactStore, spaceId, installationId })
|
|
244
|
+
: undefined;
|
|
245
|
+
return {
|
|
246
|
+
...reindex,
|
|
247
|
+
...(linked.length > 0 ? { linked } : {}),
|
|
248
|
+
...(generated ? { generated } : {}),
|
|
249
|
+
};
|
|
237
250
|
}
|
|
238
251
|
async repairWeakExtractionsForAsk(spaceId, installationId, query, state) {
|
|
239
252
|
const candidates = selectHomeGraphExtractionRepairCandidates(query, state.sources, state.nodes, state.edges, (sourceId) => state.extractionBySourceId.get(sourceId), 8);
|
|
@@ -246,6 +259,16 @@ export class HomeGraphService {
|
|
|
246
259
|
if (!artifact)
|
|
247
260
|
continue;
|
|
248
261
|
const extraction = await this.extractArtifact(source, artifact, spaceId, installationId);
|
|
262
|
+
if (extraction) {
|
|
263
|
+
await autoLinkHomeGraphSource({
|
|
264
|
+
store: this.store,
|
|
265
|
+
spaceId,
|
|
266
|
+
installationId,
|
|
267
|
+
source,
|
|
268
|
+
extraction,
|
|
269
|
+
state: readHomeGraphState(this.store, spaceId),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
249
272
|
if (extraction && extractionHasSearchableText(extraction))
|
|
250
273
|
repaired += 1;
|
|
251
274
|
}
|
|
@@ -305,6 +328,18 @@ export class HomeGraphService {
|
|
|
305
328
|
const { spaceId } = resolveReadableHomeGraphSpace(this.store, input);
|
|
306
329
|
return { ok: true, spaceId, sources: readHomeGraphState(this.store, spaceId).sources.slice(0, Math.max(1, input.limit ?? 100)) };
|
|
307
330
|
}
|
|
331
|
+
async listPages(input = {}) {
|
|
332
|
+
await this.store.init();
|
|
333
|
+
const { spaceId } = resolveReadableHomeGraphSpace(this.store, input);
|
|
334
|
+
const state = readHomeGraphState(this.store, spaceId);
|
|
335
|
+
return listHomeGraphPages({
|
|
336
|
+
artifactStore: this.artifactStore,
|
|
337
|
+
spaceId,
|
|
338
|
+
sources: state.sources,
|
|
339
|
+
limit: Math.max(1, input.limit ?? 100),
|
|
340
|
+
includeMarkdown: input.includeMarkdown !== false,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
308
343
|
async browse(input = {}) {
|
|
309
344
|
await this.store.init();
|
|
310
345
|
const { spaceId } = resolveReadableHomeGraphSpace(this.store, input);
|
|
@@ -323,8 +358,8 @@ export class HomeGraphService {
|
|
|
323
358
|
await this.store.init();
|
|
324
359
|
const { spaceId } = resolveReadableHomeGraphSpace(this.store, input);
|
|
325
360
|
return renderHomeGraphMap(renderHomeGraphState(this.store, spaceId, 'Home Graph Map'), {
|
|
326
|
-
|
|
327
|
-
|
|
361
|
+
...input,
|
|
362
|
+
knowledgeSpaceId: spaceId,
|
|
328
363
|
});
|
|
329
364
|
}
|
|
330
365
|
async exportSpace(input = {}) {
|
|
@@ -394,7 +429,14 @@ export class HomeGraphService {
|
|
|
394
429
|
const extraction = await this.extractArtifact(source, input.artifact, input.spaceId, input.installationId);
|
|
395
430
|
const linked = input.target
|
|
396
431
|
? (await this.linkKnowledge({ knowledgeSpaceId: input.spaceId, sourceId: source.id, target: input.target })).edge
|
|
397
|
-
:
|
|
432
|
+
: (await autoLinkHomeGraphSource({
|
|
433
|
+
store: this.store,
|
|
434
|
+
spaceId: input.spaceId,
|
|
435
|
+
installationId: input.installationId,
|
|
436
|
+
source,
|
|
437
|
+
...(extraction ? { extraction } : {}),
|
|
438
|
+
state: readHomeGraphState(this.store, input.spaceId),
|
|
439
|
+
}))?.edge;
|
|
398
440
|
return {
|
|
399
441
|
ok: true,
|
|
400
442
|
spaceId: input.spaceId,
|
|
@@ -432,6 +474,18 @@ export class HomeGraphService {
|
|
|
432
474
|
return undefined;
|
|
433
475
|
}
|
|
434
476
|
}
|
|
477
|
+
async autoLinkExistingSources(spaceId, installationId) {
|
|
478
|
+
const state = readHomeGraphState(this.store, spaceId);
|
|
479
|
+
const extractionBySourceId = new Map(state.extractions.map((extraction) => [extraction.sourceId, extraction]));
|
|
480
|
+
return autoLinkHomeGraphSources({
|
|
481
|
+
store: this.store,
|
|
482
|
+
spaceId,
|
|
483
|
+
installationId,
|
|
484
|
+
sources: state.sources,
|
|
485
|
+
extractionBySourceId,
|
|
486
|
+
state,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
435
489
|
async upsertHomeNode(spaceId, installationId, input) {
|
|
436
490
|
return this.store.upsertNode({
|
|
437
491
|
id: homeGraphNodeId(spaceId, 'ha_home', input.homeId ?? installationId),
|
|
@@ -565,5 +619,5 @@ export class HomeGraphService {
|
|
|
565
619
|
}
|
|
566
620
|
function extractionHasSearchableText(extraction) {
|
|
567
621
|
const structure = readRecord(extraction.structure);
|
|
568
|
-
return typeof structure.searchText === 'string' && structure.searchText
|
|
622
|
+
return typeof structure.searchText === 'string' && !isUnusableHomeGraphExtractionText(structure.searchText);
|
|
569
623
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeIssueRecord, KnowledgeMapEdge, KnowledgeMapNode, KnowledgeMapResult, KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
|
|
1
|
+
import type { KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeIssueRecord, KnowledgeMapFilterInput, KnowledgeMapEdge, KnowledgeMapNode, KnowledgeMapResult, KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
|
|
2
2
|
export declare const HOME_GRAPH_NODE_KINDS: readonly ["ha_home", "ha_entity", "ha_device", "ha_area", "ha_automation", "ha_script", "ha_scene", "ha_label", "ha_integration", "ha_room", "ha_device_passport", "ha_maintenance_item", "ha_troubleshooting_case", "ha_purchase", "ha_network_node"];
|
|
3
3
|
export type HomeGraphNodeKind = typeof HOME_GRAPH_NODE_KINDS[number];
|
|
4
4
|
export declare const HOME_GRAPH_RELATIONS: readonly ["controls", "located_in", "belongs_to_device", "has_manual", "has_receipt", "has_warranty", "has_issue", "fixed_by", "uses_battery", "connected_via", "part_of_network", "mentioned_by", "source_for"];
|
|
@@ -169,6 +169,35 @@ export interface HomeGraphAskResult {
|
|
|
169
169
|
export interface HomeGraphMapInput extends HomeGraphSpaceInput {
|
|
170
170
|
readonly limit?: number;
|
|
171
171
|
readonly includeSources?: boolean;
|
|
172
|
+
readonly includeIssues?: boolean;
|
|
173
|
+
readonly includeGenerated?: boolean;
|
|
174
|
+
readonly filters?: KnowledgeMapFilterInput;
|
|
175
|
+
readonly query?: string;
|
|
176
|
+
readonly recordKinds?: readonly ('source' | 'node' | 'issue')[];
|
|
177
|
+
readonly ids?: readonly string[];
|
|
178
|
+
readonly linkedToIds?: readonly string[];
|
|
179
|
+
readonly nodeKinds?: readonly string[];
|
|
180
|
+
readonly sourceTypes?: readonly string[];
|
|
181
|
+
readonly sourceStatuses?: readonly string[];
|
|
182
|
+
readonly nodeStatuses?: readonly string[];
|
|
183
|
+
readonly issueCodes?: readonly string[];
|
|
184
|
+
readonly issueStatuses?: readonly string[];
|
|
185
|
+
readonly issueSeverities?: readonly string[];
|
|
186
|
+
readonly edgeRelations?: readonly string[];
|
|
187
|
+
readonly tags?: readonly string[];
|
|
188
|
+
readonly minConfidence?: number;
|
|
189
|
+
readonly ha?: HomeGraphMapHaFilterInput;
|
|
190
|
+
}
|
|
191
|
+
export interface HomeGraphMapHaFilterInput {
|
|
192
|
+
readonly objectKinds?: readonly string[];
|
|
193
|
+
readonly entityIds?: readonly string[];
|
|
194
|
+
readonly deviceIds?: readonly string[];
|
|
195
|
+
readonly areaIds?: readonly string[];
|
|
196
|
+
readonly integrationIds?: readonly string[];
|
|
197
|
+
readonly integrationDomains?: readonly string[];
|
|
198
|
+
readonly domains?: readonly string[];
|
|
199
|
+
readonly deviceClasses?: readonly string[];
|
|
200
|
+
readonly labels?: readonly string[];
|
|
172
201
|
}
|
|
173
202
|
export type HomeGraphMapNode = KnowledgeMapNode;
|
|
174
203
|
export type HomeGraphMapEdge = KnowledgeMapEdge;
|
|
@@ -187,6 +216,14 @@ export interface HomeGraphReindexResult {
|
|
|
187
216
|
readonly sourceId: string;
|
|
188
217
|
readonly error: string;
|
|
189
218
|
}[];
|
|
219
|
+
readonly linked?: readonly {
|
|
220
|
+
readonly edge: KnowledgeEdgeRecord;
|
|
221
|
+
readonly node: KnowledgeNodeRecord;
|
|
222
|
+
readonly relation: string;
|
|
223
|
+
readonly score: number;
|
|
224
|
+
readonly reasons: readonly string[];
|
|
225
|
+
}[];
|
|
226
|
+
readonly generated?: HomeGraphGeneratedPagesSummary;
|
|
190
227
|
}
|
|
191
228
|
export interface HomeGraphSearchResult {
|
|
192
229
|
readonly kind: 'source' | 'node';
|
|
@@ -229,6 +266,15 @@ export interface HomeGraphDevicePassportResult extends HomeGraphProjectionResult
|
|
|
229
266
|
readonly passport: KnowledgeNodeRecord;
|
|
230
267
|
readonly missingFields: readonly string[];
|
|
231
268
|
}
|
|
269
|
+
export interface HomeGraphPageListResult {
|
|
270
|
+
readonly ok: true;
|
|
271
|
+
readonly spaceId: string;
|
|
272
|
+
readonly pages: readonly {
|
|
273
|
+
readonly source: KnowledgeSourceRecord;
|
|
274
|
+
readonly artifact?: HomeGraphProjectionResult['artifact'];
|
|
275
|
+
readonly markdown?: string;
|
|
276
|
+
}[];
|
|
277
|
+
}
|
|
232
278
|
export interface HomeGraphReviewInput extends HomeGraphSpaceInput {
|
|
233
279
|
readonly issueId?: string;
|
|
234
280
|
readonly nodeId?: string;
|