feat(ai-moderation): rich context + link media vision analysis
- Conversation context recency gates (GAP_MS/MAX_AGE_MS): drop stale messages before silence gaps; cold_start anchor + flow descriptor tells LLM whether conversation is ongoing or restarted - [location] block: channel name, thread name, nsfw/age flags from captured metadata (thread names instead of bare IDs) - Link media -> multimodal: text-batch URL fetches that resolve to images now run vision analysis (bounded 15s) and switch prompt to mixed mode; <web_content> gains og:title for page context - pnpm-workspace.yaml: approve sharp build script (unblocks install)
This commit is contained in:
@@ -11,6 +11,8 @@ export interface FetchedUrlContext {
|
||||
data?: Buffer;
|
||||
mimeType?: string;
|
||||
textContent?: string;
|
||||
/** Page title from og:title / <title> — strong signal for the LLM. */
|
||||
title?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@@ -86,6 +88,50 @@ function extractOgImage(html: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface OgMeta {
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
siteName: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts OpenGraph / twitter meta + <title> from raw HTML. Both attribute
|
||||
* orders are accepted (<meta property=... content=...> and reversed).
|
||||
*/
|
||||
export function extractOgMeta(html: string): OgMeta {
|
||||
const metaValue = (name: string): string | null => {
|
||||
const re = new RegExp(
|
||||
`<meta[^>]*(?:property|name)=["']${name}["'][^>]*content=["']([^"']+)["']`,
|
||||
"i",
|
||||
);
|
||||
const m = html.match(re);
|
||||
if (m?.[1]) return m[1].replace(/&/g, "&").replace(/"/g, '"');
|
||||
const reRev = new RegExp(
|
||||
`<meta[^>]*content=["']([^"']+)["'][^>]*(?:property|name)=["']${name}["']`,
|
||||
"i",
|
||||
);
|
||||
const mRev = html.match(reRev);
|
||||
return mRev?.[1]
|
||||
? mRev[1].replace(/&/g, "&").replace(/"/g, '"')
|
||||
: null;
|
||||
};
|
||||
|
||||
const title =
|
||||
metaValue("og:title") ||
|
||||
metaValue("twitter:title") ||
|
||||
html.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim() ||
|
||||
null;
|
||||
const description =
|
||||
metaValue("og:description") ||
|
||||
metaValue("twitter:description") ||
|
||||
metaValue("description") ||
|
||||
null;
|
||||
const siteName =
|
||||
metaValue("og:site_name") || metaValue("application-name") || null;
|
||||
|
||||
return { title, description, siteName };
|
||||
}
|
||||
|
||||
function truncateAndCleanHtml(html: string, maxLen = 1000): string {
|
||||
// Strip <script> and <style> entirely
|
||||
let text = html.replace(
|
||||
@@ -176,6 +222,7 @@ export async function fetchUrlSafely(
|
||||
url,
|
||||
type: "text",
|
||||
textContent: cleaned,
|
||||
title: extractOgMeta(text).title ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user