feat: SEO web audit, URL fetching, auto web search for SEO mode (v1.6.0)
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user