@pellux/goodvibes-sdk 0.28.18 → 0.28.20

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.
Files changed (35) hide show
  1. package/dist/_internal/contracts/artifacts/operator-contract.json +598 -1
  2. package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
  3. package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
  4. package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
  5. package/dist/_internal/contracts/generated/operator-contract.js +598 -1
  6. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
  7. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +6 -0
  8. package/dist/_internal/platform/knowledge/home-graph/reindex.d.ts.map +1 -1
  9. package/dist/_internal/platform/knowledge/home-graph/reindex.js +4 -0
  10. package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts.map +1 -1
  11. package/dist/_internal/platform/knowledge/home-graph/rendering.js +53 -8
  12. package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts.map +1 -1
  13. package/dist/_internal/platform/knowledge/semantic/answer-fallback.js +133 -1
  14. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.d.ts +1 -0
  15. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.d.ts.map +1 -1
  16. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.js +2 -2
  17. package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
  18. package/dist/_internal/platform/knowledge/semantic/answer.js +213 -37
  19. package/dist/_internal/platform/knowledge/semantic/enrichment.d.ts.map +1 -1
  20. package/dist/_internal/platform/knowledge/semantic/enrichment.js +36 -1
  21. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts.map +1 -1
  22. package/dist/_internal/platform/knowledge/semantic/fact-quality.js +16 -2
  23. package/dist/_internal/platform/knowledge/semantic/repair-profile.d.ts.map +1 -1
  24. package/dist/_internal/platform/knowledge/semantic/repair-profile.js +20 -12
  25. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts.map +1 -1
  26. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.js +82 -13
  27. package/dist/_internal/platform/knowledge/semantic/service.d.ts +1 -0
  28. package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
  29. package/dist/_internal/platform/knowledge/semantic/service.js +26 -2
  30. package/dist/_internal/platform/knowledge/semantic/types.d.ts +1 -0
  31. package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -1
  32. package/dist/_internal/platform/knowledge/types.d.ts +4 -0
  33. package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
  34. package/dist/_internal/platform/version.js +1 -1
  35. package/package.json +1 -1
@@ -6,7 +6,7 @@ import { concreteAnswerGapSpaceId } from './answer-space.js';
6
6
  import { answerNeedsFeatureGap, cleanSynthesizedAnswer } from './answer-quality.js';
7
7
  import { clampTimeoutMs, withTimeoutOrNull } from './timeouts.js';
8
8
  import { renderFallbackAnswer } from './answer-fallback.js';
9
- import { rankAnswerSources } from './answer-source-ranking.js';
9
+ import { rankAnswerSources, sourceAuthorityBoostForAnswer } from './answer-source-ranking.js';
10
10
  import { canonicalRepairSubjectNodes } from './repair-subjects.js';
