Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/lib/ai-alert-triage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,14 @@ export async function triageChange(params: {
].join("\n");

try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
const { text } = await generateText({
model: createLanguageModel(config),
system: TRIAGE_SYSTEM_PROMPT,
prompt: userMessage,
maxOutputTokens: MAX_OUTPUT_TOKENS,
temperature: 0,
abortSignal: controller.signal,
abortSignal: AbortSignal.timeout(LLM_TIMEOUT_MS),
});
clearTimeout(timeout);
return parseTriageDecision(text);
} catch (err) {
// Fail open: a triage failure must never hide a real change.
Expand Down
5 changes: 1 addition & 4 deletions src/lib/ai-change-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,14 @@ export async function summarizeChange(params: {
].join("\n");

try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
const { text } = await generateText({
model: createLanguageModel(config),
system: SYSTEM_PROMPT,
prompt: userMessage,
maxOutputTokens: MAX_OUTPUT_TOKENS,
temperature: 0.2,
abortSignal: controller.signal,
abortSignal: AbortSignal.timeout(LLM_TIMEOUT_MS),
});
clearTimeout(timeout);
const summary = text.trim();
return summary || null;
} catch (err) {
Expand Down
33 changes: 26 additions & 7 deletions src/lib/scraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,14 @@ async function handleContentTarget(
if (!target.pageUrl) return;
const page = target.pageUrl;
const curr = await ensureMarkdown(page, cache);
await ensurePageScreenshot(page, cache);
const prev = await latestSnapshot(website.id, "MARKDOWN", page);
// The screenshot only backs the "current version" thumbnail, not change
// detection (that's the markdown hash). Capture it on the first snapshot and
// whenever the content actually changes; an unchanged page keeps its last
// screenshot, so we don't spend a context.dev capture on every quiet check.
if (!prev || prev.hash !== curr.hash) {
await ensurePageScreenshot(page, cache);
}
if (prev && prev.hash !== curr.hash) {
const { preview, totalAdded, totalRemoved } = diffPreview(prev.payload, curr.md);
alerts.push({
Expand Down Expand Up @@ -234,7 +240,6 @@ async function handleProductPriceTarget(
if (!target.pageUrl) return;
const page = target.pageUrl;
const result = await extractProduct(page, { apiKey: cache.contextApiKey });
await ensurePageScreenshot(page, cache);
const snap: ProductSnapshotData = {
is_product_page: result.is_product_page,
platform: result.platform ?? null,
Expand All @@ -247,11 +252,25 @@ async function handleProductPriceTarget(
cache.product.set(page, value);

const prev = await latestSnapshot(website.id, "PRODUCT", page);
if (!prev) return;
if (!snap.is_product_page) return;
const prevData = JSON.parse(prev.payload) as ProductSnapshotData;
if (!prevData.is_product_page) return;
if (priceChangeKey(prevData) === priceChangeKey(snap)) return;
const prevData = prev ? (JSON.parse(prev.payload) as ProductSnapshotData) : null;
const priceChanged =
prevData !== null &&
snap.is_product_page &&
Comment on lines 254 to +258

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — done. The product screenshot now keys off the snapshot hash (prev.hash !== value.hash) so it refreshes on any payload change (name/platform/availability), matching the content handler, while alert emission stays gated on priceChanged. Pushed.

prevData.is_product_page &&
priceChangeKey(prevData) !== priceChangeKey(snap);

// The screenshot only backs the "current version" thumbnail. Refresh it on the
// first snapshot and whenever the extracted product payload changes at all —
// keyed off the snapshot hash, mirroring the content handler — so the thumbnail
// can't go stale while the payload moves (name/platform/availability) even when
// the price is unchanged. An identical check keeps the last screenshot, so a
// quiet page doesn't spend a context.dev capture. Alert emission stays gated on
// an actual price change below.
if (!prev || prev.hash !== value.hash) {
await ensurePageScreenshot(page, cache);
}

if (!priceChanged || !prevData) return;

const name = snap.productName || page;
const fmt = (p: number | null, c: string | null) =>
Expand Down