@prenta/core 5.8.1 → 5.10.0
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/CHANGELOG.md +16 -0
- package/dist/actions.d.ts +5 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +35 -3
- package/dist/actions.js.map +1 -1
- package/dist/ai/brand-voice-alignment.d.ts.map +1 -1
- package/dist/ai/brand-voice-alignment.js +2 -0
- package/dist/ai/brand-voice-alignment.js.map +1 -1
- package/dist/ai/brand-voice-analyze.d.ts.map +1 -1
- package/dist/ai/brand-voice-analyze.js +1 -0
- package/dist/ai/brand-voice-analyze.js.map +1 -1
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/runtime.d.ts +40 -2
- package/dist/ai/runtime.d.ts.map +1 -1
- package/dist/ai/runtime.js +60 -0
- package/dist/ai/runtime.js.map +1 -1
- package/dist/api/handler-factory.d.ts.map +1 -1
- package/dist/api/handler-factory.js +15 -6
- package/dist/api/handler-factory.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +2 -0
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/route-helpers.d.ts +8 -13
- package/dist/api/route-helpers.d.ts.map +1 -1
- package/dist/api/route-helpers.js +19 -39
- package/dist/api/route-helpers.js.map +1 -1
- package/dist/api/routes/scheduling.d.ts.map +1 -1
- package/dist/api/routes/scheduling.js +8 -1
- package/dist/api/routes/scheduling.js.map +1 -1
- package/dist/api/routes/seo-geo.d.ts +23 -0
- package/dist/api/routes/seo-geo.d.ts.map +1 -0
- package/dist/api/routes/seo-geo.js +188 -0
- package/dist/api/routes/seo-geo.js.map +1 -0
- package/dist/api/routes/seo.d.ts.map +1 -1
- package/dist/api/routes/seo.js +49 -4
- package/dist/api/routes/seo.js.map +1 -1
- package/dist/astro-redirects.d.ts +5 -0
- package/dist/astro-redirects.d.ts.map +1 -1
- package/dist/astro-redirects.js +13 -1
- package/dist/astro-redirects.js.map +1 -1
- package/dist/config/collection-lookup.d.ts +21 -0
- package/dist/config/collection-lookup.d.ts.map +1 -0
- package/dist/config/collection-lookup.js +44 -0
- package/dist/config/collection-lookup.js.map +1 -0
- package/dist/cron/index.d.ts +8 -0
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron/index.js +21 -0
- package/dist/cron/index.js.map +1 -1
- package/dist/db/model-types.d.ts +21 -0
- package/dist/db/model-types.d.ts.map +1 -1
- package/dist/forms/attribution.d.ts +6 -2
- package/dist/forms/attribution.d.ts.map +1 -1
- package/dist/forms/attribution.js +5 -0
- package/dist/forms/attribution.js.map +1 -1
- package/dist/forms/index.d.ts +2 -2
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/generated/browser.d.ts +18 -0
- package/dist/generated/client.d.ts +18 -0
- package/dist/generated/internal/class.d.ts +22 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.d.ts +187 -1
- package/dist/generated/internal/prismaNamespace.js +32 -0
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +36 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +32 -0
- package/dist/generated/models/GeoCrawlDaily.d.ts +1144 -0
- package/dist/generated/models/GeoCrawlDaily.js +1 -0
- package/dist/generated/models/GeoReferralDaily.d.ts +1116 -0
- package/dist/generated/models/GeoReferralDaily.js +1 -0
- package/dist/generated/models.d.ts +2 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/public-geo.d.ts +39 -0
- package/dist/public-geo.d.ts.map +1 -0
- package/dist/public-geo.js +79 -0
- package/dist/public-geo.js.map +1 -0
- package/dist/security/rate-limit-policy.d.ts +23 -0
- package/dist/security/rate-limit-policy.d.ts.map +1 -1
- package/dist/security/rate-limit-policy.js +36 -2
- package/dist/security/rate-limit-policy.js.map +1 -1
- package/dist/seo/ai-bots.d.ts +38 -7
- package/dist/seo/ai-bots.d.ts.map +1 -1
- package/dist/seo/ai-bots.js +82 -27
- package/dist/seo/ai-bots.js.map +1 -1
- package/dist/seo/ai-crawler-log.d.ts +2 -0
- package/dist/seo/ai-crawler-log.d.ts.map +1 -1
- package/dist/seo/ai-crawler-log.js +19 -1
- package/dist/seo/ai-crawler-log.js.map +1 -1
- package/dist/seo/ai-referrers.d.ts +25 -0
- package/dist/seo/ai-referrers.d.ts.map +1 -0
- package/dist/seo/ai-referrers.js +96 -0
- package/dist/seo/ai-referrers.js.map +1 -0
- package/dist/seo/audit-engine.d.ts +5 -0
- package/dist/seo/audit-engine.d.ts.map +1 -1
- package/dist/seo/audit-engine.js +63 -0
- package/dist/seo/audit-engine.js.map +1 -1
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +23 -1
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/autopilot-gate.d.ts.map +1 -1
- package/dist/seo/autopilot-gate.js +30 -2
- package/dist/seo/autopilot-gate.js.map +1 -1
- package/dist/seo/autopilot-policy.d.ts +2 -2
- package/dist/seo/autopilot-policy.d.ts.map +1 -1
- package/dist/seo/autopilot-policy.js +1 -0
- package/dist/seo/autopilot-policy.js.map +1 -1
- package/dist/seo/autopilot-revert.d.ts.map +1 -1
- package/dist/seo/autopilot-revert.js +5 -2
- package/dist/seo/autopilot-revert.js.map +1 -1
- package/dist/seo/autopilot.d.ts.map +1 -1
- package/dist/seo/autopilot.js +18 -11
- package/dist/seo/autopilot.js.map +1 -1
- package/dist/seo/config-store.d.ts +4 -0
- package/dist/seo/config-store.d.ts.map +1 -1
- package/dist/seo/config-store.js +3 -0
- package/dist/seo/config-store.js.map +1 -1
- package/dist/seo/digest.d.ts +23 -0
- package/dist/seo/digest.d.ts.map +1 -1
- package/dist/seo/digest.js +89 -3
- package/dist/seo/digest.js.map +1 -1
- package/dist/seo/faq-eligibility.d.ts +32 -0
- package/dist/seo/faq-eligibility.d.ts.map +1 -0
- package/dist/seo/faq-eligibility.js +61 -0
- package/dist/seo/faq-eligibility.js.map +1 -0
- package/dist/seo/faq-schema.d.ts +13 -0
- package/dist/seo/faq-schema.d.ts.map +1 -0
- package/dist/seo/faq-schema.js +41 -0
- package/dist/seo/faq-schema.js.map +1 -0
- package/dist/seo/faq-signals.d.ts +54 -0
- package/dist/seo/faq-signals.d.ts.map +1 -0
- package/dist/seo/faq-signals.js +176 -0
- package/dist/seo/faq-signals.js.map +1 -0
- package/dist/seo/geo-record.d.ts +75 -0
- package/dist/seo/geo-record.d.ts.map +1 -0
- package/dist/seo/geo-record.js +252 -0
- package/dist/seo/geo-record.js.map +1 -0
- package/dist/seo/geo-report.d.ts +96 -0
- package/dist/seo/geo-report.d.ts.map +1 -0
- package/dist/seo/geo-report.js +315 -0
- package/dist/seo/geo-report.js.map +1 -0
- package/dist/seo/geo-retention.d.ts +19 -0
- package/dist/seo/geo-retention.d.ts.map +1 -0
- package/dist/seo/geo-retention.js +23 -0
- package/dist/seo/geo-retention.js.map +1 -0
- package/dist/seo/index.d.ts +10 -3
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +5 -1
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/issue-fix-ai.d.ts +34 -5
- package/dist/seo/issue-fix-ai.d.ts.map +1 -1
- package/dist/seo/issue-fix-ai.js +59 -23
- package/dist/seo/issue-fix-ai.js.map +1 -1
- package/dist/seo/issue-fix-apply.d.ts +32 -2
- package/dist/seo/issue-fix-apply.d.ts.map +1 -1
- package/dist/seo/issue-fix-apply.js +231 -3
- package/dist/seo/issue-fix-apply.js.map +1 -1
- package/dist/seo/issue-fix-faq.d.ts +131 -0
- package/dist/seo/issue-fix-faq.d.ts.map +1 -0
- package/dist/seo/issue-fix-faq.js +455 -0
- package/dist/seo/issue-fix-faq.js.map +1 -0
- package/dist/seo/issue-fix-verify.d.ts +9 -0
- package/dist/seo/issue-fix-verify.d.ts.map +1 -1
- package/dist/seo/issue-fix-verify.js +61 -0
- package/dist/seo/issue-fix-verify.js.map +1 -1
- package/dist/seo/issue-fix.d.ts +5 -2
- package/dist/seo/issue-fix.d.ts.map +1 -1
- package/dist/seo/issue-fix.js +35 -0
- package/dist/seo/issue-fix.js.map +1 -1
- package/dist/seo/page-meta.d.ts +8 -0
- package/dist/seo/page-meta.d.ts.map +1 -1
- package/dist/seo/page-meta.js +85 -12
- package/dist/seo/page-meta.js.map +1 -1
- package/dist/seo/proposals-bulk-apply.d.ts +6 -3
- package/dist/seo/proposals-bulk-apply.d.ts.map +1 -1
- package/dist/seo/proposals-bulk-apply.js +1 -0
- package/dist/seo/proposals-bulk-apply.js.map +1 -1
- package/dist/seo/proposals.d.ts +24 -0
- package/dist/seo/proposals.d.ts.map +1 -1
- package/dist/seo/proposals.js +35 -4
- package/dist/seo/proposals.js.map +1 -1
- package/dist/seo/search-console/sync.d.ts +8 -0
- package/dist/seo/search-console/sync.d.ts.map +1 -1
- package/dist/seo/search-console/sync.js +6 -3
- package/dist/seo/search-console/sync.js.map +1 -1
- package/dist/seo/seo-field-values.d.ts +18 -0
- package/dist/seo/seo-field-values.d.ts.map +1 -0
- package/dist/seo/seo-field-values.js +25 -0
- package/dist/seo/seo-field-values.js.map +1 -0
- package/dist/site.d.ts +5 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +3 -0
- package/dist/site.js.map +1 -1
- package/package.json +1 -1
- package/prisma/migrations/0018_geo_daily/migration.sql +35 -0
- package/prisma/schema.prisma +41 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "can this page get a FAQ section?" rules for the `missing-faq`
|
|
3
|
+
* scanner and the `add-faq-section` apply path, so the two never disagree.
|
|
4
|
+
*/
|
|
5
|
+
import { collectionSupportsSections } from '../config/collection-lookup.js';
|
|
6
|
+
import { isPageBuilderTree } from '../sections/bridge.js';
|
|
7
|
+
import { coerceSections } from '../sections/helpers.js';
|
|
8
|
+
import { getSectionType, isSectionTypeAddable, isSectionTypeAllowedForTemplate, readDocumentTemplate, } from '../sections/registry.js';
|
|
9
|
+
/** True when any section — visible or hidden — has `sectionType === 'faq'`. */
|
|
10
|
+
export function hasFaqSection(data) {
|
|
11
|
+
const sections = data?.sections;
|
|
12
|
+
return (Array.isArray(sections) &&
|
|
13
|
+
sections.some((s) => s && typeof s === 'object' && s.sectionType === 'faq'));
|
|
14
|
+
}
|
|
15
|
+
/** True when the document has no readable sections and its content lives in a legacy `_layout` tree. */
|
|
16
|
+
function hasOnlyLegacyLayout(data) {
|
|
17
|
+
return (coerceSections(data.sections, { onUnknown: 'preserve' }).length === 0 &&
|
|
18
|
+
isPageBuilderTree(data._layout));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether a `faq` section may be inserted into this document: type pins,
|
|
22
|
+
* template allowlist, the collection's `sections` storage contract, and the
|
|
23
|
+
* legacy-layout guard. The `missing-faq` scanner and the `add-section` apply
|
|
24
|
+
* branch both call this so a proposal is never raised that apply would refuse.
|
|
25
|
+
*/
|
|
26
|
+
export function canAddFaqSection(data, opts = {}) {
|
|
27
|
+
const def = getSectionType('faq');
|
|
28
|
+
if (!def)
|
|
29
|
+
return { ok: false, reason: 'unknown-type' };
|
|
30
|
+
if (!isSectionTypeAddable(def))
|
|
31
|
+
return { ok: false, reason: 'not-addable' };
|
|
32
|
+
if (!isSectionTypeAllowedForTemplate(def, readDocumentTemplate(data ?? null))) {
|
|
33
|
+
return { ok: false, reason: 'template-disallows' };
|
|
34
|
+
}
|
|
35
|
+
if (opts.collection !== undefined && !collectionSupportsSections(opts.collection)) {
|
|
36
|
+
return { ok: false, reason: 'no-sections-field' };
|
|
37
|
+
}
|
|
38
|
+
if (data && hasOnlyLegacyLayout(data))
|
|
39
|
+
return { ok: false, reason: 'legacy-layout' };
|
|
40
|
+
return { ok: true };
|
|
41
|
+
}
|
|
42
|
+
function joinNames(names) {
|
|
43
|
+
if (names.length <= 1)
|
|
44
|
+
return names[0] ?? 'AI engines';
|
|
45
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
46
|
+
}
|
|
47
|
+
/** One human line naming the strongest signal; GEO referrals, then retrieval crawls, then the top question. */
|
|
48
|
+
export function strongestFaqSignalText(signals) {
|
|
49
|
+
const g = signals.geo;
|
|
50
|
+
if (g && g.referralVisits > 0) {
|
|
51
|
+
return `Cited by ${joinNames(g.engines)} ${g.referralVisits}× in the last 28 days`;
|
|
52
|
+
}
|
|
53
|
+
if (g && g.retrievalHits > 0) {
|
|
54
|
+
return `Fetched by ${joinNames(g.engines)} ${g.retrievalHits}× in the last 28 days`;
|
|
55
|
+
}
|
|
56
|
+
const q = signals.queries[0];
|
|
57
|
+
if (q)
|
|
58
|
+
return `Asked "${q.query}" ${q.impressions}× on Google in the last 28 days`;
|
|
59
|
+
return 'People reach this page with questions';
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=faq-eligibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq-eligibility.js","sourceRoot":"","sources":["../../src/seo/faq-eligibility.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAQPage helpers shared by the composer-adjacent audit type
|
|
3
|
+
* (`faq-schema-missing`) and its deterministic fix. Pure; no DB.
|
|
4
|
+
*/
|
|
5
|
+
import type { FaqItem } from './page-meta.js';
|
|
6
|
+
/** A `faq` section needs at least this many complete rows before schema is worth emitting. */
|
|
7
|
+
export declare const FAQ_SCHEMA_MIN_ROWS = 2;
|
|
8
|
+
/** True when `node`, or any entry of its `@graph`, is (or includes) FAQPage. */
|
|
9
|
+
export declare function jsonLdHasFaqPage(node: unknown): boolean;
|
|
10
|
+
export declare function buildFaqPageNode(items: FaqItem[], url: string): Record<string, unknown>;
|
|
11
|
+
/** Return a copy of `override` with `node` added to its graph (wrapping a single node into `@graph`). */
|
|
12
|
+
export declare function insertFaqPageNode(override: Record<string, unknown>, node: Record<string, unknown>): Record<string, unknown>;
|
|
13
|
+
//# sourceMappingURL=faq-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq-schema.d.ts","sourceRoot":"","sources":["../../src/seo/faq-schema.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE7C,8FAA8F;AAC9F,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAMpC,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAWvD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUvF;AAQD,yGAAyG;AACzG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** A `faq` section needs at least this many complete rows before schema is worth emitting. */
|
|
2
|
+
export const FAQ_SCHEMA_MIN_ROWS = 2;
|
|
3
|
+
function typeHasFaq(type) {
|
|
4
|
+
return type === 'FAQPage' || (Array.isArray(type) && type.includes('FAQPage'));
|
|
5
|
+
}
|
|
6
|
+
/** True when `node`, or any entry of its `@graph`, is (or includes) FAQPage. */
|
|
7
|
+
export function jsonLdHasFaqPage(node) {
|
|
8
|
+
if (!node || typeof node !== 'object')
|
|
9
|
+
return false;
|
|
10
|
+
const rec = node;
|
|
11
|
+
if (typeHasFaq(rec['@type']))
|
|
12
|
+
return true;
|
|
13
|
+
const graph = rec['@graph'];
|
|
14
|
+
return (Array.isArray(graph) &&
|
|
15
|
+
graph.some((n) => n && typeof n === 'object' && typeHasFaq(n['@type'])));
|
|
16
|
+
}
|
|
17
|
+
export function buildFaqPageNode(items, url) {
|
|
18
|
+
return {
|
|
19
|
+
'@type': 'FAQPage',
|
|
20
|
+
'@id': `${url}#faq`,
|
|
21
|
+
mainEntity: items.map((item) => ({
|
|
22
|
+
'@type': 'Question',
|
|
23
|
+
name: item.question,
|
|
24
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function stripContext(node) {
|
|
29
|
+
const rest = { ...node };
|
|
30
|
+
delete rest['@context'];
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
33
|
+
/** Return a copy of `override` with `node` added to its graph (wrapping a single node into `@graph`). */
|
|
34
|
+
export function insertFaqPageNode(override, node) {
|
|
35
|
+
const context = override['@context'] ?? 'https://schema.org';
|
|
36
|
+
if (Array.isArray(override['@graph'])) {
|
|
37
|
+
return { ...override, '@context': context, '@graph': [...override['@graph'], node] };
|
|
38
|
+
}
|
|
39
|
+
return { '@context': context, '@graph': [stripContext(override), node] };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=faq-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq-schema.js","sourceRoot":"","sources":["../../src/seo/faq-schema.ts"],"names":[],"mappings":"AAMA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAEpC,SAAS,UAAU,CAAC,IAAa;IAC/B,OAAO,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AAChF,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACnD,MAAM,GAAG,GAAG,IAA+B,CAAA;IAC3C,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC3B,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,UAAU,CAAE,CAA6B,CAAC,OAAO,CAAC,CAAC,CACzF,CACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAgB,EAAE,GAAW;IAC5D,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,GAAG,GAAG,MAAM;QACnB,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC/B,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,IAAI,CAAC,QAAQ;YACnB,cAAc,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;SACzD,CAAC,CAAC;KACJ,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAA6B;IACjD,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAA;IACxB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAA;IACvB,OAAO,IAAI,CAAA;AACb,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,iBAAiB,CAC/B,QAAiC,EACjC,IAA6B;IAE7B,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,oBAAoB,CAAA;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,EAAE,CAAA;IACtF,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,EAAE,CAAA;AAC1E,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AuditEntityInput } from './audit-engine.js';
|
|
2
|
+
export interface FaqSignals {
|
|
3
|
+
geo: {
|
|
4
|
+
referralVisits: number;
|
|
5
|
+
retrievalHits: number;
|
|
6
|
+
engines: string[];
|
|
7
|
+
} | null;
|
|
8
|
+
queries: Array<{
|
|
9
|
+
query: string;
|
|
10
|
+
impressions: number;
|
|
11
|
+
clicks: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare const FAQ_SIGNAL_WINDOW_DAYS = 28;
|
|
15
|
+
export declare const FAQ_QUERY_MIN_IMPRESSIONS = 10;
|
|
16
|
+
export declare const FAQ_QUERY_MAX = 10;
|
|
17
|
+
export declare const FAQ_QUERY_MAX_LEN = 120;
|
|
18
|
+
export declare function isQuestionQuery(query: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Pathname for matching GEO rows: leading `/`, no trailing slash,
|
|
21
|
+
* percent-decoded, lowercased, `/` for root. GEO rows are stored through
|
|
22
|
+
* `normalizeRedirectPath`, which lowercases; both sides go through this
|
|
23
|
+
* function so the comparison stays symmetric.
|
|
24
|
+
*/
|
|
25
|
+
export declare function entityPathname(url: string, siteUrl: string): string;
|
|
26
|
+
export interface FaqSignalRows {
|
|
27
|
+
referrals: Array<{
|
|
28
|
+
path: string;
|
|
29
|
+
visits: number;
|
|
30
|
+
engines: string[];
|
|
31
|
+
}>;
|
|
32
|
+
retrievalCrawls: Array<{
|
|
33
|
+
path: string;
|
|
34
|
+
hits: number;
|
|
35
|
+
}>;
|
|
36
|
+
insights: Array<{
|
|
37
|
+
entityId: string | null;
|
|
38
|
+
path: string;
|
|
39
|
+
topQueries: unknown;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pure detector — maps GEO daily rows and Search Console insight rows onto
|
|
44
|
+
* audited entities by pathname (or insight `entityId`). Entities without any
|
|
45
|
+
* signal are omitted from the result.
|
|
46
|
+
*/
|
|
47
|
+
export declare function detectFaqSignals(rows: FaqSignalRows, entities: Pick<AuditEntityInput, 'entityId' | 'url'>[], siteUrl: string): Record<string, FaqSignals>;
|
|
48
|
+
/** DB wrapper — every read is optional; a missing table or a query error yields no signal. */
|
|
49
|
+
export declare function gatherFaqSignals(db: unknown, entities: Pick<AuditEntityInput, 'entityId' | 'url'>[], opts: {
|
|
50
|
+
siteUrl: string;
|
|
51
|
+
now?: Date;
|
|
52
|
+
geoEnabled: boolean;
|
|
53
|
+
}): Promise<Record<string, FaqSignals>>;
|
|
54
|
+
//# sourceMappingURL=faq-signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq-signals.d.ts","sourceRoot":"","sources":["../../src/seo/faq-signals.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAIzD,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IAChF,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACvE;AAED,eAAO,MAAM,sBAAsB,KAAK,CAAA;AACxC,eAAO,MAAM,yBAAyB,KAAK,CAAA;AAC3C,eAAO,MAAM,aAAa,KAAK,CAAA;AAC/B,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAKpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACrE,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAChF;AAkBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,KAAK,CAAC,EAAE,EACtD,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CA8C5B;AAED,8FAA8F;AAC9F,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,KAAK,CAAC,EAAE,EACtD,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GACzD,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAmDrC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signals that a page attracts questions: AI-engine referrals / retrieval
|
|
3
|
+
* crawls (GEO measurement) and question-shaped Search Console queries.
|
|
4
|
+
* Pure detector + DB wrapper, mirroring `search-insight-signals.ts`.
|
|
5
|
+
* Consumed by the audit engine as `AuditExtraSignals.faqSignalsByEntityId`.
|
|
6
|
+
*/
|
|
7
|
+
import { hasModel } from '../api/route-helpers.js';
|
|
8
|
+
import { startOfDayUTC } from './autopilot-ledger.js';
|
|
9
|
+
import { GEO_OTHER_PATH } from './geo-record.js';
|
|
10
|
+
export const FAQ_SIGNAL_WINDOW_DAYS = 28;
|
|
11
|
+
export const FAQ_QUERY_MIN_IMPRESSIONS = 10;
|
|
12
|
+
export const FAQ_QUERY_MAX = 10;
|
|
13
|
+
export const FAQ_QUERY_MAX_LEN = 120;
|
|
14
|
+
const QUESTION_START = /^(who|what|when|where|why|how|which|can|could|should|does|do|is|are|will)\b/i;
|
|
15
|
+
export function isQuestionQuery(query) {
|
|
16
|
+
const q = query.trim();
|
|
17
|
+
if (!q)
|
|
18
|
+
return false;
|
|
19
|
+
return QUESTION_START.test(q) || q.endsWith('?');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pathname for matching GEO rows: leading `/`, no trailing slash,
|
|
23
|
+
* percent-decoded, lowercased, `/` for root. GEO rows are stored through
|
|
24
|
+
* `normalizeRedirectPath`, which lowercases; both sides go through this
|
|
25
|
+
* function so the comparison stays symmetric.
|
|
26
|
+
*/
|
|
27
|
+
export function entityPathname(url, siteUrl) {
|
|
28
|
+
let pathname;
|
|
29
|
+
try {
|
|
30
|
+
pathname = new URL(url, siteUrl || 'https://localhost').pathname;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
pathname = url.startsWith('/') ? url : `/${url}`;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
pathname = decodeURIComponent(pathname);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* keep encoded */
|
|
40
|
+
}
|
|
41
|
+
const stripped = pathname.replace(/\/+$/, '').toLowerCase();
|
|
42
|
+
return stripped === '' ? '/' : stripped;
|
|
43
|
+
}
|
|
44
|
+
function questionRows(topQueries) {
|
|
45
|
+
if (!Array.isArray(topQueries))
|
|
46
|
+
return [];
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const raw of topQueries) {
|
|
49
|
+
if (!raw || typeof raw !== 'object')
|
|
50
|
+
continue;
|
|
51
|
+
const r = raw;
|
|
52
|
+
const query = typeof r.query === 'string' ? r.query.trim() : '';
|
|
53
|
+
const impressions = typeof r.impressions === 'number' ? r.impressions : 0;
|
|
54
|
+
const clicks = typeof r.clicks === 'number' ? r.clicks : 0;
|
|
55
|
+
if (!query || impressions < FAQ_QUERY_MIN_IMPRESSIONS || !isQuestionQuery(query))
|
|
56
|
+
continue;
|
|
57
|
+
out.push({ query: query.slice(0, FAQ_QUERY_MAX_LEN), impressions, clicks });
|
|
58
|
+
}
|
|
59
|
+
out.sort((a, b) => b.impressions - a.impressions);
|
|
60
|
+
return out.slice(0, FAQ_QUERY_MAX);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Pure detector — maps GEO daily rows and Search Console insight rows onto
|
|
64
|
+
* audited entities by pathname (or insight `entityId`). Entities without any
|
|
65
|
+
* signal are omitted from the result.
|
|
66
|
+
*/
|
|
67
|
+
export function detectFaqSignals(rows, entities, siteUrl) {
|
|
68
|
+
const byPath = new Map();
|
|
69
|
+
const byId = new Set();
|
|
70
|
+
for (const e of entities) {
|
|
71
|
+
byPath.set(entityPathname(e.url, siteUrl), e.entityId);
|
|
72
|
+
byId.add(e.entityId);
|
|
73
|
+
}
|
|
74
|
+
const out = {};
|
|
75
|
+
const ensure = (entityId) => (out[entityId] ??= { geo: null, queries: [] });
|
|
76
|
+
const geoOf = (entityId) => {
|
|
77
|
+
const s = ensure(entityId);
|
|
78
|
+
s.geo ??= { referralVisits: 0, retrievalHits: 0, engines: [] };
|
|
79
|
+
return s.geo;
|
|
80
|
+
};
|
|
81
|
+
for (const r of rows.referrals) {
|
|
82
|
+
if (r.path === GEO_OTHER_PATH)
|
|
83
|
+
continue;
|
|
84
|
+
const id = byPath.get(entityPathname(r.path, siteUrl));
|
|
85
|
+
if (!id)
|
|
86
|
+
continue;
|
|
87
|
+
const g = geoOf(id);
|
|
88
|
+
g.referralVisits += r.visits;
|
|
89
|
+
for (const eng of r.engines)
|
|
90
|
+
if (!g.engines.includes(eng))
|
|
91
|
+
g.engines.push(eng);
|
|
92
|
+
}
|
|
93
|
+
for (const c of rows.retrievalCrawls) {
|
|
94
|
+
if (c.path === GEO_OTHER_PATH)
|
|
95
|
+
continue;
|
|
96
|
+
const id = byPath.get(entityPathname(c.path, siteUrl));
|
|
97
|
+
if (!id)
|
|
98
|
+
continue;
|
|
99
|
+
geoOf(id).retrievalHits += c.hits;
|
|
100
|
+
}
|
|
101
|
+
for (const row of rows.insights) {
|
|
102
|
+
const id = (row.entityId && byId.has(row.entityId) ? row.entityId : null) ??
|
|
103
|
+
byPath.get(entityPathname(row.path, siteUrl));
|
|
104
|
+
if (!id)
|
|
105
|
+
continue;
|
|
106
|
+
const q = questionRows(row.topQueries);
|
|
107
|
+
if (q.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
const s = ensure(id);
|
|
110
|
+
s.queries = [...s.queries, ...q]
|
|
111
|
+
.sort((a, b) => b.impressions - a.impressions)
|
|
112
|
+
.slice(0, FAQ_QUERY_MAX);
|
|
113
|
+
}
|
|
114
|
+
for (const [id, s] of Object.entries(out)) {
|
|
115
|
+
if (s.geo)
|
|
116
|
+
s.geo.engines.sort();
|
|
117
|
+
if (!s.geo && s.queries.length === 0)
|
|
118
|
+
delete out[id];
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** DB wrapper — every read is optional; a missing table or a query error yields no signal. */
|
|
123
|
+
export async function gatherFaqSignals(db, entities, opts) {
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Prisma boundary
|
|
125
|
+
const d = db;
|
|
126
|
+
const now = opts.now ?? new Date();
|
|
127
|
+
const from = startOfDayUTC(new Date(now.getTime() - (FAQ_SIGNAL_WINDOW_DAYS - 1) * 86_400_000));
|
|
128
|
+
const rows = { referrals: [], retrievalCrawls: [], insights: [] };
|
|
129
|
+
if (opts.geoEnabled && hasModel(d, 'geoReferralDaily')) {
|
|
130
|
+
try {
|
|
131
|
+
const grouped = await d.geoReferralDaily.groupBy({
|
|
132
|
+
by: ['path', 'engine'],
|
|
133
|
+
where: { date: { gte: from } },
|
|
134
|
+
_sum: { visits: true },
|
|
135
|
+
});
|
|
136
|
+
const byPath = new Map();
|
|
137
|
+
for (const g of grouped) {
|
|
138
|
+
const cur = byPath.get(g.path) ?? { visits: 0, engines: [] };
|
|
139
|
+
cur.visits += g._sum.visits ?? 0;
|
|
140
|
+
if (!cur.engines.includes(g.engine))
|
|
141
|
+
cur.engines.push(g.engine);
|
|
142
|
+
byPath.set(g.path, cur);
|
|
143
|
+
}
|
|
144
|
+
rows.referrals = Array.from(byPath, ([path, v]) => ({ path, ...v }));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* optional */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (opts.geoEnabled && hasModel(d, 'geoCrawlDaily')) {
|
|
151
|
+
try {
|
|
152
|
+
const grouped = await d.geoCrawlDaily.groupBy({
|
|
153
|
+
by: ['path'],
|
|
154
|
+
where: { date: { gte: from }, category: 'retrieval' },
|
|
155
|
+
_sum: { hits: true },
|
|
156
|
+
});
|
|
157
|
+
rows.retrievalCrawls = grouped.map((g) => ({ path: g.path, hits: g._sum.hits ?? 0 }));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* optional */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (hasModel(d, 'searchPageInsight')) {
|
|
164
|
+
try {
|
|
165
|
+
rows.insights = await d.searchPageInsight.findMany({
|
|
166
|
+
select: { entityId: true, path: true, topQueries: true },
|
|
167
|
+
take: 5000,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
/* optional */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return detectFaqSignals(rows, entities, opts.siteUrl);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=faq-signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq-signals.js","sourceRoot":"","sources":["../../src/seo/faq-signals.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AiBotCategory } from './ai-bots.js';
|
|
2
|
+
export { GEO_RETENTION_DEFAULT_DAYS, GEO_RETENTION_MAX_DAYS, GEO_RETENTION_MIN_DAYS, clampGeoRetentionDays, } from './geo-retention.js';
|
|
3
|
+
/**
|
|
4
|
+
* Max distinct paths per `(date, bot)` — the rest of that bot's day rolls into
|
|
5
|
+
* {@link GEO_OTHER_PATH}, so a crawler (or an attacker replaying the public
|
|
6
|
+
* beacon) cannot grow the table by inventing paths.
|
|
7
|
+
*
|
|
8
|
+
* This is a **soft bound**: the distinct-path count is memoised per isolate
|
|
9
|
+
* for {@link GEO_CAP_MEMO_MS}, so N cold isolates (serverless) that each read
|
|
10
|
+
* a below-cap count in the same minute can together overshoot to ≈ N × cap
|
|
11
|
+
* rows for that `(date, actor)` before their memos expire. Bounded, not exact.
|
|
12
|
+
*/
|
|
13
|
+
export declare const GEO_CRAWL_PATH_CAP = 500;
|
|
14
|
+
/**
|
|
15
|
+
* Same cap and the same soft-bound caveat, keyed by `(date, engine)`. Referral
|
|
16
|
+
* beacons are as unauthenticated as crawl beacons, so "a path must exist on
|
|
17
|
+
* the site to be linked" is not a property the server can rely on.
|
|
18
|
+
*/
|
|
19
|
+
export declare const GEO_REFERRAL_PATH_CAP = 500;
|
|
20
|
+
export declare const GEO_OTHER_PATH = "(other)";
|
|
21
|
+
/** How long a `(date, actor)` distinct-path count is trusted before re-counting. */
|
|
22
|
+
export declare const GEO_CAP_MEMO_MS = 60000;
|
|
23
|
+
/** How long the kill-switch setting is trusted before re-reading it. */
|
|
24
|
+
export declare const GEO_SETTINGS_MEMO_MS = 30000;
|
|
25
|
+
/** Minimum gap between two storage-failure `console.warn`s per process. */
|
|
26
|
+
export declare const GEO_WARN_THROTTLE_MS = 60000;
|
|
27
|
+
export declare const GEO_MAX_PATH_LEN = 2048;
|
|
28
|
+
export declare const GEO_MAX_ACTOR_LEN = 64;
|
|
29
|
+
/** Largest `POST /geo/beacon` body accepted; a real payload is under 200 bytes. */
|
|
30
|
+
export declare const GEO_MAX_BEACON_BYTES = 4096;
|
|
31
|
+
export type GeoRecordOutcome = 'recorded' | 'other' | 'skipped';
|
|
32
|
+
/** Body of `POST /geo/beacon` — the only thing the middleware ever transmits. */
|
|
33
|
+
export interface GeoBeaconPayload {
|
|
34
|
+
kind: 'crawl' | 'referral';
|
|
35
|
+
/** Bot token (crawl) or engine name (referral). Validated against the server registry. */
|
|
36
|
+
actor: string;
|
|
37
|
+
/** Advisory only — the server re-derives it from the registry. */
|
|
38
|
+
category?: AiBotCategory;
|
|
39
|
+
/** `url.pathname` only. */
|
|
40
|
+
path: string;
|
|
41
|
+
}
|
|
42
|
+
/** UTC calendar day — the same convention as `SearchPerfDaily`. */
|
|
43
|
+
export declare function geoDay(now: Date): Date;
|
|
44
|
+
/**
|
|
45
|
+
* Site-relative path for storage: lower-cased, no query/hash, no trailing
|
|
46
|
+
* slash. Absolute URLs, over-long inputs and anything outside
|
|
47
|
+
* {@link GEO_PATH_CHARS} are rejected (`null` → the hit is dropped).
|
|
48
|
+
*/
|
|
49
|
+
export declare function normalizeGeoPath(input: string): string | null;
|
|
50
|
+
/** Test hook; also called after a successful settings write so it takes effect at once. */
|
|
51
|
+
export declare function resetGeoSettingsMemo(): void;
|
|
52
|
+
/**
|
|
53
|
+
* `SeoModuleSettings.geoMeasurementEnabled`, memoised for
|
|
54
|
+
* {@link GEO_SETTINGS_MEMO_MS}. Both writers consult it, so flipping the switch
|
|
55
|
+
* stops server-side recording as well as the beacon. `getSeoOverrides` never
|
|
56
|
+
* throws (it returns `null` on any read failure), which resolves to the
|
|
57
|
+
* default — measurement on.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isGeoMeasurementEnabled(db: unknown): Promise<boolean>;
|
|
60
|
+
/** Test hook. */
|
|
61
|
+
export declare function resetGeoWarnThrottle(): void;
|
|
62
|
+
/** Test hook. */
|
|
63
|
+
export declare function resetGeoCapMemo(): void;
|
|
64
|
+
export declare function recordGeoCrawl(db: unknown, input: {
|
|
65
|
+
bot: string;
|
|
66
|
+
category: AiBotCategory;
|
|
67
|
+
path: string;
|
|
68
|
+
now?: Date;
|
|
69
|
+
}): Promise<GeoRecordOutcome>;
|
|
70
|
+
export declare function recordGeoReferral(db: unknown, input: {
|
|
71
|
+
engine: string;
|
|
72
|
+
path: string;
|
|
73
|
+
now?: Date;
|
|
74
|
+
}): Promise<GeoRecordOutcome>;
|
|
75
|
+
//# sourceMappingURL=geo-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo-record.d.ts","sourceRoot":"","sources":["../../src/seo/geo-record.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAIjD,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAqB,CAAA;AACvD,eAAO,MAAM,cAAc,YAAY,CAAA;AACvC,oFAAoF;AACpF,eAAO,MAAM,eAAe,QAAS,CAAA;AACrC,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,QAAS,CAAA;AAC1C,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,QAAS,CAAA;AAE1C,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,KAAK,CAAA;AACnC,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,OAAO,CAAA;AAExC,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAA;AAE/D,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,GAAG,UAAU,CAAA;IAC1B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;CACb;AAgBD,mEAAmE;AACnE,wBAAgB,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAEtC;AAUD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ7D;AAMD,2FAA2F;AAC3F,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAM3E;AAMD,iBAAiB;AACjB,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAwBD,iBAAiB;AACjB,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AA6FD,wBAAsB,cAAc,CAClC,EAAE,EAAE,OAAO,EACX,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,GACxE,OAAO,CAAC,gBAAgB,CAAC,CAwB3B;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,OAAO,EACX,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,GAClD,OAAO,CAAC,gBAAgB,CAAC,CAuB3B"}
|