@pellux/goodvibes-sdk 0.27.2 → 0.27.4
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 +845 -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 +845 -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 +2 -0
- package/dist/_internal/daemon/context.d.ts +1 -0
- package/dist/_internal/daemon/context.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-route-types.d.ts +1 -0
- package/dist/_internal/daemon/knowledge-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-routes.d.ts +1 -1
- package/dist/_internal/daemon/knowledge-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-routes.js +18 -0
- package/dist/_internal/daemon/operator.d.ts +1 -1
- package/dist/_internal/daemon/operator.d.ts.map +1 -1
- package/dist/_internal/daemon/operator.js +2 -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 +10 -1
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.js +20 -1
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts +2 -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 +23 -0
- package/dist/_internal/platform/control-plane/routes/operator.d.ts +1 -1
- package/dist/_internal/platform/control-plane/routes/operator.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/routes/operator.js +2 -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 +7 -0
- package/dist/_internal/platform/daemon/http/router-route-contexts.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/http/router-route-contexts.js +1 -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/ask.d.ts +13 -0
- package/dist/_internal/platform/knowledge/home-graph/ask.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/home-graph/ask.js +65 -0
- 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/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 +24 -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 +4 -1
- package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/rendering.js +215 -4
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts +12 -2
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/service.js +78 -21
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts +31 -1
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/types.js +2 -0
- package/dist/_internal/platform/knowledge/index.d.ts +3 -1
- package/dist/_internal/platform/knowledge/index.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/index.js +1 -0
- package/dist/_internal/platform/knowledge/ingest-compile.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/ingest-compile.js +5 -0
- package/dist/_internal/platform/knowledge/ingest-context.d.ts +1 -0
- package/dist/_internal/platform/knowledge/ingest-context.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/map.js +15 -1
- 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/scheduling.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/scheduling.js +1 -0
- package/dist/_internal/platform/knowledge/semantic/answer.d.ts +9 -0
- package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/answer.js +407 -0
- package/dist/_internal/platform/knowledge/semantic/enrichment.d.ts +23 -0
- package/dist/_internal/platform/knowledge/semantic/enrichment.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/enrichment.js +491 -0
- package/dist/_internal/platform/knowledge/semantic/index.d.ts +5 -0
- package/dist/_internal/platform/knowledge/semantic/index.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/index.js +2 -0
- package/dist/_internal/platform/knowledge/semantic/llm.d.ts +4 -0
- package/dist/_internal/platform/knowledge/semantic/llm.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/llm.js +34 -0
- package/dist/_internal/platform/knowledge/semantic/service.d.ts +37 -0
- package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/service.js +55 -0
- package/dist/_internal/platform/knowledge/semantic/types.d.ts +107 -0
- package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/types.js +1 -0
- package/dist/_internal/platform/knowledge/semantic/utils.d.ts +39 -0
- package/dist/_internal/platform/knowledge/semantic/utils.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/utils.js +189 -0
- package/dist/_internal/platform/knowledge/service.d.ts +12 -0
- package/dist/_internal/platform/knowledge/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/service.js +14 -0
- package/dist/_internal/platform/knowledge/types.d.ts +2 -2
- package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
- package/dist/_internal/platform/runtime/services.d.ts.map +1 -1
- package/dist/_internal/platform/runtime/services.js +8 -2
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import JSZip from 'jszip';
|
|
|
2
2
|
import { extname } from 'node:path';
|
|
3
3
|
import { guessMimeType } from '../artifacts/types.js';
|
|
4
4
|
import { extractReadableHtml } from './html-readability.js';
|
|
5
|
+
import { extractPdf } from './pdf-extractor.js';
|
|
5
6
|
const MAX_STRUCTURE_SEARCH_TEXT_CHARS = 128 * 1024;
|
|
6
7
|
function decodeHtmlEntities(value) {
|
|
7
8
|
return value
|
|
@@ -306,122 +307,6 @@ function extractYaml(buffer) {
|
|
|
306
307
|
metadata: {},
|
|
307
308
|
};
|
|
308
309
|
}
|
|
309
|
-
async function extractPdf(buffer) {
|
|
310
|
-
const parsed = await extractPdfWithPdfJs(buffer);
|
|
311
|
-
if (parsed)
|
|
312
|
-
return parsed;
|
|
313
|
-
return extractPdfRawStreams(buffer);
|
|
314
|
-
}
|
|
315
|
-
async function extractPdfWithPdfJs(buffer) {
|
|
316
|
-
try {
|
|
317
|
-
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
318
|
-
const loadingTask = pdfjs.getDocument({
|
|
319
|
-
data: new Uint8Array(buffer),
|
|
320
|
-
useSystemFonts: true,
|
|
321
|
-
});
|
|
322
|
-
const document = await loadingTask.promise;
|
|
323
|
-
const pageCount = document.numPages;
|
|
324
|
-
const pageTexts = [];
|
|
325
|
-
for (let pageNumber = 1; pageNumber <= pageCount; pageNumber += 1) {
|
|
326
|
-
const page = await document.getPage(pageNumber);
|
|
327
|
-
const content = await page.getTextContent();
|
|
328
|
-
const lines = textContentItemsToLines(content.items);
|
|
329
|
-
if (lines.length > 0)
|
|
330
|
-
pageTexts.push(lines.join('\n'));
|
|
331
|
-
page.cleanup();
|
|
332
|
-
}
|
|
333
|
-
await document.destroy();
|
|
334
|
-
const text = cleanText(pageTexts.join('\n\n'));
|
|
335
|
-
if (!text)
|
|
336
|
-
return undefined;
|
|
337
|
-
const searchText = searchTextPayload(text);
|
|
338
|
-
return {
|
|
339
|
-
extractorId: 'pdfjs',
|
|
340
|
-
format: 'pdf',
|
|
341
|
-
title: firstNonEmptyLine(text) ?? 'PDF document',
|
|
342
|
-
summary: summarizeText(text) ?? 'PDF document.',
|
|
343
|
-
excerpt: excerptText(text),
|
|
344
|
-
sections: uniqueStrings(text.split(/\n+/), 24),
|
|
345
|
-
links: uniqueStrings(Array.from(text.matchAll(/\bhttps?:\/\/[^\s)]+/g), (match) => match[0]), 50),
|
|
346
|
-
estimatedTokens: estimateTokens(text),
|
|
347
|
-
structure: {
|
|
348
|
-
pageCount,
|
|
349
|
-
extractedTextChars: text.length,
|
|
350
|
-
...(searchText ? { searchText } : {}),
|
|
351
|
-
},
|
|
352
|
-
metadata: {
|
|
353
|
-
limitations: ['PDF text extraction does not perform OCR for scanned images.'],
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return undefined;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function textContentItemsToLines(items) {
|
|
362
|
-
const lines = [];
|
|
363
|
-
let current = '';
|
|
364
|
-
for (const item of items) {
|
|
365
|
-
const record = unknownRecord(item);
|
|
366
|
-
const text = typeof record.str === 'string' ? cleanText(record.str) : '';
|
|
367
|
-
if (text)
|
|
368
|
-
current = current ? `${current} ${text}` : text;
|
|
369
|
-
if (record.hasEOL === true && current) {
|
|
370
|
-
lines.push(current);
|
|
371
|
-
current = '';
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (current)
|
|
375
|
-
lines.push(current);
|
|
376
|
-
return lines;
|
|
377
|
-
}
|
|
378
|
-
function unknownRecord(value) {
|
|
379
|
-
return value && typeof value === 'object' ? value : {};
|
|
380
|
-
}
|
|
381
|
-
function extractPdfRawStreams(buffer) {
|
|
382
|
-
const body = buffer.toString('latin1');
|
|
383
|
-
const texts = [];
|
|
384
|
-
const streamRe = /stream\r?\n([\s\S]*?)\r?\nendstream/g;
|
|
385
|
-
let match;
|
|
386
|
-
while ((match = streamRe.exec(body)) !== null) {
|
|
387
|
-
const chunk = match[1];
|
|
388
|
-
const parenRe = /\(([^)\\]*(?:\\.[^)\\]*)*)\)/g;
|
|
389
|
-
let textMatch;
|
|
390
|
-
while ((textMatch = parenRe.exec(chunk)) !== null) {
|
|
391
|
-
const text = cleanText(textMatch[1]
|
|
392
|
-
.replace(/\\n/g, '\n')
|
|
393
|
-
.replace(/\\r/g, '\r')
|
|
394
|
-
.replace(/\\t/g, '\t')
|
|
395
|
-
.replace(/\\\\/g, '\\')
|
|
396
|
-
.replace(/\\\(/g, '(')
|
|
397
|
-
.replace(/\\\)/g, ')'));
|
|
398
|
-
if (text.length > 1)
|
|
399
|
-
texts.push(text);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const combined = uniqueStrings(texts, 64).join('\n');
|
|
403
|
-
const searchable = uniqueStrings(texts, 512).join('\n');
|
|
404
|
-
const searchText = searchTextPayload(searchable);
|
|
405
|
-
return {
|
|
406
|
-
extractorId: 'pdf',
|
|
407
|
-
format: 'pdf',
|
|
408
|
-
title: firstNonEmptyLine(combined) ?? 'PDF document',
|
|
409
|
-
summary: summarizeText(combined) ?? 'PDF extraction produced limited text; OCR is not used in-core.',
|
|
410
|
-
excerpt: excerptText(combined),
|
|
411
|
-
sections: uniqueStrings(combined.split(/\n+/), 8),
|
|
412
|
-
links: uniqueStrings(Array.from(combined.matchAll(/\bhttps?:\/\/[^\s)]+/g), (match) => match[0]), 50),
|
|
413
|
-
estimatedTokens: estimateTokens(combined),
|
|
414
|
-
structure: {
|
|
415
|
-
extractedStringCount: texts.length,
|
|
416
|
-
...(searchText ? { searchText } : {}),
|
|
417
|
-
},
|
|
418
|
-
metadata: {
|
|
419
|
-
limitations: texts.length === 0
|
|
420
|
-
? ['No readable text streams were found. Complex PDFs need OCR or a dedicated provider.']
|
|
421
|
-
: ['PDF extraction is best-effort and does not use OCR.'],
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
310
|
async function extractDocx(buffer) {
|
|
426
311
|
const zip = await JSZip.loadAsync(buffer);
|
|
427
312
|
const file = zip.file('word/document.xml');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KnowledgeSemanticService } from '../semantic/index.js';
|
|
2
|
+
import type { KnowledgeStore } from '../store.js';
|
|
3
|
+
import type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphSearchResult } from './types.js';
|
|
4
|
+
import type { HomeGraphSearchState } from './search.js';
|
|
5
|
+
export declare function answerHomeGraphQuery(input: {
|
|
6
|
+
readonly store: KnowledgeStore;
|
|
7
|
+
readonly semanticService?: KnowledgeSemanticService;
|
|
8
|
+
readonly spaceId: string;
|
|
9
|
+
readonly query: HomeGraphAskInput;
|
|
10
|
+
readonly state: HomeGraphSearchState;
|
|
11
|
+
readonly results: readonly HomeGraphSearchResult[];
|
|
12
|
+
}): Promise<HomeGraphAskResult>;
|
|
13
|
+
//# sourceMappingURL=ask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/ask.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC/F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,wBAAwB,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACpD,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoD9B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { collectLinkedObjects, renderAskAnswer } from './state.js';
|
|
2
|
+
export async function answerHomeGraphQuery(input) {
|
|
3
|
+
const sources = input.results.flatMap((result) => result.source ? [result.source] : []);
|
|
4
|
+
const linkedObjects = collectLinkedObjects(input.results, input.state);
|
|
5
|
+
if (input.semanticService) {
|
|
6
|
+
await input.semanticService.enrichSources(uniqueSources(sources), {
|
|
7
|
+
knowledgeSpaceId: input.spaceId,
|
|
8
|
+
limit: Math.min(3, Math.max(1, sources.length)),
|
|
9
|
+
});
|
|
10
|
+
const answer = await input.semanticService.answer({
|
|
11
|
+
query: input.query.query,
|
|
12
|
+
knowledgeSpaceId: input.spaceId,
|
|
13
|
+
mode: input.query.mode ?? 'standard',
|
|
14
|
+
limit: input.query.limit ?? 8,
|
|
15
|
+
includeSources: input.query.includeSources,
|
|
16
|
+
includeConfidence: input.query.includeConfidence,
|
|
17
|
+
includeLinkedObjects: input.query.includeLinkedObjects,
|
|
18
|
+
candidateSourceIds: sources.map((source) => source.id),
|
|
19
|
+
candidateNodeIds: input.results.flatMap((result) => result.node ? [result.node.id] : []),
|
|
20
|
+
linkedObjects,
|
|
21
|
+
noMatchMessage: `No Home Graph knowledge matched "${input.query.query}".`,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
spaceId: input.spaceId,
|
|
26
|
+
query: input.query.query,
|
|
27
|
+
answer: {
|
|
28
|
+
text: answer.answer.text,
|
|
29
|
+
mode: answer.answer.mode,
|
|
30
|
+
confidence: answer.answer.confidence,
|
|
31
|
+
sources: answer.answer.sources,
|
|
32
|
+
linkedObjects: answer.answer.linkedObjects,
|
|
33
|
+
facts: answer.answer.facts,
|
|
34
|
+
gaps: answer.answer.gaps,
|
|
35
|
+
synthesized: answer.answer.synthesized,
|
|
36
|
+
},
|
|
37
|
+
results: input.results,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const confidence = Math.min(100, Math.max(10, input.results[0]?.score ?? 10));
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
spaceId: input.spaceId,
|
|
44
|
+
query: input.query.query,
|
|
45
|
+
answer: {
|
|
46
|
+
text: renderAskAnswer(input.query.query, input.results, input.query.mode ?? 'standard'),
|
|
47
|
+
mode: input.query.mode ?? 'standard',
|
|
48
|
+
confidence,
|
|
49
|
+
sources: input.query.includeSources === false ? [] : sources,
|
|
50
|
+
linkedObjects: input.query.includeLinkedObjects === false ? [] : linkedObjects,
|
|
51
|
+
},
|
|
52
|
+
results: input.results,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function uniqueSources(sources) {
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const source of sources) {
|
|
59
|
+
if (seen.has(source.id))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(source.id);
|
|
62
|
+
out.push(source);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { KnowledgeStore } from '../store.js';
|
|
2
|
+
import type { KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
|
|
3
|
+
import type { HomeGraphState } from './state.js';
|
|
4
|
+
export interface HomeGraphAutoLinkResult {
|
|
5
|
+
readonly edge: KnowledgeEdgeRecord;
|
|
6
|
+
readonly node: KnowledgeNodeRecord;
|
|
7
|
+
readonly relation: string;
|
|
8
|
+
readonly score: number;
|
|
9
|
+
readonly reasons: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function autoLinkHomeGraphSource(input: {
|
|
12
|
+
readonly store: KnowledgeStore;
|
|
13
|
+
readonly spaceId: string;
|
|
14
|
+
readonly installationId: string;
|
|
15
|
+
readonly source: KnowledgeSourceRecord;
|
|
16
|
+
readonly extraction?: KnowledgeExtractionRecord;
|
|
17
|
+
readonly state: HomeGraphState;
|
|
18
|
+
}): Promise<HomeGraphAutoLinkResult | undefined>;
|
|
19
|
+
export declare function autoLinkHomeGraphSources(input: {
|
|
20
|
+
readonly store: KnowledgeStore;
|
|
21
|
+
readonly spaceId: string;
|
|
22
|
+
readonly installationId: string;
|
|
23
|
+
readonly sources: readonly KnowledgeSourceRecord[];
|
|
24
|
+
readonly extractionBySourceId: ReadonlyMap<string, KnowledgeExtractionRecord>;
|
|
25
|
+
readonly state: HomeGraphState;
|
|
26
|
+
}): Promise<readonly HomeGraphAutoLinkResult[]>;
|
|
27
|
+
//# sourceMappingURL=auto-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-link.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/auto-link.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;AAQrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOjD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,yBAAyB,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA2B/C;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;IAC9E,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC,CAiB9C"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { buildHomeGraphMetadata, edgeIsActive, isGeneratedPageSource, readRecord, uniqueStrings, } from './helpers.js';
|
|
2
|
+
const MIN_AUTO_LINK_SCORE = 90;
|
|
3
|
+
const MIN_SOURCE_TEXT_CHARS = 16;
|
|
4
|
+
const MAX_TEXT_FIELD_CHARS = 32_768;
|
|
5
|
+
const GENERIC_TOKENS = new Set(['device', 'home', 'assistant', 'smart', 'manual', 'owner', 'guide', 'user', 'the']);
|
|
6
|
+
export async function autoLinkHomeGraphSource(input) {
|
|
7
|
+
if (isGeneratedPageSource(input.source) || shouldSkipAutoLink(input.source))
|
|
8
|
+
return undefined;
|
|
9
|
+
if (hasActiveSourceLink(input.source.id, input.state.edges))
|
|
10
|
+
return undefined;
|
|
11
|
+
const candidates = scoreCandidates(input.source, input.extraction, input.state);
|
|
12
|
+
const best = candidates[0];
|
|
13
|
+
if (!best || best.score < MIN_AUTO_LINK_SCORE)
|
|
14
|
+
return undefined;
|
|
15
|
+
const next = candidates[1];
|
|
16
|
+
if (next && best.score - next.score < 20 && !best.reasons.some((reason) => reason.startsWith('exact-model:'))) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const relation = inferRelation(input.source);
|
|
20
|
+
const edge = await input.store.upsertEdge({
|
|
21
|
+
fromKind: 'source',
|
|
22
|
+
fromId: input.source.id,
|
|
23
|
+
toKind: 'node',
|
|
24
|
+
toId: best.node.id,
|
|
25
|
+
relation,
|
|
26
|
+
weight: Math.min(5, Math.max(1, Math.round(best.score / 60))),
|
|
27
|
+
metadata: buildHomeGraphMetadata(input.spaceId, input.installationId, {
|
|
28
|
+
linkStatus: 'active',
|
|
29
|
+
linkMethod: 'homegraph-auto-link',
|
|
30
|
+
autoLinkedAt: Date.now(),
|
|
31
|
+
autoLinkScore: best.score,
|
|
32
|
+
autoLinkReasons: best.reasons,
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
return { edge, node: best.node, relation, score: best.score, reasons: best.reasons };
|
|
36
|
+
}
|
|
37
|
+
export async function autoLinkHomeGraphSources(input) {
|
|
38
|
+
const linked = [];
|
|
39
|
+
for (const source of input.sources) {
|
|
40
|
+
const result = await autoLinkHomeGraphSource({
|
|
41
|
+
store: input.store,
|
|
42
|
+
spaceId: input.spaceId,
|
|
43
|
+
installationId: input.installationId,
|
|
44
|
+
source,
|
|
45
|
+
extraction: input.extractionBySourceId.get(source.id),
|
|
46
|
+
state: {
|
|
47
|
+
...input.state,
|
|
48
|
+
edges: [...input.state.edges, ...linked.map((entry) => entry.edge)],
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
if (result)
|
|
52
|
+
linked.push(result);
|
|
53
|
+
}
|
|
54
|
+
return linked;
|
|
55
|
+
}
|
|
56
|
+
function scoreCandidates(source, extraction, state) {
|
|
57
|
+
const sourceText = sourceEvidenceText(source, extraction);
|
|
58
|
+
if (sourceText.length < MIN_SOURCE_TEXT_CHARS)
|
|
59
|
+
return [];
|
|
60
|
+
const lower = sourceText.toLowerCase();
|
|
61
|
+
const sourceTokens = new Set(tokenize(sourceText));
|
|
62
|
+
return state.nodes
|
|
63
|
+
.filter((node) => isAutoLinkCandidateNode(node))
|
|
64
|
+
.map((node) => {
|
|
65
|
+
const reasons = [];
|
|
66
|
+
let score = 0;
|
|
67
|
+
const identity = nodeIdentity(node, state);
|
|
68
|
+
for (const model of identity.models) {
|
|
69
|
+
if (model.length >= 4 && includesIdentity(lower, model)) {
|
|
70
|
+
score += model.length >= 8 ? 180 : 120;
|
|
71
|
+
reasons.push(`exact-model:${model}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const entityId of identity.entityIds) {
|
|
75
|
+
if (includesIdentity(lower, entityId)) {
|
|
76
|
+
score += 140;
|
|
77
|
+
reasons.push(`entity-id:${entityId}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const deviceId of identity.deviceIds) {
|
|
81
|
+
if (includesIdentity(lower, deviceId)) {
|
|
82
|
+
score += 120;
|
|
83
|
+
reasons.push(`device-id:${deviceId}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const titleTokens = tokenize(node.title).filter((token) => !isGenericToken(token));
|
|
87
|
+
const overlap = titleTokens.filter((token) => sourceTokens.has(token));
|
|
88
|
+
if (overlap.length > 0) {
|
|
89
|
+
score += overlap.reduce((sum, token) => sum + (token.length <= 3 ? 12 : 18), 0);
|
|
90
|
+
reasons.push(`title:${overlap.slice(0, 5).join(',')}`);
|
|
91
|
+
}
|
|
92
|
+
const manufacturerMatches = identity.manufacturers.filter((manufacturer) => includesIdentity(lower, manufacturer));
|
|
93
|
+
if (manufacturerMatches.length > 0) {
|
|
94
|
+
score += 28;
|
|
95
|
+
reasons.push(`manufacturer:${manufacturerMatches[0]}`);
|
|
96
|
+
}
|
|
97
|
+
const relatedEntityMatches = identity.relatedEntityTokens.filter((token) => sourceTokens.has(token));
|
|
98
|
+
if (relatedEntityMatches.length > 0) {
|
|
99
|
+
score += Math.min(50, relatedEntityMatches.length * 10);
|
|
100
|
+
reasons.push(`related-entity:${relatedEntityMatches.slice(0, 5).join(',')}`);
|
|
101
|
+
}
|
|
102
|
+
if (node.kind === 'ha_device' && isManualLikeSource(source))
|
|
103
|
+
score += 16;
|
|
104
|
+
if (node.kind === 'ha_integration' && isIntegrationDocumentationSource(source))
|
|
105
|
+
score += 40;
|
|
106
|
+
if (node.kind === 'ha_integration' && isManualLikeSource(source))
|
|
107
|
+
score -= 45;
|
|
108
|
+
return { node, score, reasons };
|
|
109
|
+
})
|
|
110
|
+
.filter((entry) => entry.score > 0)
|
|
111
|
+
.sort((left, right) => right.score - left.score || left.node.id.localeCompare(right.node.id));
|
|
112
|
+
}
|
|
113
|
+
function shouldSkipAutoLink(source) {
|
|
114
|
+
const kind = typeof source.metadata.homeGraphSourceKind === 'string' ? source.metadata.homeGraphSourceKind : '';
|
|
115
|
+
return kind === 'snapshot' || kind === 'generated-page';
|
|
116
|
+
}
|
|
117
|
+
function hasActiveSourceLink(sourceId, edges) {
|
|
118
|
+
return edges.some((edge) => edgeIsActive(edge) && ((edge.fromKind === 'source' && edge.fromId === sourceId && edge.toKind === 'node')
|
|
119
|
+
|| (edge.fromKind === 'node' && edge.toKind === 'source' && edge.toId === sourceId)));
|
|
120
|
+
}
|
|
121
|
+
function isAutoLinkCandidateNode(node) {
|
|
122
|
+
return node.kind === 'ha_device'
|
|
123
|
+
|| node.kind === 'ha_entity'
|
|
124
|
+
|| node.kind === 'ha_integration'
|
|
125
|
+
|| node.kind === 'ha_area'
|
|
126
|
+
|| node.kind === 'ha_room';
|
|
127
|
+
}
|
|
128
|
+
function sourceEvidenceText(source, extraction) {
|
|
129
|
+
const structure = readRecord(extraction?.structure);
|
|
130
|
+
return uniqueStrings([
|
|
131
|
+
source.title,
|
|
132
|
+
source.summary,
|
|
133
|
+
source.description,
|
|
134
|
+
source.sourceUri,
|
|
135
|
+
source.canonicalUri,
|
|
136
|
+
source.tags.join(' '),
|
|
137
|
+
extraction?.title,
|
|
138
|
+
extraction?.summary,
|
|
139
|
+
extraction?.excerpt,
|
|
140
|
+
...(extraction?.sections ?? []),
|
|
141
|
+
readString(structure.searchText),
|
|
142
|
+
]).join('\n').slice(0, MAX_TEXT_FIELD_CHARS);
|
|
143
|
+
}
|
|
144
|
+
function nodeIdentity(node, state) {
|
|
145
|
+
const homeAssistant = readRecord(node.metadata.homeAssistant);
|
|
146
|
+
const relatedEntities = node.kind === 'ha_device'
|
|
147
|
+
? relatedEntityNodes(node.id, state)
|
|
148
|
+
: [];
|
|
149
|
+
return {
|
|
150
|
+
models: uniqueStrings([
|
|
151
|
+
readString(node.metadata.model),
|
|
152
|
+
readString(node.metadata.modelId),
|
|
153
|
+
readString(node.metadata.model_id),
|
|
154
|
+
]),
|
|
155
|
+
manufacturers: uniqueStrings([
|
|
156
|
+
readString(node.metadata.manufacturer),
|
|
157
|
+
readString(node.metadata.vendor),
|
|
158
|
+
]),
|
|
159
|
+
entityIds: uniqueStrings([
|
|
160
|
+
readString(homeAssistant.entityId),
|
|
161
|
+
...relatedEntities.map((entity) => readString(readRecord(entity.metadata.homeAssistant).entityId)),
|
|
162
|
+
]),
|
|
163
|
+
deviceIds: uniqueStrings([
|
|
164
|
+
readString(homeAssistant.deviceId),
|
|
165
|
+
node.kind === 'ha_device' ? readString(homeAssistant.objectId) : undefined,
|
|
166
|
+
]),
|
|
167
|
+
relatedEntityTokens: uniqueStrings(relatedEntities.flatMap((entity) => [
|
|
168
|
+
...tokenize(entity.title),
|
|
169
|
+
...tokenize(readString(readRecord(entity.metadata.homeAssistant).entityId) ?? ''),
|
|
170
|
+
readString(entity.metadata.domain),
|
|
171
|
+
readString(entity.metadata.platform),
|
|
172
|
+
])),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function relatedEntityNodes(deviceNodeId, state) {
|
|
176
|
+
const entityIds = new Set(state.edges.filter((edge) => (edgeIsActive(edge)
|
|
177
|
+
&& edge.fromKind === 'node'
|
|
178
|
+
&& edge.toKind === 'node'
|
|
179
|
+
&& edge.toId === deviceNodeId
|
|
180
|
+
&& edge.relation === 'belongs_to_device')).map((edge) => edge.fromId));
|
|
181
|
+
return state.nodes.filter((node) => entityIds.has(node.id) && node.kind === 'ha_entity');
|
|
182
|
+
}
|
|
183
|
+
function inferRelation(source) {
|
|
184
|
+
const tags = source.tags.map((tag) => tag.toLowerCase());
|
|
185
|
+
const text = [source.sourceType, source.title, source.sourceUri, ...tags].join(' ').toLowerCase();
|
|
186
|
+
if (text.includes('receipt'))
|
|
187
|
+
return 'has_receipt';
|
|
188
|
+
if (text.includes('warranty'))
|
|
189
|
+
return 'has_warranty';
|
|
190
|
+
if (isManualLikeSource(source))
|
|
191
|
+
return 'has_manual';
|
|
192
|
+
return 'source_for';
|
|
193
|
+
}
|
|
194
|
+
function isManualLikeSource(source) {
|
|
195
|
+
const tags = source.tags.map((tag) => tag.toLowerCase());
|
|
196
|
+
const text = [source.sourceType, source.title, source.sourceUri, ...tags].join(' ').toLowerCase();
|
|
197
|
+
return source.sourceType === 'manual'
|
|
198
|
+
|| tags.includes('manual')
|
|
199
|
+
|| tags.includes('artifact')
|
|
200
|
+
|| tags.includes('document')
|
|
201
|
+
|| /\bmanual\b|\.pdf\b|owner.?s guide|user guide/.test(text);
|
|
202
|
+
}
|
|
203
|
+
function isIntegrationDocumentationSource(source) {
|
|
204
|
+
const tags = source.tags.map((tag) => tag.toLowerCase());
|
|
205
|
+
const kind = typeof source.metadata.homeGraphSourceKind === 'string'
|
|
206
|
+
? source.metadata.homeGraphSourceKind.toLowerCase()
|
|
207
|
+
: '';
|
|
208
|
+
return tags.includes('integration') || tags.includes('documentation') || kind === 'documentation-candidate';
|
|
209
|
+
}
|
|
210
|
+
function tokenize(value) {
|
|
211
|
+
return uniqueStrings(value.toLowerCase().split(/[^a-z0-9_.:-]+/).filter((token) => token.length >= 2));
|
|
212
|
+
}
|
|
213
|
+
function isGenericToken(value) {
|
|
214
|
+
return GENERIC_TOKENS.has(value);
|
|
215
|
+
}
|
|
216
|
+
function includesIdentity(haystack, identity) {
|
|
217
|
+
const normalized = identity.trim().toLowerCase();
|
|
218
|
+
if (!normalized)
|
|
219
|
+
return false;
|
|
220
|
+
if (/^[a-z0-9_.:-]+$/.test(normalized)) {
|
|
221
|
+
return new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(normalized)}(?:$|[^a-z0-9])`).test(haystack);
|
|
222
|
+
}
|
|
223
|
+
return haystack.includes(normalized);
|
|
224
|
+
}
|
|
225
|
+
function readString(value) {
|
|
226
|
+
if (typeof value === 'string') {
|
|
227
|
+
const trimmed = value.trim();
|
|
228
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
229
|
+
}
|
|
230
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
231
|
+
return String(value);
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
function escapeRegExp(value) {
|
|
235
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
236
|
+
}
|
|
@@ -10,6 +10,7 @@ interface HomeGraphPageContext {
|
|
|
10
10
|
export declare function generateAutomaticHomeGraphPages(context: HomeGraphPageContext & {
|
|
11
11
|
readonly input: HomeGraphSnapshotInput;
|
|
12
12
|
}): Promise<HomeGraphGeneratedPagesSummary>;
|
|
13
|
+
export declare function refreshAutomaticHomeGraphPages(context: HomeGraphPageContext): Promise<HomeGraphGeneratedPagesSummary>;
|
|
13
14
|
export declare function refreshHomeGraphDevicePassport(context: HomeGraphPageContext & {
|
|
14
15
|
readonly input: HomeGraphProjectionInput;
|
|
15
16
|
}): Promise<HomeGraphDevicePassportResult & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-pages.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/generated-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8BlD,OAAO,KAAK,EACV,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzE,OAAO,CAAC,8BAA8B,CAAC,
|
|
1
|
+
{"version":3,"file":"generated-pages.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/generated-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8BlD,OAAO,KAAK,EACV,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzE,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AA2ED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,6BAA6B,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CA2EhF;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAsC5E;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB5E"}
|
|
@@ -3,13 +3,19 @@ import { HOME_GRAPH_CONNECTOR_ID, buildHomeGraphMetadata, edgeIsActive, homeGrap
|
|
|
3
3
|
import { findHomeAssistantNode, missingDevicePassportFields, readHomeGraphState, renderHomeGraphState, safeHomeGraphFilename, sourcesLinkedToNode, } from './state.js';
|
|
4
4
|
import { renderDevicePassportPage, renderPacketPage, renderRoomPage, } from './rendering.js';
|
|
5
5
|
export async function generateAutomaticHomeGraphPages(context) {
|
|
6
|
-
|
|
6
|
+
return generateHomeGraphPagesForCurrentState(context, context.input.pageAutomation ?? {});
|
|
7
|
+
}
|
|
8
|
+
export async function refreshAutomaticHomeGraphPages(context) {
|
|
9
|
+
return generateHomeGraphPagesForCurrentState(context, {});
|
|
10
|
+
}
|
|
11
|
+
async function generateHomeGraphPagesForCurrentState(context, options) {
|
|
12
|
+
const effectiveOptions = options ?? {};
|
|
7
13
|
const summary = createGeneratedPagesSummary();
|
|
8
|
-
if (
|
|
14
|
+
if (effectiveOptions.enabled === false)
|
|
9
15
|
return summary;
|
|
10
16
|
const state = readHomeGraphState(context.store, context.spaceId);
|
|
11
|
-
if (
|
|
12
|
-
const devices = limitRecords(state.nodes.filter((node) => node.kind === 'ha_device' && node.status !== 'stale').sort(compareByTitle),
|
|
17
|
+
if (effectiveOptions.devicePassports !== false) {
|
|
18
|
+
const devices = limitRecords(state.nodes.filter((node) => node.kind === 'ha_device' && node.status !== 'stale').sort(compareByTitle), effectiveOptions.maxDevicePassports);
|
|
13
19
|
for (const device of devices) {
|
|
14
20
|
const deviceId = readHomeAssistantObjectId(device, 'objectId', 'deviceId') ?? device.id;
|
|
15
21
|
try {
|
|
@@ -36,10 +42,10 @@ export async function generateAutomaticHomeGraphPages(context) {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
|
-
if (
|
|
45
|
+
if (effectiveOptions.roomPages !== false) {
|
|
40
46
|
const rooms = limitRecords(state.nodes
|
|
41
47
|
.filter((node) => (node.kind === 'ha_area' || node.kind === 'ha_room') && node.status !== 'stale')
|
|
42
|
-
.sort(compareByTitle),
|
|
48
|
+
.sort(compareByTitle), effectiveOptions.maxRoomPages);
|
|
43
49
|
for (const room of rooms) {
|
|
44
50
|
const areaId = readHomeAssistantObjectId(room, 'objectId', 'areaId') ?? room.id;
|
|
45
51
|
try {
|
|
@@ -84,6 +90,7 @@ export async function refreshHomeGraphDevicePassport(context) {
|
|
|
84
90
|
&& edge.toId === device.id
|
|
85
91
|
&& edge.relation === 'belongs_to_device'))));
|
|
86
92
|
const sources = sourcesLinkedToNode(device.id, state).filter((source) => !isGeneratedPageSource(source));
|
|
93
|
+
const semanticFacts = semanticFactsLinkedToSources(sources, state.nodes, state.edges);
|
|
87
94
|
const issues = state.issues.filter((issue) => issue.nodeId === device.id);
|
|
88
95
|
const missingFields = missingDevicePassportFields(device, sources);
|
|
89
96
|
const passport = await store.upsertNode({
|
|
@@ -109,7 +116,7 @@ export async function refreshHomeGraphDevicePassport(context) {
|
|
|
109
116
|
relation: 'source_for',
|
|
110
117
|
metadata: buildHomeGraphMetadata(spaceId, installationId),
|
|
111
118
|
});
|
|
112
|
-
const markdown = renderDevicePassportPage({ spaceId, device, entities, sources, issues, missingFields });
|
|
119
|
+
const markdown = renderDevicePassportPage({ spaceId, device, entities, sources, extractions: state.extractions, issues, missingFields, semanticFacts });
|
|
113
120
|
const generated = await materializeGeneratedMarkdown({
|
|
114
121
|
store,
|
|
115
122
|
artifactStore,
|
|
@@ -147,7 +154,7 @@ export async function generateHomeGraphRoomPage(context) {
|
|
|
147
154
|
const state = readHomeGraphState(store, spaceId);
|
|
148
155
|
const areaId = input.areaId ?? input.roomId;
|
|
149
156
|
const title = input.title ?? resolveRoomTitle(state.nodes, areaId) ?? 'Home Graph Room';
|
|
150
|
-
const markdown = renderRoomPage({ ...state, title }, areaId);
|
|
157
|
+
const markdown = renderRoomPage({ ...state, title, extractions: state.extractions }, areaId);
|
|
151
158
|
const filename = `${safeHomeGraphFilename(title)}.md`;
|
|
152
159
|
const targetNode = areaId
|
|
153
160
|
? findHomeAssistantNode(state.nodes, 'ha_area', areaId) ?? findHomeAssistantNode(state.nodes, 'ha_room', areaId)
|
|
@@ -272,6 +279,15 @@ function createGeneratedPagesSummary() {
|
|
|
272
279
|
function compareByTitle(left, right) {
|
|
273
280
|
return left.title.localeCompare(right.title) || left.id.localeCompare(right.id);
|
|
274
281
|
}
|
|
282
|
+
function semanticFactsLinkedToSources(sources, nodes, edges) {
|
|
283
|
+
const sourceIds = new Set(sources.map((source) => source.id));
|
|
284
|
+
const factIds = new Set(edges.filter((edge) => (edgeIsActive(edge)
|
|
285
|
+
&& edge.fromKind === 'source'
|
|
286
|
+
&& sourceIds.has(edge.fromId)
|
|
287
|
+
&& edge.toKind === 'node'
|
|
288
|
+
&& edge.relation === 'supports_fact')).map((edge) => edge.toId));
|
|
289
|
+
return nodes.filter((node) => factIds.has(node.id));
|
|
290
|
+
}
|
|
275
291
|
function limitRecords(records, limit) {
|
|
276
292
|
if (typeof limit !== 'number')
|
|
277
293
|
return records;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { HomeGraphService } from './service.js';
|
|
2
2
|
export { HOME_GRAPH_NODE_KINDS, HOME_GRAPH_RELATIONS, } from './types.js';
|
|
3
|
-
export type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphGeneratedPagesSummary, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphKnowledgeTarget, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphMapEdge, HomeGraphMapHaFilterInput, HomeGraphMapInput, HomeGraphMapNode, HomeGraphMapResult, HomeGraphNodeKind, HomeGraphObjectInput, HomeGraphObjectKind, HomeGraphPageAutomationOptions, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphReindexResult, HomeGraphRelation, HomeGraphReviewInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult, } from './types.js';
|
|
3
|
+
export type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphGeneratedPagesSummary, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphKnowledgeTarget, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphMapEdge, HomeGraphMapHaFilterInput, HomeGraphMapInput, HomeGraphMapNode, HomeGraphMapResult, HomeGraphNodeKind, HomeGraphObjectInput, HomeGraphObjectKind, HomeGraphPageAutomationOptions, HomeGraphPageListResult, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphReindexResult, HomeGraphRelation, HomeGraphReviewInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult, } from './types.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,6BAA6B,EAC7B,eAAe,EACf,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,6BAA6B,EAC7B,eAAe,EACf,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,yBAAyB,EACzB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,8BAA8B,EAC9B,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ArtifactStore } from '../../artifacts/index.js';
|
|
2
|
+
import type { KnowledgeSourceRecord } from '../types.js';
|
|
3
|
+
import type { HomeGraphPageListResult } from './types.js';
|
|
4
|
+
export declare function listHomeGraphPages(input: {
|
|
5
|
+
readonly artifactStore: ArtifactStore;
|
|
6
|
+
readonly spaceId: string;
|
|
7
|
+
readonly sources: readonly KnowledgeSourceRecord[];
|
|
8
|
+
readonly limit: number;
|
|
9
|
+
readonly includeMarkdown: boolean;
|
|
10
|
+
}): Promise<HomeGraphPageListResult>;
|
|
11
|
+
//# sourceMappingURL=pages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA0BnC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isGeneratedPageSource } from './helpers.js';
|
|
2
|
+
export async function listHomeGraphPages(input) {
|
|
3
|
+
const pages = [];
|
|
4
|
+
const sources = input.sources
|
|
5
|
+
.filter(isGeneratedPageSource)
|
|
6
|
+
.sort(compareGeneratedPages)
|
|
7
|
+
.slice(0, input.limit);
|
|
8
|
+
for (const source of sources) {
|
|
9
|
+
const artifact = typeof source.artifactId === 'string' ? input.artifactStore.get(source.artifactId) : undefined;
|
|
10
|
+
const markdown = input.includeMarkdown && artifact
|
|
11
|
+
? await readMarkdown(input.artifactStore, artifact.id)
|
|
12
|
+
: undefined;
|
|
13
|
+
pages.push({
|
|
14
|
+
source,
|
|
15
|
+
...(artifact ? {
|
|
16
|
+
artifact: {
|
|
17
|
+
id: artifact.id,
|
|
18
|
+
mimeType: artifact.mimeType,
|
|
19
|
+
filename: artifact.filename,
|
|
20
|
+
createdAt: artifact.createdAt,
|
|
21
|
+
metadata: artifact.metadata,
|
|
22
|
+
},
|
|
23
|
+
} : {}),
|
|
24
|
+
...(markdown ? { markdown } : {}),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return { ok: true, spaceId: input.spaceId, pages };
|
|
28
|
+
}
|
|
29
|
+
function compareGeneratedPages(left, right) {
|
|
30
|
+
const leftKind = typeof left.metadata.projectionKind === 'string' ? left.metadata.projectionKind : '';
|
|
31
|
+
const rightKind = typeof right.metadata.projectionKind === 'string' ? right.metadata.projectionKind : '';
|
|
32
|
+
return leftKind.localeCompare(rightKind)
|
|
33
|
+
|| (left.title ?? left.id).localeCompare(right.title ?? right.id)
|
|
34
|
+
|| left.id.localeCompare(right.id);
|
|
35
|
+
}
|
|
36
|
+
async function readMarkdown(artifactStore, artifactId) {
|
|
37
|
+
try {
|
|
38
|
+
const { buffer } = await artifactStore.readContent(artifactId);
|
|
39
|
+
return buffer.toString('utf-8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|