11
11
  const GENERIC_ANSWER_INTENT_TOKENS = new Set([
12
12
  'capabilities',
@@ -39,35 +39,38 @@ export async function answerKnowledgeQuery(context, input) {
39
39
  const spaceId = normalizeKnowledgeSpaceId(input.knowledgeSpaceId);
40
40
  const mode = input.mode ?? 'standard';
41
41
  const limit = Math.max(1, input.limit ?? 8);
42
- const evidence = collectAnswerEvidence(context.store, input, spaceId, limit);
42
+ let evidence = collectAnswerEvidence(context.store, input, spaceId, limit);
43
43
  if (evidence.length === 0) {
44
- const linkedObjects = input.includeLinkedObjects === false ? [] : [...input.linkedObjects ?? []];
45
- const gapSpaceId = concreteAnswerGapSpaceId(spaceId, [], [], linkedObjects);
46
- const gap = await persistAnswerGap(context.store, gapSpaceId, input.query, 'No indexed evidence matched the question.', {
47
- linkedObjects,
48
- });
49
- return {
50
- ok: true,
51
- spaceId,
52
- query: input.query,
53
- answer: {
54
- text: input.noMatchMessage ?? `No knowledge matched "${input.query}".`,
55
- mode,
56
- confidence: 0,
57
- sources: [],
58
- linkedObjects,
59
- facts: [],
60
- gaps: [gap],
61
- synthesized: false,
62
- },
63
- results: [],
64
- };
44
+ const linkedObjects = input.includeLinkedObjects === false ? [] : filterAnswerLinkedObjects(spaceId, input.query, [...(input.linkedObjects ?? [])]);
45
+ const linkedEvidence = includeOfficialLinkedEvidence(context.store, spaceId, input.query, evidence, linkedObjects, limit);
46
+ if (linkedEvidence.length > 0) {
47
+ evidence = linkedEvidence;
48
+ }
49
+ else {
50
+ const gap = shouldPersistNoMatchGap(spaceId, input.query, linkedObjects)
51
+ ? await persistAnswerGap(context.store, concreteAnswerGapSpaceId(spaceId, [], [], linkedObjects), input.query, 'No indexed evidence matched the question.', {
52
+ linkedObjects,
53
+ })
54
+ : null;
55
+ return {
56
+ ok: true,
57
+ spaceId,
58
+ query: input.query,
59
+ answer: {
60
+ text: input.noMatchMessage ?? `No knowledge matched "${input.query}".`,
61
+ mode,
62
+ confidence: 0,
63
+ sources: [],
64
+ linkedObjects,
65
+ facts: [],
66
+ gaps: gap ? [gap] : [],
67
+ synthesized: false,
68
+ },
69
+ results: [],
70
+ };
71
+ }
65
72
  }
66
- const llmAnswer = await synthesizeAnswer(context.llm ?? null, input.query, mode, evidence, input.timeoutMs);
67
- const facts = filterFactsForQuery(input.query, uniqueNodes(evidence.flatMap((item) => item.facts))).slice(0, 24);
68
- const sources = rankAnswerSources(evidence, facts)
69
- .slice(0, limit)
70
- .map(withAnswerSourceAliases);
73
+ let rawFacts = filterFactsForQuery(input.query, uniqueNodes(evidence.flatMap((item) => item.facts))).slice(0, 24);
71
74
  const inferredHomeAssistantLinkedObjects = input.includeLinkedObjects === false
72
75
  ? []
73
76
  : inferHomeAssistantLinkedObjects(context.store, spaceId, input.query);
@@ -84,6 +87,14 @@ export async function answerKnowledgeQuery(context, input) {
84
87
  .filter(isSemanticAnswerLinkedObject)
85
88
  .slice(0, 24);
86
89
  const linkedObjects = filterAnswerLinkedObjects(spaceId, input.query, rawLinkedObjects);
90
+ evidence = includeOfficialLinkedEvidence(context.store, spaceId, input.query, evidence, linkedObjects, limit);
91
+ rawFacts = filterFactsForQuery(input.query, uniqueNodes(evidence.flatMap((item) => item.facts))).slice(0, 24);
92
+ const llmAnswer = await synthesizeAnswer(context.llm ?? null, input.query, mode, evidence, input.timeoutMs);
93
+ const rankedSources = rankAnswerSources(evidence, rawFacts);
94
+ const facts = withAnswerFactContract(context.store, rawFacts, linkedObjects);
95
+ const sources = includeOfficialLinkedSources(context.store, spaceId, rankedSources, linkedObjects)
96
+ .slice(0, limit)
97
+ .map(withAnswerSourceAliases);
87
98
  const gapSpaceId = concreteAnswerGapSpaceId(spaceId, evidence, sources, linkedObjects);
88
99
  const gaps = await persistAnswerGaps(context.store, gapSpaceId, input.query, llmAnswer?.gaps ?? [], {
89
100
  sources,
@@ -105,14 +116,14 @@ export async function answerKnowledgeQuery(context, input) {
105
116
  const llmText = llmAnswer?.answer?.trim();
106
117
  const cleanedLlmText = llmText ? cleanSynthesizedAnswer(llmText, featureIntent) : undefined;
107
118
  const dropLowValueLlmAnswer = featureIntent && Boolean(cleanedLlmText) && isLowValueFeatureOrSpecText(cleanedLlmText ?? '');
108
- const fallback = !llmText || dropLowValueLlmAnswer
109
- ? renderFallbackAnswer(input.query, mode, evidence, facts)
110
- : null;
111
- const synthesizedAnswer = dropLowValueLlmAnswer ? undefined : cleanedLlmText;
112
- const text = synthesizedAnswer || cleanSynthesizedAnswer(fallback?.text || '', featureIntent);
119
+ const fallback = renderFallbackAnswer(input.query, mode, evidence, facts);
120
+ const preferFallback = shouldPreferFallbackAnswer(featureIntent, cleanedLlmText, fallback.text);
121
+ const useFallback = dropLowValueLlmAnswer || preferFallback || !cleanedLlmText;
122
+ const text = cleanSynthesizedAnswer((useFallback ? fallback.text : cleanedLlmText) || '', featureIntent);
123
+ const synthesized = useFallback ? fallback.synthesized : Boolean(cleanedLlmText);
113
124
  const confidence = input.includeConfidence === false
114
125
  ? 0
115
- : dropLowValueLlmAnswer ? 0 : answerConfidence(llmAnswer, evidence);
126
+ : dropLowValueLlmAnswer || (useFallback && !fallback.synthesized) ? 0 : answerConfidence(preferFallback ? null : llmAnswer, evidence);
116
127
  return {
117
128
  ok: true,
118
129
  spaceId,
@@ -125,11 +136,19 @@ export async function answerKnowledgeQuery(context, input) {
125
136
  linkedObjects,
126
137
  facts,
127
138
  gaps: evidenceGap ? uniqueNodes([...gaps, evidenceGap]) : gaps,
128
- synthesized: Boolean(synthesizedAnswer) || Boolean(fallback?.synthesized),
139
+ synthesized,
129
140
  },
130
141
  results: evidence.map(toSearchResult),
131
142
  };
132
143
  }
144
+ function shouldPersistNoMatchGap(spaceId, query, linkedObjects) {
145
+ if (linkedObjects.length > 0)
146
+ return true;
147
+ if (normalizeKnowledgeSpaceId(spaceId) !== 'homeassistant')
148
+ return true;
149
+ const subjectTokens = tokenizeSemanticQuery(query).filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
150
+ return subjectTokens.length > 0;
151
+ }
133
152
  function collectAnswerEvidence(store, input, spaceId, limit) {
134
153
  const tokens = expandQueryTokens(tokenizeSemanticQuery(input.query));
135
154
  if (tokens.length === 0)
@@ -142,6 +161,8 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
142
161
  const sourceFacts = buildSourceFactIndex(store, spaceId);
143
162
  const linkedSourceIds = sourceIdsLinkedToNodes(store, new Set([...candidateNodeIds, ...linkedObjectIds]), spaceId);
144
163
  const broadHomeAssistantAlias = normalizeKnowledgeSpaceId(spaceId) === 'homeassistant' && !strictCandidates;
164
+ if (broadHomeAssistantAlias && subjectTokens.length === 0 && linkedObjectIds.size === 0)
165
+ return [];
145
166
  const homeAssistantScope = !strictCandidates
146
167
  ? inferHomeAssistantAnswerScope(store, spaceId, input.query, subjectTokens)
147
168
  : null;
@@ -253,6 +274,140 @@ function shouldUseEvidenceLinkedObjects(spaceId, input, inferredHomeAssistantLin
253
274
  }
254
275
  return true;
255
276
  }
277
+ function withAnswerFactContract(store, facts, linkedObjects) {
278
+ if (facts.length === 0)
279
+ return [];
280
+ const linkedObjectIds = new Set(linkedObjects.map((node) => node.id));
281
+ return facts.map((fact) => {
282
+ const source = fact.sourceId ? store.getSource(fact.sourceId) : null;
283
+ const discovery = readRecord(source?.metadata.sourceDiscovery);
284
+ const metadataLinkedIds = uniqueStrings([
285
+ ...readStringArray(fact.metadata.linkedObjectIds),
286
+ ...readStringArray(fact.metadata.subjectIds),
287
+ ...readStringArray(discovery.linkedObjectIds),
288
+ ]);
289
+ const subjectIds = metadataLinkedIds.filter((id) => linkedObjectIds.has(id));
290
+ const fallbackSubjectIds = subjectIds.length > 0
291
+ ? subjectIds
292
+ : linkedObjects.length === 1 ? [linkedObjects[0].id] : [];
293
+ const subjects = fallbackSubjectIds
294
+ .map((id) => linkedObjects.find((node) => node.id === id) ?? store.getNode(id))
295
+ .filter((node) => Boolean(node && node.status !== 'stale'));
296
+ if (subjects.length === 0)
297
+ return fact;
298
+ const targetHints = answerTargetHints(subjects);
299
+ const metadata = {
300
+ ...fact.metadata,
301
+ subject: readString(fact.metadata.subject) ?? subjects[0]?.title,
302
+ subjectIds: subjects.map((node) => node.id),
303
+ linkedObjectIds: subjects.map((node) => node.id),
304
+ targetHints,
305
+ };
306
+ return {
307
+ ...fact,
308
+ metadata,
309
+ subject: metadata.subject,
310
+ subjectIds: metadata.subjectIds,
311
+ linkedObjectIds: metadata.linkedObjectIds,
312
+ targetHints,
313
+ };
314
+ });
315
+ }
316
+ function answerTargetHints(nodes) {
317
+ return nodes.map((node) => ({
318
+ id: node.id,
319
+ kind: node.kind,
320
+ title: node.title,
321
+ ...(node.summary ? { summary: node.summary } : {}),
322
+ }));
323
+ }
324
+ function includeOfficialLinkedSources(store, spaceId, rankedSources, linkedObjects) {
325
+ if (linkedObjects.length === 0)
326
+ return rankedSources;
327
+ const linkedIds = new Set(linkedObjects.map((node) => node.id));
328
+ const linkedSourceIds = sourceIdsLinkedToNodes(store, linkedIds, spaceId);
329
+ const official = store.listSources(10_000)
330
+ .filter((source) => belongsToAnswerSpace(source, spaceId))
331
+ .filter((source) => sourceAuthorityBoostForAnswer(source) > 0)
332
+ .filter((source) => {
333
+ const discovery = readRecord(source.metadata.sourceDiscovery);
334
+ return linkedSourceIds.has(source.id)
335
+ || readStringArray(discovery.linkedObjectIds).some((id) => linkedIds.has(id));
336
+ })
337
+ .sort((left, right) => sourceAuthorityBoostForAnswer(right) - sourceAuthorityBoostForAnswer(left));
338
+ return uniqueSources([...official, ...rankedSources]);
339
+ }
340
+ function includeOfficialLinkedEvidence(store, spaceId, query, evidence, linkedObjects, limit) {
341
+ if (linkedObjects.length === 0)
342
+ return [...evidence];
343
+ const linkedIds = new Set(linkedObjects.map((node) => node.id));
344
+ const linkedSourceIds = sourceIdsLinkedToNodes(store, linkedIds, spaceId);
345
+ const tokens = expandQueryTokens(tokenizeSemanticQuery(query));
346
+ const sourceFacts = buildSourceFactIndex(store, spaceId);
347
+ const officialItems = store.listSources(10_000)
348
+ .filter((source) => belongsToAnswerSpace(source, spaceId))
349
+ .filter((source) => sourceAuthorityBoostForAnswer(source) > 0)
350
+ .filter((source) => linkedSourceIds.has(source.id) || readStringArray(readRecord(source.metadata.sourceDiscovery).linkedObjectIds).some((id) => linkedIds.has(id)))
351
+ .map((source) => {
352
+ const extraction = store.getExtractionBySourceId(source.id);
353
+ const facts = filterFactsForQuery(query, sourceFacts.get(source.id) ?? []);
354
+ const text = sourceSemanticText(source, extraction);
355
+ const scoringText = [
356
+ source.title,
357
+ source.summary,
358
+ source.description,
359
+ source.tags.join(' '),
360
+ text,
361
+ facts.map(renderFactForScoring).join(' '),
362
+ ].join('\n');
363
+ const semanticScore = scoreSemanticText(scoringText, tokens);
364
+ return {
365
+ kind: 'source',
366
+ id: source.id,
367
+ title: source.title ?? source.canonicalUri ?? source.sourceUri ?? source.id,
368
+ score: semanticScore + sourceAuthorityBoostForAnswer(source) + Math.min(80, facts.length * 10),
369
+ source,
370
+ excerpt: selectEvidenceExcerpt(query, text, facts),
371
+ facts,
372
+ };
373
+ })
374
+ .filter((item) => item.score > 0);
375
+ if (officialItems.length === 0)
376
+ return [...evidence];
377
+ return uniqueEvidenceItems([...officialItems, ...evidence]
378
+ .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)))
379
+ .slice(0, Math.max(limit, evidence.length, 1));
380
+ }
381
+ function shouldPreferFallbackAnswer(featureIntent, llmText, fallbackText) {
382
+ if (!featureIntent || !fallbackText)
383
+ return false;
384
+ if (!llmText)
385
+ return true;
386
+ const lower = llmText.toLowerCase();
387
+ if (isLowValueFeatureOrSpecText(llmText))
388
+ return true;
389
+ if (/\b(source-backed facts identify|available source-backed details include|matching sources exist|not enough source-backed facts|not enough concrete source-backed)\b/.test(lower)) {
390
+ return hasConcreteFeatureSignal(fallbackText);
391
+ }
392
+ if (/\bevidence\b/.test(lower) && featureFamilyCount(fallbackText) >= 2)
393
+ return true;
394
+ if (featureFamilyCount(llmText) < 2 && featureFamilyCount(fallbackText) >= 2)
395
+ return true;
396
+ return false;
397
+ }
398
+ function featureFamilyCount(text) {
399
+ const lower = text.toLowerCase();
400
+ return [
401
+ /\b(hdmi|earc|arc|ports?|usb|ethernet|optical|rf|antenna|rs-?232c?)\b/,
402
+ /\b(hdr|hdr10|dolby vision|hlg|filmmaker)\b/,
403
+ /\b(4k|8k|uhd|resolution|refresh|120\s*hz|100\s*hz|nanocell|display|screen)\b/,
404
+ /\b(webos|apps?|streaming|airplay|homekit|chromecast|smart tv|thinq)\b/,
405
+ /\b(wi-?fi|bluetooth|wireless lan)\b/,
406
+ /\b(audio|speakers?|dolby atmos|sound)\b/,
407
+ /\b(game|gaming|vrr|allm|freesync|g-sync)\b/,
408
+ /\b(tuner|atsc|qam|ntsc|broadcast)\b/,
409
+ ].filter((pattern) => pattern.test(lower)).length;
410
+ }
256
411
  async function synthesizeAnswer(llm, query, mode, evidence, requestedTimeoutMs) {
257
412
  if (!llm)
258
413
  return null;
@@ -546,17 +701,26 @@ function selectEvidenceExcerpt(query, text, facts) {
546
701
  const tokens = expandQueryTokens(tokenizeSemanticQuery(query));
547
702
  const intent = factIntent(tokenizeSemanticQuery(query));
548
703
  const featureIntent = Boolean(intent && hasFeatureIntent(intent));
704
+ const evidenceText = stripEvidenceRoutingFragments(text);
549
705
  const factLines = facts
550
706
  .map(renderFactForPrompt)
551
707
  .filter((line) => scoreSemanticText(line, tokens) > 0)
552
708
  .filter((line) => !featureIntent || !isLowValueFeatureOrSpecText(line))
553
709
  .slice(0, 12);
554
- const windows = evidenceWindows(text, tokens)
710
+ const windows = evidenceWindows(evidenceText, tokens)
555
711
  .filter((line) => !featureIntent || !isLowValueFeatureOrSpecText(line))
556
712
  .slice(0, 4);
557
- const fallback = featureIntent && (factLines.length > 0 || windows.length > 0) ? [] : [clampText(text, 720)];
713
+ const fallback = featureIntent && (factLines.length > 0 || windows.length > 0) ? [] : [clampText(evidenceText, 720)];
558
714
  return uniqueStrings([...factLines, ...windows, ...fallback]).join('\n');
559
715
  }
716
+ function stripEvidenceRoutingFragments(text) {
717
+ return normalizeWhitespace(text
718
+ .replace(/homegraph:\/\/\S+/gi, ' ')
719
+ .replace(/https?:\/\/\S+/gi, ' ')
720
+ .replace(/\bsemantic-gap-repair\b/gi, ' ')
721
+ .replace(/\bgenerated-page\b/gi, ' ')
722
+ .replace(/\b[a-z0-9-]+\.(?:com|net|org|io|dev|tv|ca|co\.uk)(?:\/\S*)?/gi, ' '));
723
+ }
560
724
  function evidenceWindows(text, tokens) {
561
725
  const normalized = normalizeWhitespace(text);
562
726
  const sentences = splitSentences(normalized, 420);
@@ -706,6 +870,18 @@ function uniqueSources(sources) {
706
870
  }
707
871
  return out;
708
872
  }
873
+ function uniqueEvidenceItems(items) {
874
+ const seen = new Set();
875
+ const out = [];
876
+ for (const item of items) {
877
+ const key = `${item.kind}:${item.id}`;
878
+ if (seen.has(key))
879
+ continue;
880
+ seen.add(key);
881
+ out.push(item);
882
+ }
883
+ return out;
884
+ }
709
885
  function withAnswerSourceAliases(source) {
710
886
  return {
711
887
  ...source,
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA+BtC"}
1
+ {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAmBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA+BtC"}
@@ -1,4 +1,5 @@
1
1
  import { MAX_SEMANTIC_SOURCE_CHARS, applySourceMetadata, clampText, normalizeWhitespace, readRecord, readString, semanticHash, semanticMetadata, semanticSlug, sourceKnowledgeSpace, sourceSemanticHash, sourceSemanticText, splitSentences, uniqueStrings, } from './utils.js';
2
+ import { canonicalRepairSubjectNodes } from './repair-subjects.js';
2
3
  export async function enrichKnowledgeSource(context, source, options = {}) {
3
4
  const extraction = context.store.getExtractionBySourceId(source.id);
4
5
  const text = sourceSemanticText(source, extraction);
@@ -192,7 +193,11 @@ async function persistSemanticExtraction(store, source, extraction, semantic, op
192
193
  entities.push(node);
193
194
  await linkSourceToNode(store, source.id, node.id, 'mentions_entity', spaceId, semantic.extractor);
194
195
  }
196
+ const sourceLinkedObjects = linkedObjectsForSource(store, source);
197
+ const sourceLinkedObjectIds = sourceLinkedObjects.map((node) => node.id);
198
+ const sourceTargetHints = sourceLinkedObjects.map((node) => ({ id: node.id, kind: node.kind, title: node.title }));
195
199
  for (const fact of semantic.facts.slice(0, 160)) {
200
+ const targetHints = fact.targetHints?.length ? fact.targetHints : sourceTargetHints;
196
201
  const node = await store.upsertNode({
197
202
  id: `sem-fact-${semanticHash(spaceId, source.id, fact.kind, fact.title, fact.value ?? fact.summary)}`,
198
203
  kind: 'fact',
@@ -208,7 +213,12 @@ async function persistSemanticExtraction(store, source, extraction, semantic, op
208
213
  value: fact.value,
209
214
  evidence: fact.evidence,
210
215
  labels: fact.labels ?? [],
211
- targetHints: fact.targetHints ?? [],
216
+ targetHints,
217
+ ...(sourceLinkedObjectIds.length > 0 ? {
218
+ subject: sourceLinkedObjects[0]?.title,
219
+ subjectIds: sourceLinkedObjectIds,
220
+ linkedObjectIds: sourceLinkedObjectIds,
221
+ } : {}),
212
222
  sourceId: source.id,
213
223
  extractionId: extraction?.id,
214
224
  extractor: semantic.extractor,
@@ -480,6 +490,31 @@ function readStringArray(value) {
480
490
  ? uniqueStrings(value.map((entry) => typeof entry === 'string' ? entry : undefined))
481
491
  : [];
482
492
  }
493
+ function linkedObjectsForSource(store, source) {
494
+ const discovery = readRecord(source.metadata.sourceDiscovery);
495
+ const sourceSpaceId = sourceKnowledgeSpace(source);
496
+ const ids = uniqueStrings([
497
+ ...readStringArray(discovery.linkedObjectIds),
498
+ ...store.listEdges()
499
+ .filter((edge) => edge.fromKind === 'source' && edge.fromId === source.id)
500
+ .filter((edge) => edge.toKind === 'node' && edge.relation === 'source_for')
501
+ .filter((edge) => {
502
+ const edgeSpaceId = readString(edge.metadata.knowledgeSpaceId);
503
+ return !edgeSpaceId || edgeSpaceId === sourceSpaceId;
504
+ })
505
+ .map((edge) => edge.toId),
506
+ ]);
507
+ const nodes = [];
508
+ for (const id of ids) {
509
+ const node = store.getNode(id);
510
+ if (node && node.status !== 'stale')
511
+ nodes.push(node);
512
+ }
513
+ return canonicalRepairSubjectNodes({
514
+ nodes,
515
+ text: `${source.title ?? ''} ${source.summary ?? ''} ${source.description ?? ''}`,
516
+ });
517
+ }
483
518
  function isFact(value) {
484
519
  return Boolean(value);
485
520
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fact-quality.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/fact-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAcvD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAK/E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAQlE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA0HjE;AAkBD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAiB5E"}
1
+ {"version":3,"file":"fact-quality.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/fact-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAcvD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAK/E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAQlE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAqIjE;AAsBD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAiB5E"}
@@ -29,6 +29,7 @@ export function semanticFactText(fact) {
29
29
  export function isLowValueFeatureOrSpecText(text) {
30
30
  const lower = text.toLowerCase();
31
31
  const magicRemoteDetail = /\bmagic remote\b/.test(lower) || /\bbluetooth\b/.test(lower);
32
+ const nonRemoteFeatureSignal = hasNonRemoteFeatureSignal(lower);
32
33
  if (/\?\s*$/.test(text.trim()))
33
34
  return true;
34
35
  if (/\bsemantic-gap-repair\b/.test(lower))
@@ -49,6 +50,8 @@ export function isLowValueFeatureOrSpecText(text) {
49
50
  return true;
50
51
  }
51
52
  if (/^\s*\d+\s*(yes|no)\b/.test(lower)
53
+ || /^\s*0?\d+\s*x\s+(?:ethernet|audio|features?|os|webos|ports?)\b/.test(lower)
54
+ || /^\s*\d+\s*m\s*\(/.test(lower)
52
55
  || /^\s*\d+(hdmi|usb|audio|ports?|features?|smart)\b/.test(lower)
53
56
  || /^\s*0\s+ports\b/.test(lower)
54
57
  || /\b\d+\s+features such as\b/.test(lower)
@@ -56,6 +59,9 @@ export function isLowValueFeatureOrSpecText(text) {
56
59
  || /^\s*\d+\s*kg\d*/.test(lower)) {
57
60
  return true;
58
61
  }
62
+ if (/\b(series_url|exhibition display|supported audio formats|supported video formats|supported picture formats)\b/.test(lower)) {
63
+ return true;
64
+ }
59
65
  if (/\b(button|buttons|remote control)\b/.test(lower) && /[\u25b2\u25bc\u25c4\u25ba]|[▲▼◄►]|\\u25/.test(text)) {
60
66
  return true;
61
67
  }
@@ -73,7 +79,8 @@ export function isLowValueFeatureOrSpecText(text) {
73
79
  return true;
74
80
  }
75
81
  if (/\b(magic remote|remote control)\b/.test(lower)
76
- && /\b(accessor(y|ies)|battery|batteries|button|environment|infrared|mr20ga|point|pointer|remote sensor|sap|sensor|shake|voice recognition)\b/.test(lower)) {
82
+ && /\b(accessor(y|ies)|battery|batteries|button|environment|infrared|mr20ga|point|pointer|remote sensor|sap|sensor|shake|voice recognition)\b/.test(lower)
83
+ && !nonRemoteFeatureSignal) {
77
84
  return true;
78
85
  }
79
86
  if (/\b(crutchfield|speakercompare|speaker compare|equal[- ]power|equal[- ]volume|speaker shopping|speaker recommendations?)\b/.test(lower)) {
@@ -82,6 +89,9 @@ export function isLowValueFeatureOrSpecText(text) {
82
89
  if (/\b(compare sonic characteristics|listening modes?|listening room|auditioning speakers|speakers side-by-side|same amount of power|money-back guarantee|advisors have listened|best choice for your system|speakercompare listening kit|headphones? brand model)\b/.test(lower)) {
83
90
  return true;
84
91
  }
92
+ if (/\b(more direct comparison|direct comparison|compare products?|product comparison)\b/.test(lower)) {
93
+ return true;
94
+ }
85
95
  if (/\b(prices? & features?|smart tv prices?|latest price|view latest|check .* specifications.*price|current page|loading\.?)\b/.test(lower)) {
86
96
  return true;
87
97
  }
@@ -117,7 +127,8 @@ export function isLowValueFeatureOrSpecText(text) {
117
127
  return true;
118
128
  }
119
129
  if (/\b(magic remote|remote)\b/.test(lower)
120
- && /\b(shake|pointer|cursor appears|environment|operating environment|voice recognition|recognition performance|point|sensor|press|button|sap|more actions?)\b/.test(lower)) {
130
+ && /\b(shake|pointer|cursor appears|environment|operating environment|voice recognition|recognition performance|point|sensor|press|button|sap|more actions?)\b/.test(lower)
131
+ && !nonRemoteFeatureSignal) {
121
132
  return true;
122
133
  }
123
134
  if (/\b(sap|secondary audio program)\b/.test(lower) && /\b(button|press|enabled|audio)\b/.test(lower)) {
@@ -154,6 +165,9 @@ export function isLowValueFeatureOrSpecText(text) {
154
165
  }
155
166
  return false;
156
167
  }
168
+ function hasNonRemoteFeatureSignal(text) {
169
+ return /\b(hdmi|earc|arc|hdr|hdr10|dolby vision|hlg|filmmaker|game optimizer|game mode|gaming|freesync|vrr|allm|4k|uhd|resolution|refresh|webos|airplay|homekit|wi-?fi|ethernet|usb|optical|tuner|atsc|qam|speaker|audio)\b/.test(text);
170
+ }
157
171
  function isUrlOrPathFragment(value) {
158
172
  return /https?:\/\//.test(value)
159
173
  || /\b[a-z0-9-]+\.(com|net|org|io|dev|tv|ca|co\.uk)\/[a-z0-9/_?=&.#-]+/.test(value)
@@ -1 +1 @@
1
- {"version":3,"file":"repair-profile.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/repair-profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AA0HD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GAAG,SAAS,iBAAiB,EAAE,CA8B/B"}
1
+ {"version":3,"file":"repair-profile.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/repair-profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AA0HD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GAAG,SAAS,iBAAiB,EAAE,CA6B/B"}
@@ -13,7 +13,7 @@ const PROFILE_RULES = [
13
13
  ['4K UHD resolution', /\b4k\b|\buhd\b|\b3840\s*(?:x|×)\s*2160\b/i],
14
14
  ['NanoCell display technology', /\bnanocell\b/i],
15
15
  ['LCD/LED display', /\blcd\b|\bled\b/i],
16
- ['100/120 Hz refresh-rate evidence', /\b(?:100|120)\s*hz\b|\btrumotion\s*240\b/i],
16
+ ['100/120 Hz refresh rate', /\b(?:100|120)\s*hz\b|\btrumotion\s*240\b/i],
17
17
  ['HDR10', /\bhdr10\b/i],
18
18
  ['Dolby Vision', /\bdolby vision\b/i],
19
19
  ['HLG', /\bhlg\b/i],
@@ -74,10 +74,10 @@ const PROFILE_RULES = [
74
74
  intent: /\b(game|gaming|vrr|allm|freesync|g-?sync|low latency|hdmi\s*2\.1|4k\s*120)\b/,
75
75
  minimumMatches: 1,
76
76
  terms: [
77
- ['FreeSync/VRR evidence', /\bfreesync\b|\bvrr\b/i],
78
- ['ALLM/low latency evidence', /\ballm\b|\blow latency\b/i],
77
+ ['FreeSync/VRR support', /\bfreesync\b|\bvrr\b/i],
78
+ ['ALLM/low-latency support', /\ballm\b|\blow latency\b/i],
79
79
  ['Game Optimizer/game mode', /\bgame optimizer\b|\bgame mode\b|\bgaming\b/i],
80
- ['HDMI 2.1/high-bandwidth HDMI evidence', /\bhdmi\s*2\.1\b|\b4k\s*(?:at|@)?\s*120\b|\b120\s*hz\b/i],
80
+ ['HDMI 2.1/high-bandwidth HDMI', /\bhdmi\s*2\.1\b|\b4k\s*(?:at|@)?\s*120\b|\b120\s*hz\b/i],
81
81
  ],
82
82
  },
83
83
  {
@@ -88,10 +88,10 @@ const PROFILE_RULES = [
88
88
  intent: /\b(audio|speaker|sound|dolby atmos|dolby digital|earc|arc|watts?|channels?)\b/,
89
89
  minimumMatches: 1,
90
90
  terms: [
91
- ['speaker/audio output evidence', /\bspeakers?\b|\b(?:10|20|40)\s*w\b|\b2(?:\.0)?\s*ch\b/i],
91
+ ['speaker/audio output', /\bspeakers?\b|\b(?:10|20|40)\s*w\b|\b2(?:\.0)?\s*ch\b/i],
92
92
  ['Dolby audio formats', /\bdolby atmos\b|\bdolby digital\b|\bdolby audio\b/i],
93
93
  ['HDMI ARC/eARC audio', /\bearc\b|\barc\b/i],
94
- ['supported audio formats', /\bsupported audio formats?\b|\bpcm\b|\btruehd\b/i],
94
+ ['audio format support', /\bsupported audio formats?\b|\bpcm\b|\btruehd\b/i],
95
95
  ],
96
96
  },
97
97
  {
@@ -110,14 +110,12 @@ const PROFILE_RULES = [
110
110
  },
111
111
  ];
112
112
  export function deriveRepairProfileFacts(input) {
113
- const text = normalizeWhitespace([
113
+ const text = profileEvidenceText([
114
114
  input.source.title,
115
115
  input.source.summary,
116
116
  input.source.description,
117
- input.source.sourceUri,
118
- input.source.canonicalUri,
119
117
  input.text,
120
- ].filter(Boolean).join(' '));
118
+ ]);
121
119
  if (!hasConcreteFeatureSignal(text))
122
120
  return [];
123
121
  const query = input.query.toLowerCase();
@@ -127,9 +125,10 @@ export function deriveRepairProfileFacts(input) {
127
125
  .filter(([, pattern]) => pattern.test(text))
128
126
  .map(([label]) => label));
129
127
  const wanted = broadProfileIntent || rule.intent.test(query);
130
- if (values.length < (wanted ? 1 : rule.minimumMatches))
128
+ const minimumMatches = rule.intent.test(query) ? 1 : rule.minimumMatches;
129
+ if (values.length < minimumMatches || !wanted)
131
130
  return [];
132
- const summary = `${rule.title} evidence includes ${joinValues(values)}.`;
131
+ const summary = `${rule.title}: ${joinValues(values)}.`;
133
132
  if (isLowValueFeatureOrSpecText(summary))
134
133
  return [];
135
134
  return [{
@@ -143,6 +142,15 @@ export function deriveRepairProfileFacts(input) {
143
142
  }];
144
143
  }).slice(0, 10);
145
144
  }
145
+ function profileEvidenceText(values) {
146
+ return normalizeWhitespace(values
147
+ .filter(Boolean)
148
+ .join(' ')
149
+ .replace(/homegraph:\/\/\S+/g, ' ')
150
+ .replace(/https?:\/\/\S+/g, ' ')
151
+ .replace(/\b[a-z0-9.-]+\.(?:com|net|org|io|dev|tv|ca|co\.uk)\/[a-z0-9/_?=&.#-]+/gi, ' ')
152
+ .replace(/\b(?:series_url|current page|loading)\b/gi, ' '));
153
+ }
146
154
  function joinValues(values) {
147
155
  if (values.length <= 1)
148
156
  return values[0] ?? '';
@@ -1 +1 @@
1
- {"version":3,"file":"self-improvement-promotion.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement-promotion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,6BAA6B,EAE9B,MAAM,aAAa,CAAC;AAmBrB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3I;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,EACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,mBAAmB,EACxB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,IAAI,EAAE,6BAA6B,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
1
+ {"version":3,"file":"self-improvement-promotion.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement-promotion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,6BAA6B,EAE9B,MAAM,aAAa,CAAC;AAkBrB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3I;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,EACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,mBAAmB,EACxB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,IAAI,EAAE,6BAA6B,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}