@ainyc/canonry 1.31.0 → 1.33.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/assets/assets/{index-r6biprHB.css → index-B5Cg2H9M.css} +1 -1
- package/assets/assets/index-BiIPqZJb.js +246 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-YW4IZ34Z.js → chunk-WRNSBFNQ.js} +217 -49
- package/dist/cli.js +221 -114
- package/dist/index.js +1 -1
- package/package.json +6 -6
- package/assets/assets/index-DmFB_uXa.js +0 -246
package/assets/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-BiIPqZJb.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-B5Cg2H9M.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -784,6 +784,7 @@ var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "fai
|
|
|
784
784
|
var runKindSchema = z8.enum(["answer-visibility", "site-audit", "gsc-sync", "inspect-sitemap"]);
|
|
785
785
|
var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
|
|
786
786
|
var citationStateSchema = z8.enum(["cited", "not-cited"]);
|
|
787
|
+
var visibilityStateSchema = z8.enum(["visible", "not-visible"]);
|
|
787
788
|
var computedTransitionSchema = z8.enum(["new", "cited", "lost", "emerging", "not-cited"]);
|
|
788
789
|
var runDtoSchema = z8.object({
|
|
789
790
|
id: z8.string(),
|
|
@@ -808,11 +809,14 @@ var querySnapshotDtoSchema = z8.object({
|
|
|
808
809
|
keyword: z8.string().optional(),
|
|
809
810
|
provider: providerNameSchema,
|
|
810
811
|
citationState: citationStateSchema,
|
|
812
|
+
answerMentioned: z8.boolean().optional(),
|
|
813
|
+
visibilityState: visibilityStateSchema.optional(),
|
|
811
814
|
transition: computedTransitionSchema.optional(),
|
|
812
815
|
answerText: z8.string().nullable().optional(),
|
|
813
816
|
citedDomains: z8.array(z8.string()).default([]),
|
|
814
817
|
competitorOverlap: z8.array(z8.string()).default([]),
|
|
815
818
|
recommendedCompetitors: z8.array(z8.string()).default([]),
|
|
819
|
+
matchedTerms: z8.array(z8.string()).default([]),
|
|
816
820
|
groundingSources: z8.array(groundingSourceSchema).default([]),
|
|
817
821
|
searchQueries: z8.array(z8.string()).default([]),
|
|
818
822
|
model: z8.string().nullable().optional(),
|
|
@@ -1071,6 +1075,99 @@ var ga4TrafficSummaryDtoSchema = z11.object({
|
|
|
1071
1075
|
lastSyncedAt: z11.string().nullable()
|
|
1072
1076
|
});
|
|
1073
1077
|
|
|
1078
|
+
// ../contracts/src/answer-visibility.ts
|
|
1079
|
+
var GENERIC_TOKENS = /* @__PURE__ */ new Set([
|
|
1080
|
+
"agency",
|
|
1081
|
+
"app",
|
|
1082
|
+
"company",
|
|
1083
|
+
"corp",
|
|
1084
|
+
"group",
|
|
1085
|
+
"health",
|
|
1086
|
+
"inc",
|
|
1087
|
+
"llc",
|
|
1088
|
+
"online",
|
|
1089
|
+
"platform",
|
|
1090
|
+
"services",
|
|
1091
|
+
"site",
|
|
1092
|
+
"solutions",
|
|
1093
|
+
"software",
|
|
1094
|
+
"systems",
|
|
1095
|
+
"tech"
|
|
1096
|
+
]);
|
|
1097
|
+
function extractAnswerMentions(answerText, displayName, domains) {
|
|
1098
|
+
if (!answerText) return { mentioned: false, matchedTerms: [] };
|
|
1099
|
+
const matchedTerms = [];
|
|
1100
|
+
const lowerAnswer = answerText.toLowerCase();
|
|
1101
|
+
for (const domain of domains) {
|
|
1102
|
+
const normalizedDomain = normalizeProjectDomain(domain);
|
|
1103
|
+
if (!normalizedDomain || !normalizedDomain.includes(".")) continue;
|
|
1104
|
+
if (domainMentioned(lowerAnswer, normalizedDomain)) {
|
|
1105
|
+
matchedTerms.push(normalizedDomain);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const normalizedDisplayName = normalizeText(displayName);
|
|
1109
|
+
if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
|
|
1110
|
+
matchedTerms.push(displayName);
|
|
1111
|
+
}
|
|
1112
|
+
const tokens = collectDistinctiveTokens(displayName, domains);
|
|
1113
|
+
let tokenMatches = 0;
|
|
1114
|
+
const matchedTokens = [];
|
|
1115
|
+
for (const token of tokens) {
|
|
1116
|
+
if (new RegExp(`\\b${escapeRegExp(token)}\\b`).test(lowerAnswer)) {
|
|
1117
|
+
tokenMatches++;
|
|
1118
|
+
matchedTokens.push(token);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const tokenThresholdMet = tokens.length > 0 && (tokens.length === 1 && tokenMatches >= 1 || tokenMatches >= Math.min(2, tokens.length));
|
|
1122
|
+
if (tokenThresholdMet) {
|
|
1123
|
+
matchedTerms.push(...matchedTokens);
|
|
1124
|
+
}
|
|
1125
|
+
const unique = [...new Set(matchedTerms)];
|
|
1126
|
+
return { mentioned: unique.length > 0, matchedTerms: unique };
|
|
1127
|
+
}
|
|
1128
|
+
function determineAnswerMentioned(answerText, displayName, domains) {
|
|
1129
|
+
return extractAnswerMentions(answerText, displayName, domains).mentioned;
|
|
1130
|
+
}
|
|
1131
|
+
function visibilityStateFromAnswerMentioned(answerMentioned) {
|
|
1132
|
+
return answerMentioned ? "visible" : "not-visible";
|
|
1133
|
+
}
|
|
1134
|
+
function domainMentioned(lowerAnswer, normalizedDomain) {
|
|
1135
|
+
const escapedDomain = escapeRegExp(normalizedDomain.toLowerCase());
|
|
1136
|
+
const patterns = [
|
|
1137
|
+
new RegExp(`(^|[^a-z0-9-])${escapedDomain}($|[^a-z0-9-])`),
|
|
1138
|
+
new RegExp(`https?://(?:www\\.)?${escapedDomain}(?:[/:?#]|$)`),
|
|
1139
|
+
new RegExp(`www\\.${escapedDomain}(?:[/:?#]|$)`)
|
|
1140
|
+
];
|
|
1141
|
+
return patterns.some((pattern) => pattern.test(lowerAnswer));
|
|
1142
|
+
}
|
|
1143
|
+
function collectDistinctiveTokens(displayName, domains) {
|
|
1144
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1145
|
+
for (const token of extractDistinctiveTokens(displayName)) {
|
|
1146
|
+
tokens.add(token);
|
|
1147
|
+
}
|
|
1148
|
+
for (const domain of domains) {
|
|
1149
|
+
const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
|
|
1150
|
+
for (const label of hostname.split(".").filter(Boolean)) {
|
|
1151
|
+
const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
1152
|
+
if (isDistinctiveToken(token)) tokens.add(token);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return [...tokens];
|
|
1156
|
+
}
|
|
1157
|
+
function extractDistinctiveTokens(value) {
|
|
1158
|
+
return normalizeText(value).split(" ").filter(isDistinctiveToken);
|
|
1159
|
+
}
|
|
1160
|
+
function isDistinctiveToken(token) {
|
|
1161
|
+
if (token.length < 4) return false;
|
|
1162
|
+
return !GENERIC_TOKENS.has(token);
|
|
1163
|
+
}
|
|
1164
|
+
function normalizeText(value) {
|
|
1165
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
1166
|
+
}
|
|
1167
|
+
function escapeRegExp(value) {
|
|
1168
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1074
1171
|
// ../api-routes/src/auth.ts
|
|
1075
1172
|
import crypto2 from "crypto";
|
|
1076
1173
|
import { eq } from "drizzle-orm";
|
|
@@ -1165,6 +1262,7 @@ var querySnapshots = sqliteTable("query_snapshots", {
|
|
|
1165
1262
|
provider: text("provider").notNull().default("gemini"),
|
|
1166
1263
|
model: text("model"),
|
|
1167
1264
|
citationState: text("citation_state").notNull(),
|
|
1265
|
+
answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
|
|
1168
1266
|
answerText: text("answer_text"),
|
|
1169
1267
|
citedDomains: text("cited_domains").notNull().default("[]"),
|
|
1170
1268
|
competitorOverlap: text("competitor_overlap").notNull().default("[]"),
|
|
@@ -1731,7 +1829,9 @@ var MIGRATIONS = [
|
|
|
1731
1829
|
)`,
|
|
1732
1830
|
`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_project_date ON ga_ai_referrals(project_id, date)`,
|
|
1733
1831
|
`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_source ON ga_ai_referrals(source)`,
|
|
1734
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)
|
|
1832
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`,
|
|
1833
|
+
// v18: Answer-level visibility derived from answer text
|
|
1834
|
+
`ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER`
|
|
1735
1835
|
];
|
|
1736
1836
|
function migrate(db) {
|
|
1737
1837
|
const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -1840,6 +1940,33 @@ function writeAuditLog(db, entry) {
|
|
|
1840
1940
|
createdAt: now
|
|
1841
1941
|
}).run();
|
|
1842
1942
|
}
|
|
1943
|
+
function resolveSnapshotMentionResult(snapshot, project) {
|
|
1944
|
+
if (snapshot.answerText) {
|
|
1945
|
+
const domains = effectiveDomains({
|
|
1946
|
+
canonicalDomain: project.canonicalDomain,
|
|
1947
|
+
ownedDomains: normalizeOwnedDomains(project.ownedDomains)
|
|
1948
|
+
});
|
|
1949
|
+
return extractAnswerMentions(snapshot.answerText, project.displayName, domains);
|
|
1950
|
+
}
|
|
1951
|
+
if (typeof snapshot.answerMentioned === "boolean") {
|
|
1952
|
+
return { mentioned: snapshot.answerMentioned, matchedTerms: [] };
|
|
1953
|
+
}
|
|
1954
|
+
return { mentioned: false, matchedTerms: [] };
|
|
1955
|
+
}
|
|
1956
|
+
function resolveSnapshotAnswerMentioned(snapshot, project) {
|
|
1957
|
+
return resolveSnapshotMentionResult(snapshot, project).mentioned;
|
|
1958
|
+
}
|
|
1959
|
+
function resolveSnapshotVisibilityState(snapshot, project) {
|
|
1960
|
+
return visibilityStateFromAnswerMentioned(resolveSnapshotMentionResult(snapshot, project).mentioned);
|
|
1961
|
+
}
|
|
1962
|
+
function resolveSnapshotMatchedTerms(snapshot, project) {
|
|
1963
|
+
return resolveSnapshotMentionResult(snapshot, project).matchedTerms;
|
|
1964
|
+
}
|
|
1965
|
+
function normalizeOwnedDomains(value) {
|
|
1966
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
1967
|
+
const parsed = parseJsonColumn(typeof value === "string" ? value : null, []);
|
|
1968
|
+
return parsed.filter((item) => typeof item === "string");
|
|
1969
|
+
}
|
|
1843
1970
|
|
|
1844
1971
|
// ../api-routes/src/projects.ts
|
|
1845
1972
|
async function projectRoutes(app, opts) {
|
|
@@ -2498,6 +2625,11 @@ async function runRoutes(app, opts) {
|
|
|
2498
2625
|
app.get("/runs/:id", async (request, reply) => {
|
|
2499
2626
|
const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
|
|
2500
2627
|
if (!run) throw notFound("Run", request.params.id);
|
|
2628
|
+
const project = app.db.select({
|
|
2629
|
+
displayName: projects.displayName,
|
|
2630
|
+
canonicalDomain: projects.canonicalDomain,
|
|
2631
|
+
ownedDomains: projects.ownedDomains
|
|
2632
|
+
}).from(projects).where(eq7(projects.id, run.projectId)).get();
|
|
2501
2633
|
const snapshots = app.db.select({
|
|
2502
2634
|
id: querySnapshots.id,
|
|
2503
2635
|
runId: querySnapshots.runId,
|
|
@@ -2506,6 +2638,7 @@ async function runRoutes(app, opts) {
|
|
|
2506
2638
|
provider: querySnapshots.provider,
|
|
2507
2639
|
model: querySnapshots.model,
|
|
2508
2640
|
citationState: querySnapshots.citationState,
|
|
2641
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
2509
2642
|
answerText: querySnapshots.answerText,
|
|
2510
2643
|
citedDomains: querySnapshots.citedDomains,
|
|
2511
2644
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
@@ -2518,6 +2651,7 @@ async function runRoutes(app, opts) {
|
|
|
2518
2651
|
...formatRun(run),
|
|
2519
2652
|
snapshots: snapshots.map((s) => {
|
|
2520
2653
|
const rawParsed = parseSnapshotRawResponse(s.rawResponse);
|
|
2654
|
+
const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
|
|
2521
2655
|
return {
|
|
2522
2656
|
id: s.id,
|
|
2523
2657
|
runId: s.runId,
|
|
@@ -2525,10 +2659,13 @@ async function runRoutes(app, opts) {
|
|
|
2525
2659
|
keyword: s.keyword,
|
|
2526
2660
|
provider: s.provider,
|
|
2527
2661
|
citationState: s.citationState,
|
|
2662
|
+
answerMentioned,
|
|
2663
|
+
visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
|
|
2528
2664
|
answerText: s.answerText,
|
|
2529
2665
|
citedDomains: parseJsonColumn(s.citedDomains, []),
|
|
2530
2666
|
competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
|
|
2531
2667
|
recommendedCompetitors: parseJsonColumn(s.recommendedCompetitors, []),
|
|
2668
|
+
matchedTerms: project ? resolveSnapshotMatchedTerms(s, project) : [],
|
|
2532
2669
|
model: s.model ?? rawParsed.model,
|
|
2533
2670
|
location: s.location,
|
|
2534
2671
|
groundingSources: rawParsed.groundingSources,
|
|
@@ -3115,6 +3252,7 @@ async function historyRoutes(app) {
|
|
|
3115
3252
|
provider: querySnapshots.provider,
|
|
3116
3253
|
model: querySnapshots.model,
|
|
3117
3254
|
citationState: querySnapshots.citationState,
|
|
3255
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
3118
3256
|
answerText: querySnapshots.answerText,
|
|
3119
3257
|
citedDomains: querySnapshots.citedDomains,
|
|
3120
3258
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
@@ -3135,6 +3273,8 @@ async function historyRoutes(app) {
|
|
|
3135
3273
|
provider: s.provider,
|
|
3136
3274
|
model: s.model,
|
|
3137
3275
|
citationState: s.citationState,
|
|
3276
|
+
answerMentioned: resolveSnapshotAnswerMentioned(s, project),
|
|
3277
|
+
visibilityState: resolveSnapshotVisibilityState(s, project),
|
|
3138
3278
|
answerText: s.answerText,
|
|
3139
3279
|
citedDomains: parseJsonColumn(s.citedDomains, []),
|
|
3140
3280
|
competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
|
|
@@ -3155,12 +3295,16 @@ async function historyRoutes(app) {
|
|
|
3155
3295
|
const runIds = new Set(projectRuns.map((r) => r.id));
|
|
3156
3296
|
const rawSnapshots = app.db.select().from(querySnapshots).where(inArray(querySnapshots.runId, [...runIds])).all();
|
|
3157
3297
|
const timelineLocationFilter = request.query.location;
|
|
3158
|
-
const
|
|
3298
|
+
const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
|
|
3299
|
+
const allSnapshots = filteredSnapshots.map((snapshot) => ({
|
|
3300
|
+
...snapshot,
|
|
3301
|
+
answerMentioned: resolveSnapshotAnswerMentioned(snapshot, project)
|
|
3302
|
+
}));
|
|
3159
3303
|
const deduped = /* @__PURE__ */ new Map();
|
|
3160
3304
|
for (const snap of allSnapshots) {
|
|
3161
3305
|
const key = `${snap.runId}:${snap.keywordId}`;
|
|
3162
3306
|
const existing = deduped.get(key);
|
|
3163
|
-
if (!existing || snap.citationState === "cited") {
|
|
3307
|
+
if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState === "cited") {
|
|
3164
3308
|
deduped.set(key, snap);
|
|
3165
3309
|
}
|
|
3166
3310
|
}
|
|
@@ -3183,8 +3327,10 @@ async function historyRoutes(app) {
|
|
|
3183
3327
|
return snaps.map((snap, idx) => {
|
|
3184
3328
|
const run = projectRuns.find((r) => r.id === snap.runId);
|
|
3185
3329
|
let transition = snap.citationState === "cited" ? "cited" : "not-cited";
|
|
3330
|
+
let visibilityTransition = snap.answerMentioned ? "visible" : "not-visible";
|
|
3186
3331
|
if (idx === 0) {
|
|
3187
3332
|
transition = "new";
|
|
3333
|
+
visibilityTransition = "new";
|
|
3188
3334
|
} else {
|
|
3189
3335
|
const prev = snaps[idx - 1];
|
|
3190
3336
|
if (prev.citationState === "not-cited" && snap.citationState === "cited") {
|
|
@@ -3192,12 +3338,20 @@ async function historyRoutes(app) {
|
|
|
3192
3338
|
} else if (prev.citationState === "cited" && snap.citationState === "not-cited") {
|
|
3193
3339
|
transition = "lost";
|
|
3194
3340
|
}
|
|
3341
|
+
if (!prev.answerMentioned && snap.answerMentioned) {
|
|
3342
|
+
visibilityTransition = "emerging";
|
|
3343
|
+
} else if (prev.answerMentioned && !snap.answerMentioned) {
|
|
3344
|
+
visibilityTransition = "lost";
|
|
3345
|
+
}
|
|
3195
3346
|
}
|
|
3196
3347
|
return {
|
|
3197
3348
|
runId: snap.runId,
|
|
3198
3349
|
createdAt: run?.createdAt ?? snap.createdAt,
|
|
3199
3350
|
citationState: snap.citationState,
|
|
3200
|
-
transition
|
|
3351
|
+
transition,
|
|
3352
|
+
answerMentioned: snap.answerMentioned,
|
|
3353
|
+
visibilityState: snap.answerMentioned ? "visible" : "not-visible",
|
|
3354
|
+
visibilityTransition
|
|
3201
3355
|
};
|
|
3202
3356
|
});
|
|
3203
3357
|
}
|
|
@@ -3228,7 +3382,7 @@ async function historyRoutes(app) {
|
|
|
3228
3382
|
return reply.send(timeline);
|
|
3229
3383
|
});
|
|
3230
3384
|
app.get("/projects/:name/snapshots/diff", async (request, reply) => {
|
|
3231
|
-
resolveProject(app.db, request.params.name);
|
|
3385
|
+
const project = resolveProject(app.db, request.params.name);
|
|
3232
3386
|
const { run1, run2 } = request.query;
|
|
3233
3387
|
if (!run1 || !run2) {
|
|
3234
3388
|
throw validationError("Both run1 and run2 query params are required");
|
|
@@ -3236,22 +3390,40 @@ async function historyRoutes(app) {
|
|
|
3236
3390
|
const snaps1 = app.db.select({
|
|
3237
3391
|
keywordId: querySnapshots.keywordId,
|
|
3238
3392
|
keyword: keywords.keyword,
|
|
3239
|
-
citationState: querySnapshots.citationState
|
|
3393
|
+
citationState: querySnapshots.citationState,
|
|
3394
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
3395
|
+
answerText: querySnapshots.answerText
|
|
3240
3396
|
}).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run1)).all();
|
|
3241
3397
|
const snaps2 = app.db.select({
|
|
3242
3398
|
keywordId: querySnapshots.keywordId,
|
|
3243
3399
|
keyword: keywords.keyword,
|
|
3244
|
-
citationState: querySnapshots.citationState
|
|
3400
|
+
citationState: querySnapshots.citationState,
|
|
3401
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
3402
|
+
answerText: querySnapshots.answerText
|
|
3245
3403
|
}).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run2)).all();
|
|
3246
3404
|
const map1 = /* @__PURE__ */ new Map();
|
|
3247
3405
|
for (const s of snaps1) {
|
|
3406
|
+
const resolved = {
|
|
3407
|
+
...s,
|
|
3408
|
+
resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
|
|
3409
|
+
resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
|
|
3410
|
+
};
|
|
3248
3411
|
const existing = map1.get(s.keywordId);
|
|
3249
|
-
if (!existing ||
|
|
3412
|
+
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
|
|
3413
|
+
map1.set(s.keywordId, resolved);
|
|
3414
|
+
}
|
|
3250
3415
|
}
|
|
3251
3416
|
const map2 = /* @__PURE__ */ new Map();
|
|
3252
3417
|
for (const s of snaps2) {
|
|
3418
|
+
const resolved = {
|
|
3419
|
+
...s,
|
|
3420
|
+
resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
|
|
3421
|
+
resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
|
|
3422
|
+
};
|
|
3253
3423
|
const existing = map2.get(s.keywordId);
|
|
3254
|
-
if (!existing ||
|
|
3424
|
+
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
|
|
3425
|
+
map2.set(s.keywordId, resolved);
|
|
3426
|
+
}
|
|
3255
3427
|
}
|
|
3256
3428
|
const allKeywordIds = /* @__PURE__ */ new Set([...map1.keys(), ...map2.keys()]);
|
|
3257
3429
|
const diff = [...allKeywordIds].map((kwId) => {
|
|
@@ -3262,7 +3434,12 @@ async function historyRoutes(app) {
|
|
|
3262
3434
|
keyword: s2?.keyword ?? s1?.keyword ?? null,
|
|
3263
3435
|
run1State: s1?.citationState ?? null,
|
|
3264
3436
|
run2State: s2?.citationState ?? null,
|
|
3265
|
-
|
|
3437
|
+
run1AnswerMentioned: s1?.resolvedAnswerMentioned ?? null,
|
|
3438
|
+
run2AnswerMentioned: s2?.resolvedAnswerMentioned ?? null,
|
|
3439
|
+
run1VisibilityState: s1?.resolvedVisibilityState ?? null,
|
|
3440
|
+
run2VisibilityState: s2?.resolvedVisibilityState ?? null,
|
|
3441
|
+
changed: (s1?.citationState ?? null) !== (s2?.citationState ?? null),
|
|
3442
|
+
visibilityChanged: (s1?.resolvedAnswerMentioned ?? null) !== (s2?.resolvedAnswerMentioned ?? null)
|
|
3266
3443
|
};
|
|
3267
3444
|
});
|
|
3268
3445
|
return reply.send({ run1, run2, diff });
|
|
@@ -8919,12 +9096,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
|
|
|
8919
9096
|
var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
|
|
8920
9097
|
function stripCanonrySchema(content) {
|
|
8921
9098
|
const regex = new RegExp(
|
|
8922
|
-
`${
|
|
9099
|
+
`${escapeRegExp2(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp2(CANONRY_SCHEMA_END)}`,
|
|
8923
9100
|
"g"
|
|
8924
9101
|
);
|
|
8925
9102
|
return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
8926
9103
|
}
|
|
8927
|
-
function
|
|
9104
|
+
function escapeRegExp2(str) {
|
|
8928
9105
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8929
9106
|
}
|
|
8930
9107
|
function injectCanonrySchema(content, schemas) {
|
|
@@ -9031,7 +9208,7 @@ async function getSchemaStatus(connection, env) {
|
|
|
9031
9208
|
const thirdPartySchemas = [];
|
|
9032
9209
|
if (hasCanonryMarker) {
|
|
9033
9210
|
const markerRegex = new RegExp(
|
|
9034
|
-
`${
|
|
9211
|
+
`${escapeRegExp2(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp2(CANONRY_SCHEMA_END)}`
|
|
9035
9212
|
);
|
|
9036
9213
|
const match = markerRegex.exec(rawContent);
|
|
9037
9214
|
if (match?.[1]) {
|
|
@@ -11124,7 +11301,7 @@ var CDPConnectionManager = class {
|
|
|
11124
11301
|
await sleep(1500);
|
|
11125
11302
|
} catch (err) {
|
|
11126
11303
|
throw new CDPProviderError(
|
|
11127
|
-
"
|
|
11304
|
+
"CDP_TARGET_SELECTOR_FAILED",
|
|
11128
11305
|
`Failed to navigate to ${target.newConversationUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
11129
11306
|
);
|
|
11130
11307
|
}
|
|
@@ -11613,10 +11790,11 @@ async function healthcheck5(config) {
|
|
|
11613
11790
|
async function executeTrackedQuery5(input) {
|
|
11614
11791
|
const model = input.config.model ?? DEFAULT_MODEL5;
|
|
11615
11792
|
const client = new OpenAI3({ apiKey: input.config.apiKey, baseURL: BASE_URL });
|
|
11793
|
+
const prompt = buildPrompt4(input.keyword, input.location);
|
|
11616
11794
|
const response = await client.chat.completions.create({
|
|
11617
11795
|
model,
|
|
11618
11796
|
messages: [
|
|
11619
|
-
{ role: "user", content:
|
|
11797
|
+
{ role: "user", content: prompt }
|
|
11620
11798
|
]
|
|
11621
11799
|
});
|
|
11622
11800
|
const rawResponse = responseToRecord4(response);
|
|
@@ -11644,6 +11822,12 @@ function normalizeResult6(raw) {
|
|
|
11644
11822
|
searchQueries: raw.searchQueries
|
|
11645
11823
|
};
|
|
11646
11824
|
}
|
|
11825
|
+
function buildPrompt4(keyword, location) {
|
|
11826
|
+
if (location) {
|
|
11827
|
+
return `${keyword} (searching from ${location.city}, ${location.region}, ${location.country})`;
|
|
11828
|
+
}
|
|
11829
|
+
return keyword;
|
|
11830
|
+
}
|
|
11647
11831
|
function extractCitations(rawResponse) {
|
|
11648
11832
|
if (Array.isArray(rawResponse.citations)) {
|
|
11649
11833
|
return rawResponse.citations.filter((c) => typeof c === "string");
|
|
@@ -12176,6 +12360,11 @@ var JobRunner = class {
|
|
|
12176
12360
|
const normalized = adapter.normalizeResult(raw);
|
|
12177
12361
|
log.info("query.result", { runId, provider: providerName, keyword: kw.keyword, citedDomains: normalized.citedDomains, groundingSources: normalized.groundingSources.map((s) => s.uri), matchDomains: allDomains });
|
|
12178
12362
|
const citationState = determineCitationState(normalized, allDomains);
|
|
12363
|
+
const answerMentioned = determineAnswerMentioned(
|
|
12364
|
+
normalized.answerText,
|
|
12365
|
+
project.displayName,
|
|
12366
|
+
allDomains
|
|
12367
|
+
);
|
|
12179
12368
|
const overlap = computeCompetitorOverlap(normalized, competitorDomains);
|
|
12180
12369
|
const extractedCompetitors = extractRecommendedCompetitors(
|
|
12181
12370
|
normalized.answerText,
|
|
@@ -12198,6 +12387,7 @@ var JobRunner = class {
|
|
|
12198
12387
|
provider: providerName,
|
|
12199
12388
|
model: raw.model,
|
|
12200
12389
|
citationState,
|
|
12390
|
+
answerMentioned,
|
|
12201
12391
|
answerText: normalized.answerText,
|
|
12202
12392
|
citedDomains: JSON.stringify(normalized.citedDomains),
|
|
12203
12393
|
competitorOverlap: JSON.stringify(overlap),
|
|
@@ -12220,6 +12410,7 @@ var JobRunner = class {
|
|
|
12220
12410
|
provider: providerName,
|
|
12221
12411
|
model: raw.model,
|
|
12222
12412
|
citationState,
|
|
12413
|
+
answerMentioned,
|
|
12223
12414
|
answerText: normalized.answerText,
|
|
12224
12415
|
citedDomains: JSON.stringify(normalized.citedDomains),
|
|
12225
12416
|
competitorOverlap: JSON.stringify(overlap),
|
|
@@ -12235,7 +12426,7 @@ var JobRunner = class {
|
|
|
12235
12426
|
}).run();
|
|
12236
12427
|
}
|
|
12237
12428
|
totalSnapshotsInserted++;
|
|
12238
|
-
log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState });
|
|
12429
|
+
log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState, answerMentioned });
|
|
12239
12430
|
});
|
|
12240
12431
|
} catch (err) {
|
|
12241
12432
|
if (err instanceof RunCancelledError) {
|
|
@@ -13561,11 +13752,12 @@ var SnapshotService = class {
|
|
|
13561
13752
|
manualCompetitors: ctx.manualCompetitors,
|
|
13562
13753
|
targetDomain: ctx.domain
|
|
13563
13754
|
});
|
|
13755
|
+
const answerVisibilityDomains = [ctx.domain];
|
|
13564
13756
|
return {
|
|
13565
13757
|
provider: provider.adapter.name,
|
|
13566
13758
|
displayName: provider.adapter.displayName,
|
|
13567
13759
|
model: raw.model,
|
|
13568
|
-
mentioned:
|
|
13760
|
+
mentioned: determineAnswerMentioned(normalized.answerText, ctx.companyName, answerVisibilityDomains),
|
|
13569
13761
|
cited: citesTargetDomain(normalized.citedDomains, normalized.groundingSources, ctx.domain),
|
|
13570
13762
|
describedAccurately: "unknown",
|
|
13571
13763
|
accuracyNotes: null,
|
|
@@ -13822,26 +14014,6 @@ function buildFallbackRecommendedActions(audit) {
|
|
|
13822
14014
|
];
|
|
13823
14015
|
return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
|
|
13824
14016
|
}
|
|
13825
|
-
function mentionsTargetCompany(answerText, companyName, domain) {
|
|
13826
|
-
const haystack = normalizeText(answerText);
|
|
13827
|
-
if (!haystack) return false;
|
|
13828
|
-
const fullName = normalizeText(companyName);
|
|
13829
|
-
if (fullName && haystack.includes(fullName)) {
|
|
13830
|
-
return true;
|
|
13831
|
-
}
|
|
13832
|
-
const targetTokens = uniqueStrings([
|
|
13833
|
-
...extractDistinctiveTokens(companyName),
|
|
13834
|
-
...extractDistinctiveTokens(extractHostname2(domain).split(".")[0] ?? "")
|
|
13835
|
-
]);
|
|
13836
|
-
if (targetTokens.length === 0) return false;
|
|
13837
|
-
let matches = 0;
|
|
13838
|
-
for (const token of targetTokens) {
|
|
13839
|
-
if (new RegExp(`\\b${escapeRegExp2(token)}\\b`, "i").test(answerText)) {
|
|
13840
|
-
matches++;
|
|
13841
|
-
}
|
|
13842
|
-
}
|
|
13843
|
-
return matches >= Math.min(2, targetTokens.length) || matches >= 1 && targetTokens.length === 1;
|
|
13844
|
-
}
|
|
13845
14017
|
function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
|
|
13846
14018
|
const normalizedTarget = extractHostname2(targetDomain);
|
|
13847
14019
|
for (const domain of citedDomains) {
|
|
@@ -13912,9 +14084,6 @@ function parseJsonObject(input) {
|
|
|
13912
14084
|
const json = start >= 0 && end >= start ? candidate.slice(start, end + 1) : candidate;
|
|
13913
14085
|
return JSON.parse(json);
|
|
13914
14086
|
}
|
|
13915
|
-
function normalizeText(value) {
|
|
13916
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
13917
|
-
}
|
|
13918
14087
|
function normalizeStringList(values) {
|
|
13919
14088
|
const items = values.flatMap((value) => value.split(","));
|
|
13920
14089
|
return uniqueStrings(
|
|
@@ -13945,9 +14114,6 @@ function domainMatches2(candidate, target) {
|
|
|
13945
14114
|
const normalizedTarget = normalizeDomain(target);
|
|
13946
14115
|
return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
|
|
13947
14116
|
}
|
|
13948
|
-
function extractDistinctiveTokens(value) {
|
|
13949
|
-
return normalizeText(value).split(" ").filter((token) => token.length >= 4).filter((token) => !["llc", "inc", "corp", "company", "group", "services", "solutions", "agency"].includes(token));
|
|
13950
|
-
}
|
|
13951
14117
|
function isDomainLike(value) {
|
|
13952
14118
|
const normalized = normalizeDomain(value);
|
|
13953
14119
|
return normalized.includes(".") && !normalized.includes(" ");
|
|
@@ -13956,9 +14122,6 @@ function clipText(value, length) {
|
|
|
13956
14122
|
if (value.length <= length) return value;
|
|
13957
14123
|
return `${value.slice(0, length - 3)}...`;
|
|
13958
14124
|
}
|
|
13959
|
-
function escapeRegExp2(value) {
|
|
13960
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13961
|
-
}
|
|
13962
14125
|
|
|
13963
14126
|
// src/server.ts
|
|
13964
14127
|
var _require2 = createRequire2(import.meta.url);
|
|
@@ -14725,14 +14888,19 @@ export {
|
|
|
14725
14888
|
isFirstRun,
|
|
14726
14889
|
showFirstRunNotice,
|
|
14727
14890
|
trackEvent,
|
|
14891
|
+
projects,
|
|
14892
|
+
runs,
|
|
14893
|
+
querySnapshots,
|
|
14894
|
+
apiKeys,
|
|
14895
|
+
createClient,
|
|
14896
|
+
parseJsonColumn,
|
|
14897
|
+
migrate,
|
|
14728
14898
|
providerQuotaPolicySchema,
|
|
14729
14899
|
resolveProviderInput,
|
|
14730
14900
|
notificationEventSchema,
|
|
14731
14901
|
effectiveDomains,
|
|
14902
|
+
determineAnswerMentioned,
|
|
14732
14903
|
setGoogleAuthConfig,
|
|
14733
14904
|
formatAuditFactorScore,
|
|
14734
|
-
apiKeys,
|
|
14735
|
-
createClient,
|
|
14736
|
-
migrate,
|
|
14737
14905
|
createServer
|
|
14738
14906
|
};
|