feat: SEO web audit, URL fetching, auto web search for SEO mode (v1.6.0)

This commit is contained in:
admin
2026-03-18 20:35:14 +00:00
Unverified
parent 2158d89314
commit 5f1bce4b99
16 changed files with 817 additions and 184 deletions

View File

@@ -361,7 +361,7 @@ function parseStreamingContent(text: string, currentAgent: string) {
// 3. Clean display text - hide all tag-like sequences and their partials
chatDisplay = text
// Hide complete tags (flexible brackets), including SUGGEST_AGENT
.replace(/\[+(?:AGENT|SUGGEST_AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV):?[\w-]*:?[\w-]*\]+/gi, "")
.replace(/\[+(?:AGENT|SUGGEST_AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV|WEB_AUDIT|WEB_SEARCH):?[\w-]*:?https?:\/\/[^\]]*\]+/gi, "").replace(/\[+(?:AGENT|SUGGEST_AGENT|content|seo|smm|pm|code|design|web|app|PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT|PREV|WEB_AUDIT|WEB_SEARCH):?[\w-]*:?[\w-]*\]+/gi, "")
// Hide content inside preview block (cleanly)
.replace(/\[+PREVIEW:[\w-]+:?[\w-]+?\]+[\s\S]*?(?:\[\/(?:PREVIEW|APP|WEB|SEO|CODE|DESIGN|SMM|PM|CONTENT)\]+|$)/gi, "")
// Hide closing tags
@@ -695,7 +695,66 @@ export default function AIAssist() {
setStatus(null);
}
const response = await modelAdapter.generateAIAssistStream(
// SEO mode: auto-fetch URLs from user input for live auditing
if (currentAgent === "seo") {
const urlMatches = [...finalInput.matchAll(/https?:\/\/[^\s<>"')\]]+/gi)].map(m => m[0]);
const uniqueUrls = [...new Set(urlMatches)].slice(0, 2);
if (uniqueUrls.length > 0) {
setStatus("Auditing website" + (uniqueUrls.length > 1 ? "s" : "") + "...");
try {
for (const url of uniqueUrls) {
const auditRes = await fetch("/api/fetch-url?url=" + encodeURIComponent(url));
if (auditRes.ok) {
const auditData = await auditRes.json();
enrichedInput += "\n\n[WEBSITE AUDIT DATA - " + url + "]\n";
enrichedInput += "Title: " + (auditData.title || "N/A") + "\n";
enrichedInput += "Meta Description: " + (auditData.metaDescription || "N/A") + "\n";
enrichedInput += "Meta Keywords: " + (auditData.metaKeywords || "N/A") + "\n";
enrichedInput += "Canonical: " + (auditData.canonical || "N/A") + "\n";
enrichedInput += "OG Title: " + (auditData.ogTitle || "N/A") + "\n";
enrichedInput += "OG Description: " + (auditData.ogDescription || "N/A") + "\n";
enrichedInput += "Headings:\n";
if (auditData.headings && auditData.headings.length > 0) {
for (const h of auditData.headings) {
enrichedInput += " H" + h.level + ": " + h.text + "\n";
}
} else {
enrichedInput += " None found\n";
}
const internalLinks = (auditData.links || []).filter((l: { internal: boolean }) => l.internal);
const externalLinks = (auditData.links || []).filter((l: { internal: boolean }) => !l.internal);
enrichedInput += "Links: " + internalLinks.length + " internal, " + externalLinks.length + " external (of " + (auditData.links || []).length + " total)\n";
const imagesWithAlt = (auditData.images || []).filter((img: { alt: string }) => img.alt && img.alt.trim());
const imagesNoAlt = (auditData.images || []).filter((img: { alt: string }) => !img.alt || !img.alt.trim());
enrichedInput += "Images: " + (auditData.images || []).length + " total, " + imagesWithAlt.length + " with alt, " + imagesNoAlt.length + " without alt\n";
enrichedInput += "Text content length: " + (auditData.text || "").length + " chars\n";
enrichedInput += "HTML size: " + (auditData.htmlLength || 0) + " chars\n";
enrichedInput += "[/WEBSITE AUDIT DATA]\n";
}
}
} catch (e) { console.warn("Website audit failed:", e); }
setStatus(null);
}
// If no URL found and web search not enabled, auto-enable web search for SEO
if (uniqueUrls.length === 0 && !webSearchEnabled) {
try {
setStatus("Searching for SEO context...");
const searchRes = await fetch("/api/search?q=" + encodeURIComponent(finalInput.split("\n")[0].substring(0, 200)));
if (searchRes.ok) {
const searchData = await searchRes.json();
if (searchData.results && searchData.results.length > 0) {
const searchContext = searchData.results.slice(0, 5).map((r: { title: string; url: string; snippet: string }, i: number) =>
(i + 1) + ". **" + r.title + "** (" + r.url + ") - " + r.snippet
).join("\n");
enrichedInput = "[WEB SEARCH CONTEXT - Top 5 relevant results]\n" + searchContext + "\n\n---\nUsing the above search results as reference context, answer the user query. Cite sources when relevant.\n\nUser query: " + finalInput;
}
}
} catch (e) { console.warn("SEO web search failed:", e); }
setStatus(null);
}
}
const response = await modelAdapter.generateAIAssistStream(
{
messages: [...formattedHistory, { role: "user" as const, content: enrichedInput, timestamp: new Date() }],
currentAgent,