@pellux/goodvibes-sdk 0.28.8 → 0.28.9
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 +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
- package/dist/_internal/contracts/generated/operator-contract.js +1 -1
- package/dist/_internal/platform/knowledge/home-graph/search-utils.d.ts +3 -0
- package/dist/_internal/platform/knowledge/home-graph/search-utils.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/home-graph/search-utils.js +16 -0
- package/dist/_internal/platform/knowledge/home-graph/search.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/search.js +12 -13
- package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/answer.js +6 -0
- package/dist/_internal/platform/knowledge/semantic/homeassistant-scope.d.ts +11 -0
- package/dist/_internal/platform/knowledge/semantic/homeassistant-scope.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/homeassistant-scope.js +179 -0
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Synced from packages/contracts/src/generated/foundation-metadata.ts
|
|
2
2
|
export const FOUNDATION_METADATA = {
|
|
3
3
|
"productId": "goodvibes",
|
|
4
|
-
"productVersion": "0.28.
|
|
4
|
+
"productVersion": "0.28.9",
|
|
5
5
|
"operatorMethodCount": 264,
|
|
6
6
|
"operatorEventCount": 30,
|
|
7
7
|
"peerEndpointCount": 6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAKzF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAOvF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function intersects(left, right) {
|
|
2
|
+
for (const value of left) {
|
|
3
|
+
if (right.has(value))
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
export function isSingularObjectQuery(query, tokens) {
|
|
9
|
+
const normalized = query.toLowerCase();
|
|
10
|
+
if (/\b(the|this|that|my)\s+(tv|television|device|sensor|switch|camera|printer|router|phone)\b/.test(normalized))
|
|
11
|
+
return true;
|
|
12
|
+
if (tokens.includes('tv') || tokens.includes('television')) {
|
|
13
|
+
return !tokens.includes('tvs') && !tokens.includes('televisions');
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
@@ -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;AAIrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAsIxD,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,CAoFzB;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;AAmND,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { belongsToSpace, edgeIsActive, isGeneratedPageSource, readRecord } from './helpers.js';
|
|
2
2
|
import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
|
|
3
|
+
import { intersects, isSingularObjectQuery } from './search-utils.js';
|
|
3
4
|
const MAX_FIELD_CHARS = 4_096;
|
|
4
5
|
const MAX_SECTION_COUNT = 32;
|
|
5
6
|
const MAX_SEARCH_TEXT_CHARS = 64 * 1024;
|
|
@@ -66,6 +67,8 @@ const GENERIC_ANCHOR_TOKENS = new Set([
|
|
|
66
67
|
'smart',
|
|
67
68
|
'storage',
|
|
68
69
|
'switch',
|
|
70
|
+
'television',
|
|
71
|
+
'tv',
|
|
69
72
|
]);
|
|
70
73
|
const QUERY_EXPANSIONS = {
|
|
71
74
|
capability: ['capabilities', 'feature', 'features', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
|
|
@@ -149,7 +152,7 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
|
|
|
149
152
|
const expandedTokens = expandTokens(tokens);
|
|
150
153
|
const anchorTokens = selectAnchorQueryTokens(tokens);
|
|
151
154
|
const anchors = selectAnchorNodes(anchorTokens, nodes);
|
|
152
|
-
const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node));
|
|
155
|
+
const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node), isSingularObjectQuery(query, tokens));
|
|
153
156
|
const anchorIds = new Set(sourceAnchors.map((node) => node.id));
|
|
154
157
|
const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
|
|
155
158
|
const sourceLinks = buildSourceLinkIndex(edges);
|
|
@@ -234,7 +237,7 @@ export function selectHomeGraphExtractionRepairCandidates(query, sources, nodes,
|
|
|
234
237
|
if (tokens.length === 0)
|
|
235
238
|
return [];
|
|
236
239
|
const anchors = selectAnchorNodes(tokens, nodes);
|
|
237
|
-
const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
|
|
240
|
+
const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node), isSingularObjectQuery(query, tokens));
|
|
238
241
|
const anchorIds = new Set(sourceAnchors.map((anchor) => anchor.id));
|
|
239
242
|
const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
|
|
240
243
|
const sourceLinks = buildSourceLinkIndex(edges);
|
|
@@ -351,7 +354,7 @@ function sourceAnchorIdentityScore(anchorTokens, source, extraction) {
|
|
|
351
354
|
function selectAnchorNodes(tokens, nodes) {
|
|
352
355
|
if (tokens.length === 0)
|
|
353
356
|
return [];
|
|
354
|
-
|
|
357
|
+
const scored = nodes.map((node) => {
|
|
355
358
|
if (!isHomeGraphAnchorNode(node))
|
|
356
359
|
return { node, score: 0 };
|
|
357
360
|
const baseScore = scoreFields(tokens, nodeIdentityFields(node));
|
|
@@ -362,7 +365,10 @@ function selectAnchorNodes(tokens, nodes) {
|
|
|
362
365
|
};
|
|
363
366
|
})
|
|
364
367
|
.filter((entry) => entry.score >= 10)
|
|
365
|
-
.sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
|
|
368
|
+
.sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id));
|
|
369
|
+
const topScore = scored[0]?.score ?? 0;
|
|
370
|
+
return scored
|
|
371
|
+
.filter((entry) => entry.score >= Math.max(10, topScore - 12))
|
|
366
372
|
.slice(0, 12);
|
|
367
373
|
}
|
|
368
374
|
function selectAnchorQueryTokens(tokens) {
|
|
@@ -373,10 +379,10 @@ function isHomeGraphAnchorNode(node) {
|
|
|
373
379
|
return false;
|
|
374
380
|
return HOME_GRAPH_ANCHOR_KINDS.has(node.kind);
|
|
375
381
|
}
|
|
376
|
-
function selectSourceAnchors(tokens, nodes) {
|
|
382
|
+
function selectSourceAnchors(tokens, nodes, singularObjectQuery = false) {
|
|
377
383
|
const preferred = nodes.filter((node) => sourceAnchorIntentBoost(tokens, node) >= 0);
|
|
378
384
|
if (preferred.length > 0)
|
|
379
|
-
return preferred.slice(0,
|
|
385
|
+
return preferred.slice(0, singularObjectQuery ? 1 : ANCHOR_SCOPE_LIMIT);
|
|
380
386
|
return nodes.slice(0, ANCHOR_SCOPE_LIMIT);
|
|
381
387
|
}
|
|
382
388
|
function sourceAnchorIntentBoost(tokens, node) {
|
|
@@ -456,13 +462,6 @@ function nodeKindBoost(kind) {
|
|
|
456
462
|
return 0;
|
|
457
463
|
}
|
|
458
464
|
}
|
|
459
|
-
function intersects(left, right) {
|
|
460
|
-
for (const value of left) {
|
|
461
|
-
if (right.has(value))
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
465
|
function isPendingDocumentationCandidate(source, extraction) {
|
|
467
466
|
return !extraction
|
|
468
467
|
&& source.status !== 'indexed'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AA2BpB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAsCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CA+DxC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getKnowledgeSpaceId, isHomeAssistantKnowledgeSpace, isInKnowledgeSpace, normalizeKnowledgeSpaceId, } from '../spaces.js';
|
|
2
2
|
import { MAX_ANSWER_EVIDENCE_CHARS, clampText, normalizeWhitespace, readRecord, readString, scoreSemanticText, semanticHash, semanticMetadata, sourceSemanticText, splitSentences, tokenizeSemanticQuery, uniqueStrings, } from './utils.js';
|
|
3
3
|
import { hasConcreteFeatureSignal, isLowValueFeatureOrSpecText, isSemanticAnswerLinkedObject, semanticFactText, } from './fact-quality.js';
|
|
4
|
+
import { inferHomeAssistantAnswerScope, nodeInHomeAssistantAnswerScope, sourceInHomeAssistantAnswerScope, } from './homeassistant-scope.js';
|
|
4
5
|
const GENERIC_ANSWER_INTENT_TOKENS = new Set([
|
|
5
6
|
'capabilities',
|
|
6
7
|
'capability',
|
|
@@ -97,8 +98,12 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
|
|
|
97
98
|
const sourceFacts = buildSourceFactIndex(store, spaceId);
|
|
98
99
|
const linkedSourceIds = sourceIdsLinkedToNodes(store, new Set([...candidateNodeIds, ...linkedObjectIds]), spaceId);
|
|
99
100
|
const broadHomeAssistantAlias = normalizeKnowledgeSpaceId(spaceId) === 'homeassistant' && !strictCandidates;
|
|
101
|
+
const homeAssistantScope = !strictCandidates
|
|
102
|
+
? inferHomeAssistantAnswerScope(store, spaceId, input.query, subjectTokens)
|
|
103
|
+
: null;
|
|
100
104
|
const sourceItems = store.listSources(10_000)
|
|
101
105
|
.filter((source) => belongsToAnswerSpace(source, spaceId))
|
|
106
|
+
.filter((source) => sourceInHomeAssistantAnswerScope(store, source, homeAssistantScope))
|
|
102
107
|
.filter((source) => !strictCandidates || candidateSourceIds.has(source.id) || (candidateSourceIds.size === 0 && linkedSourceIds.has(source.id)))
|
|
103
108
|
.map((source) => {
|
|
104
109
|
const extraction = store.getExtractionBySourceId(source.id);
|
|
@@ -139,6 +144,7 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
|
|
|
139
144
|
});
|
|
140
145
|
const nodeItems = store.listNodes(10_000)
|
|
141
146
|
.filter((node) => belongsToAnswerSpace(node, spaceId) && node.status !== 'stale')
|
|
147
|
+
.filter((node) => nodeInHomeAssistantAnswerScope(node, homeAssistantScope))
|
|
142
148
|
.filter((node) => !strictCandidates
|
|
143
149
|
|| candidateNodeIds.has(node.id)
|
|
144
150
|
|| linkedObjectIds.has(node.id)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KnowledgeStore } from '../store.js';
|
|
2
|
+
import type { KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
|
|
3
|
+
export interface HomeAssistantAnswerScope {
|
|
4
|
+
readonly anchorNodeIds: ReadonlySet<string>;
|
|
5
|
+
readonly linkedSourceIds: ReadonlySet<string>;
|
|
6
|
+
readonly anchorText: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function inferHomeAssistantAnswerScope(store: KnowledgeStore, spaceId: string, query: string, subjectTokens: readonly string[]): HomeAssistantAnswerScope | null;
|
|
9
|
+
export declare function sourceInHomeAssistantAnswerScope(store: KnowledgeStore, source: KnowledgeSourceRecord, scope: HomeAssistantAnswerScope | null): boolean;
|
|
10
|
+
export declare function nodeInHomeAssistantAnswerScope(node: KnowledgeNodeRecord, scope: HomeAssistantAnswerScope | null): boolean;
|
|
11
|
+
//# sourceMappingURL=homeassistant-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homeassistant-scope.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/homeassistant-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAI9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,wBAAwB,GAAG,IAAI,CA6BjC;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAMT;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAKT"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getKnowledgeSpaceId, isHomeAssistantKnowledgeSpace, normalizeKnowledgeSpaceId } from '../spaces.js';
|
|
2
|
+
import { readRecord, readString, scoreSemanticText, sourceSemanticText } from './utils.js';
|
|
3
|
+
export function inferHomeAssistantAnswerScope(store, spaceId, query, subjectTokens) {
|
|
4
|
+
const normalized = normalizeKnowledgeSpaceId(spaceId);
|
|
5
|
+
if (normalized !== 'homeassistant' && !isHomeAssistantKnowledgeSpace(normalized))
|
|
6
|
+
return null;
|
|
7
|
+
if (subjectTokens.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
const singularObjectQuery = isSingularObjectQuery(query, subjectTokens);
|
|
10
|
+
const strongIdentityTokens = subjectTokens.filter(isStrongIdentityToken);
|
|
11
|
+
if (!singularObjectQuery && strongIdentityTokens.length === 0)
|
|
12
|
+
return null;
|
|
13
|
+
const linkedSourceCountByNode = linkedSourceCountsByAnchor(store);
|
|
14
|
+
const anchors = store.listNodes(10_000)
|
|
15
|
+
.filter((node) => node.status !== 'stale' && isHomeAssistantObjectNode(node))
|
|
16
|
+
.filter((node) => normalized === 'homeassistant' || normalizeKnowledgeSpaceId(getKnowledgeSpaceId(node)) === normalized)
|
|
17
|
+
.map((node) => ({
|
|
18
|
+
node,
|
|
19
|
+
score: scoreHomeAssistantObject(node, subjectTokens)
|
|
20
|
+
+ Math.min(24, (linkedSourceCountByNode.get(node.id) ?? 0) * 8),
|
|
21
|
+
}))
|
|
22
|
+
.filter((entry) => entry.score > 0)
|
|
23
|
+
.sort((left, right) => right.score - left.score || left.node.id.localeCompare(right.node.id));
|
|
24
|
+
const topScore = anchors[0]?.score ?? 0;
|
|
25
|
+
if (topScore <= 0)
|
|
26
|
+
return null;
|
|
27
|
+
const selectedAnchors = anchors
|
|
28
|
+
.filter((entry) => entry.score >= Math.max(1, topScore - 12))
|
|
29
|
+
.slice(0, singularObjectQuery ? 1 : 8);
|
|
30
|
+
const anchorNodeIds = new Set(selectedAnchors.map((entry) => entry.node.id));
|
|
31
|
+
const linkedSourceIds = sourceIdsLinkedToAnchors(store, anchorNodeIds);
|
|
32
|
+
const anchorText = selectedAnchors.map((entry) => homeAssistantObjectText(entry.node));
|
|
33
|
+
return { anchorNodeIds, linkedSourceIds, anchorText };
|
|
34
|
+
}
|
|
35
|
+
export function sourceInHomeAssistantAnswerScope(store, source, scope) {
|
|
36
|
+
if (!scope || scope.anchorNodeIds.size === 0)
|
|
37
|
+
return true;
|
|
38
|
+
if (scope.linkedSourceIds.has(source.id))
|
|
39
|
+
return true;
|
|
40
|
+
const extraction = store.getExtractionBySourceId(source.id);
|
|
41
|
+
const text = sourceSemanticText(source, extraction);
|
|
42
|
+
return scope.anchorText.some((anchor) => sourceMatchesAnchor(text, anchor));
|
|
43
|
+
}
|
|
44
|
+
export function nodeInHomeAssistantAnswerScope(node, scope) {
|
|
45
|
+
if (!scope || scope.anchorNodeIds.size === 0)
|
|
46
|
+
return true;
|
|
47
|
+
if (scope.anchorNodeIds.has(node.id))
|
|
48
|
+
return true;
|
|
49
|
+
const sourceId = readString(node.metadata.sourceId) ?? node.sourceId;
|
|
50
|
+
return Boolean(sourceId && scope.linkedSourceIds.has(sourceId));
|
|
51
|
+
}
|
|
52
|
+
function sourceIdsLinkedToAnchors(store, anchorNodeIds) {
|
|
53
|
+
const sourceIds = new Set();
|
|
54
|
+
if (anchorNodeIds.size === 0)
|
|
55
|
+
return sourceIds;
|
|
56
|
+
for (const edge of store.listEdges()) {
|
|
57
|
+
if (edge.fromKind === 'source' && edge.toKind === 'node' && anchorNodeIds.has(edge.toId))
|
|
58
|
+
sourceIds.add(edge.fromId);
|
|
59
|
+
if (edge.fromKind === 'node' && anchorNodeIds.has(edge.fromId) && edge.toKind === 'source')
|
|
60
|
+
sourceIds.add(edge.toId);
|
|
61
|
+
}
|
|
62
|
+
return sourceIds;
|
|
63
|
+
}
|
|
64
|
+
function linkedSourceCountsByAnchor(store) {
|
|
65
|
+
const counts = new Map();
|
|
66
|
+
for (const edge of store.listEdges()) {
|
|
67
|
+
if (edge.fromKind === 'source' && edge.toKind === 'node') {
|
|
68
|
+
counts.set(edge.toId, (counts.get(edge.toId) ?? 0) + 1);
|
|
69
|
+
}
|
|
70
|
+
else if (edge.fromKind === 'node' && edge.toKind === 'source') {
|
|
71
|
+
counts.set(edge.fromId, (counts.get(edge.fromId) ?? 0) + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return counts;
|
|
75
|
+
}
|
|
76
|
+
function isHomeAssistantObjectNode(node) {
|
|
77
|
+
return node.kind === 'ha_device'
|
|
78
|
+
|| node.kind === 'ha_entity'
|
|
79
|
+
|| node.kind === 'ha_area'
|
|
80
|
+
|| node.kind === 'ha_integration'
|
|
81
|
+
|| node.kind === 'ha_automation'
|
|
82
|
+
|| node.kind === 'ha_script'
|
|
83
|
+
|| node.kind === 'ha_scene';
|
|
84
|
+
}
|
|
85
|
+
function scoreHomeAssistantObject(node, tokens) {
|
|
86
|
+
const metadata = readRecord(node.metadata);
|
|
87
|
+
const homeAssistant = readRecord(metadata.homeAssistant);
|
|
88
|
+
const text = homeAssistantObjectText(node);
|
|
89
|
+
const baseScore = scoreSemanticText(text, tokens);
|
|
90
|
+
const domain = (readString(metadata.domain) ?? readString(homeAssistant.domain) ?? '').toLowerCase();
|
|
91
|
+
const platform = (readString(metadata.platform) ?? readString(homeAssistant.integrationDomain) ?? '').toLowerCase();
|
|
92
|
+
const tvQuery = tokens.some((token) => token === 'tv' || token === 'television');
|
|
93
|
+
if (tvQuery) {
|
|
94
|
+
const lower = text.toLowerCase();
|
|
95
|
+
if (node.kind === 'ha_device' && /\b(tv|television|webos|bravia|roku)\b/.test(lower))
|
|
96
|
+
return baseScore + 80;
|
|
97
|
+
if (node.kind === 'ha_entity' && (domain === 'media_player' || platform === 'webostv'))
|
|
98
|
+
return baseScore + 70;
|
|
99
|
+
if (node.kind === 'ha_integration' && (platform === 'webostv' || lower.includes('webos')))
|
|
100
|
+
return baseScore + 30;
|
|
101
|
+
if (node.kind === 'ha_automation' || domain === 'sensor' || domain === 'switch')
|
|
102
|
+
return Math.max(0, baseScore - 40);
|
|
103
|
+
}
|
|
104
|
+
return baseScore;
|
|
105
|
+
}
|
|
106
|
+
function homeAssistantObjectText(node) {
|
|
107
|
+
const homeAssistant = readRecord(node.metadata.homeAssistant);
|
|
108
|
+
return [
|
|
109
|
+
node.title,
|
|
110
|
+
node.summary,
|
|
111
|
+
node.aliases.join(' '),
|
|
112
|
+
readString(node.metadata.manufacturer),
|
|
113
|
+
readString(node.metadata.model),
|
|
114
|
+
readString(homeAssistant.objectKind),
|
|
115
|
+
readString(homeAssistant.objectId),
|
|
116
|
+
readString(homeAssistant.entityId),
|
|
117
|
+
readString(homeAssistant.domain),
|
|
118
|
+
readString(homeAssistant.deviceClass),
|
|
119
|
+
readString(homeAssistant.integrationDomain),
|
|
120
|
+
].filter(Boolean).join('\n');
|
|
121
|
+
}
|
|
122
|
+
function isSingularObjectQuery(query, tokens) {
|
|
123
|
+
const normalized = query.toLowerCase();
|
|
124
|
+
if (/\b(the|this|that|my)\s+(tv|television|device|sensor|switch|camera|printer|router|phone|object|thing)\b/.test(normalized)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (tokens.includes('tv') || tokens.includes('television')) {
|
|
128
|
+
return !tokens.includes('tvs') && !tokens.includes('televisions');
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
function isStrongIdentityToken(token) {
|
|
133
|
+
return token.length >= 4 && !GENERIC_ANCHOR_TOKENS.has(token);
|
|
134
|
+
}
|
|
135
|
+
function sourceMatchesAnchor(sourceText, anchorText) {
|
|
136
|
+
const tokens = anchorText
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.split(/[^a-z0-9_.:-]+/)
|
|
139
|
+
.filter((token) => token.length >= 4 && !GENERIC_ANCHOR_TOKENS.has(token));
|
|
140
|
+
if (tokens.length === 0)
|
|
141
|
+
return false;
|
|
142
|
+
return scoreSemanticText(sourceText, tokens) >= Math.min(24, tokens.length * 12);
|
|
143
|
+
}
|
|
144
|
+
const GENERIC_ANCHOR_TOKENS = new Set([
|
|
145
|
+
'audio',
|
|
146
|
+
'available',
|
|
147
|
+
'capabilities',
|
|
148
|
+
'capability',
|
|
149
|
+
'configuration',
|
|
150
|
+
'device',
|
|
151
|
+
'entity',
|
|
152
|
+
'feature',
|
|
153
|
+
'features',
|
|
154
|
+
'format',
|
|
155
|
+
'formats',
|
|
156
|
+
'gaming',
|
|
157
|
+
'have',
|
|
158
|
+
'hdmi',
|
|
159
|
+
'hdr',
|
|
160
|
+
'home',
|
|
161
|
+
'assistant',
|
|
162
|
+
'integration',
|
|
163
|
+
'network',
|
|
164
|
+
'port',
|
|
165
|
+
'ports',
|
|
166
|
+
'rate',
|
|
167
|
+
'refresh',
|
|
168
|
+
'sensor',
|
|
169
|
+
'smart',
|
|
170
|
+
'spec',
|
|
171
|
+
'specification',
|
|
172
|
+
'specifications',
|
|
173
|
+
'switch',
|
|
174
|
+
'support',
|
|
175
|
+
'supported',
|
|
176
|
+
'supports',
|
|
177
|
+
'television',
|
|
178
|
+
'tv',
|
|
179
|
+
]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '0.28.
|
|
3
|
+
let version = '0.28.9';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|