feat: SEO audit export, default Vibe Architect, /vibe route (v2.0.0)

This commit is contained in:
admin
2026-03-18 21:34:13 +00:00
Unverified
parent 0e22495ff7
commit fcb3c04bbc
5 changed files with 300 additions and 4 deletions

View File

@@ -3,7 +3,24 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and this project adheres to [Semantic Versioning]
## [2.0.0] - 2026-03-18 21:33 UTC
### Added
- **SEO Audit Export** — Export SEO/GEO audit reports as HTML or PDF with comprehensive fix instructions
- **SEO Report Generator** — Standalone `lib/seo-report.ts` utility with color-coded scores, issue tables with fix instructions, and print-friendly CSS
- **Default Vibe Architect** — Welcome screen now opens to Vibe Architect by default (was Prompt Enhancer)
- **Vibe Architect Dedicated Route** — Full-screen immersive mode at `/vibe` with `vibeMode` prop
### Changed
- **General Agent Plain Chat** — General mode no longer triggers plan/code flow, now works as plain chat
- **SEO Follow-up Fix** — Non-visual agents (SEO, content, SMM) preserve existing canvas on follow-up messages
### Technical Details
- Files modified: 3 (AIAssist.tsx, page.tsx)
- Files added: 2 (seo-report.ts, vibe/page.tsx)
(https://semver.org/spec/v2.0.0.html).
## [1.9.0] - 2026-03-18 21:05 UTC

View File

@@ -1,6 +1,6 @@
# PromptArch: AI Orchestration Platform
> **Latest Version**: [v1.9.0](CHANGELOG.md#190---2026-03-18) (2026-03-18)
> **Latest Version**: [v2.0.0](CHANGELOG.md#2.0.0---2026-03-18) (2026-03-18)(CHANGELOG.md#190---2026-03-18) (2026-03-18)
> **Development Note**: This entire platform was developed exclusively using [TRAE.AI IDE](https://trae.ai) powered by elite [GLM 4.7 model](https://z.ai/subscribe?ic=R0K78RJKNW).
> **Learn more about this architecture [here](https://z.ai/subscribe?ic=R0K78RJKNW).**
@@ -143,6 +143,7 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
| Version | Date | Highlights |
|---------|------|------------|
| [2.0.0](CHANGELOG.md#2.0.0---2026-03-18) | SEO Export, Default Vibe, /vibe Route, General Chat | 2026-03-18 |
| [1.9.0](CHANGELOG.md#190---2026-03-18) | 2026-03-18 21:05 UTC | Vibe Architect rebrand, sidebar highlight |
| [1.8.0](CHANGELOG.md#180---2026-03-18) | 2026-03-18 21:02 UTC | Vibe Architect dedicated mode, SEO follow-up fix |
| [1.7.0](CHANGELOG.md#170---2026-03-18) | 2026-03-18 20:44 UTC | Industry-grade SEO audit, plan flow fix for non-code agents |

View File

@@ -20,7 +20,7 @@ const HistoryPanel = dynamic(() => import("@/components/HistoryPanel"), { ssr: f
const SettingsPanel = dynamic(() => import("@/components/SettingsPanel"), { ssr: false });
export default function Home() {
const [currentView, setCurrentView] = useState<View>("enhance");
const [currentView, setCurrentView] = useState<View>("ai-assist");
useEffect(() => {
console.log("[Home] Initializing Qwen OAuth service on client...");

View File

@@ -1,10 +1,11 @@
"use client";
import React, { useState, useEffect, useRef, memo } from "react";
import { downloadSeoReport } from "@/lib/seo-report";
import {
MessageSquare, Send, Code2, Palette, Search,
Trash2, Copy, Monitor, StopCircle, X, Zap, Ghost,
Wand2, LayoutPanelLeft, Play, Orbit, Plus, Key, ShieldCheck, Wrench
Wand2, LayoutPanelLeft, Play, Orbit, Plus, Key, ShieldCheck, Wrench, FileText
} from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
@@ -520,6 +521,7 @@ export default function AIAssist({ vibeMode = false }: { vibeMode?: boolean } =
const [deviceSize, setDeviceSize] = useState<"full" | "desktop" | "tablet" | "mobile">("full");
const deviceWidths: Record<string, string> = { full: "100%", desktop: "1280px", tablet: "768px", mobile: "375px" };
const [isModifying, setIsModifying] = useState(false);
const [seoAuditData, setSeoAuditData] = useState<any>(null);
const [abortController, setAbortController] = useState<AbortController | null>(null);
// Agent suggestion state
@@ -711,6 +713,7 @@ export default function AIAssist({ vibeMode = false }: { vibeMode?: boolean } =
const auditRes = await fetch("/api/fetch-url?url=" + encodeURIComponent(url));
if (auditRes.ok) {
const d = await auditRes.json();
setSeoAuditData(d);
enrichedInput += "\n\n[COMPREHENSIVE SEO AUDIT - " + url + "]\n";
enrichedInput += "== OVERALL SCORE: " + (d.scores?.overall || "?") + "/100 ==\n";
enrichedInput += "Technical: " + (d.scores?.technical || "?") + " | Content: " + (d.scores?.content || "?") + " | Performance: " + (d.scores?.performance || "?") + " | Social: " + (d.scores?.social || "?") + "\n\n";
@@ -911,6 +914,11 @@ export default function AIAssist({ vibeMode = false }: { vibeMode?: boolean } =
setIsProcessing(false);
}
};
const exportSeoReport = (format: "html" | "pdf") => {
if (!seoAuditData) return;
downloadSeoReport(seoAuditData, format);
};
const clearHistory = () => {
updateActiveTab({
@@ -1338,6 +1346,7 @@ export default function AIAssist({ vibeMode = false }: { vibeMode?: boolean } =
{/* Post-coding action buttons */}
{msg.role === "assistant" && assistStep === "preview" && i === aiAssistHistory.length - 1 && !isProcessing && (
<>
<div className="mt-4 grid grid-cols-3 gap-2 animate-in zoom-in-95 duration-300">
<Button
onClick={() => { setShowCanvas(true); setViewMode(isPreviewRenderable(previewData as PreviewData) ? "preview" : "code"); }}
@@ -1361,6 +1370,25 @@ export default function AIAssist({ vibeMode = false }: { vibeMode?: boolean } =
<LayoutPanelLeft className="h-3.5 w-3.5 mr-1" /> <span className="truncate">Modify</span>
</Button>
</div>
{currentAgent === "seo" && seoAuditData && (
<div className="mt-2 flex gap-2 animate-in zoom-in-95 duration-300">
<Button
onClick={() => exportSeoReport("html")}
variant="outline"
className="flex-1 bg-emerald-500/10 hover:bg-emerald-500/20 border-emerald-500/20 text-emerald-300 font-black uppercase text-[9px] tracking-wider py-3 rounded-xl min-w-0"
>
<Download className="h-3.5 w-3.5 mr-1" /> <span className="truncate">Export HTML</span>
</Button>
<Button
onClick={() => exportSeoReport("pdf")}
variant="outline"
className="flex-1 bg-amber-500/10 hover:bg-amber-500/20 border-amber-500/20 text-amber-300 font-black uppercase text-[9px] tracking-wider py-3 rounded-xl min-w-0"
>
<FileText className="h-3.5 w-3.5 mr-1" /> <span className="truncate">Export PDF</span>
</Button>
</div>
)}
</>
)}
</div>

250
lib/seo-report.ts Normal file
View File

@@ -0,0 +1,250 @@
/**
* SEO Audit Report Generator
* Generates standalone HTML reports from audit data
*/
export interface SeoAuditData {
url: string;
domain: string;
responseTime: number;
server: string;
htmlSize: number;
title: string | null;
titleLength: number;
titleStatus: string;
metaDescription: string | null;
descLength: number;
descStatus: string;
metaKeywords: string | null;
viewport: string | null;
charset: string | null;
robotsDirectives: string | null;
canonical: string | null;
hasCanonicalMismatch: boolean;
h1Count: number;
h2Count: number;
h3Count: number;
h4Count: number;
headingStatus: string;
headings: { level: number; text: string }[];
links?: { total: number; internal: number; external: number; nofollow: number };
images?: { total: number; withAlt: number; withoutAlt: number; lazyLoaded: number; altCoverage: number };
content?: { wordCount: number; sentenceCount: number; paragraphCount: number; avgWordsPerSentence: number };
openGraph?: { title: string | null; description: string | null; image: string | null; type: string | null };
twitterCard?: { card: string | null };
performance?: {
inlineStyles: number;
externalScripts: number;
externalStylesheets: number;
hasPreconnect: boolean;
hasPreload: boolean;
hasDnsPrefetch: boolean;
usesAsyncScripts: boolean;
usesDeferScripts: boolean;
};
accessibility?: { hasLangAttr: boolean; hasAriaLabels: boolean; hasAltOnFirstImage: boolean };
structuredData?: { hasJsonLd: boolean; hasMicrodata: boolean; types: { type: string; found: boolean }[] };
scores?: { overall: number; technical: number; content: number; performance: number; social: number };
issues?: { severity: string; category: string; message: string }[];
}
const scoreColor = (s: number): string =>
s >= 80 ? "#22c55e" : s >= 60 ? "#f59e0b" : "#ef4444";
const sevColor = (s: string): string =>
s === "critical" ? "#ef4444" : s === "warning" ? "#f59e0b" : "#6b7280";
const fixInstructions: Record<string, string> = {
Meta: "Ensure every page has a unique title tag (50-60 chars) with primary keyword near start. Write meta descriptions (150-160 chars) with CTA. Add viewport meta tag.",
Content: "Maintain exactly one H1 per page with primary keyword. Build logical heading hierarchy (H1>H2>H3). Aim for 1000+ words on core pages.",
Technical: "Set self-referencing canonical tags. Ensure valid SSL with HTTPS redirect. Check robots.txt is not blocking important pages.",
Mobile: "Add viewport meta tag. Test with Google Mobile-Friendly tool. Ensure tap targets are 48x48px minimum.",
Security: "Migrate to HTTPS with valid SSL. Set up HTTP-to-HTTPS redirects. Configure HSTS headers.",
Performance: "Minimize inline styles. Add preconnect hints. Implement lazy loading. Use async/defer for scripts. Compress images.",
Social: "Add OG tags (og:title, og:description, og:image). Add Twitter Card meta. Ensure OG images are 1200x630px+.",
Accessibility: "Add lang attribute to <html>. Implement ARIA labels. Add descriptive alt text to all images.",
Links: "Fix broken internal links. Add external links to authoritative sources. Review nofollow attributes.",
"Structured Data": "Implement JSON-LD schema. Validate with Google Rich Results Test. Add FAQ/Article/Product schema.",
};
export function generateSeoReportHtml(d: SeoAuditData): string {
const now = new Date().toLocaleString();
const sc = scoreColor;
const svc = sevColor;
const issueRows = (d.issues || [])
.map(
(issue) =>
"<tr><td><span style=\"display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;color:white;background:" +
svc(issue.severity) +
"\">" +
issue.severity.toUpperCase() +
"</span></td><td style=\"font-weight:600\">" +
issue.category +
"</td><td>" +
issue.message +
"</td><td style=\"white-space:pre-wrap;font-size:12px;color:#22c55e\">" +
(fixInstructions[issue.category] || "Review and address this issue.") +
"</td></tr>"
)
.join("");
const headingRows = (d.headings || [])
.map((h) => "<tr><td style=\"font-weight:700\">H" + h.level + "</td><td>" + h.text + "</td></tr>")
.join("");
const t = (v: string | null | undefined, fallback = "None") => v || "<em>" + fallback + "</em>";
return "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>SEO Audit - " +
(d.domain || "report") +
"</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0f172a;color:#e2e8f0;line-height:1.6;padding:40px}h1{font-size:28px;margin-bottom:8px}h2{font-size:20px;margin:32px 0 16px;padding-bottom:8px;border-bottom:2px solid #334155}.container{max-width:900px;margin:0 auto}.meta{text-align:center;padding:24px;background:#1e293b;border-radius:16px;margin-bottom:32px}.scores{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:24px 0}.score-card{text-align:center;padding:20px 12px;background:#1e293b;border-radius:12px}.score-value{font-size:36px;font-weight:800}.score-label{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8;margin-top:4px}table{width:100%;border-collapse:collapse;margin:16px 0;background:#1e293b;border-radius:12px;overflow:hidden}th,td{padding:12px 16px;text-align:left;border-bottom:1px solid #334155}th{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8}tr:hover{background:#334155}a{color:#60a5fa}.section{background:#1e293b;border-radius:16px;padding:24px;margin-bottom:20px}@media print{body{background:white;color:#1e293b}.section,.meta,.scores{background:#f8fafc;border:1px solid #e2e8f0}.score-value{color:#0f172a!important}}</style></head><body><div class=\"container\"><div class=\"meta\"><h1>SEO/GEO Audit Report</h1><p style=\"color:#60a5fa;font-family:monospace\">" +
(d.url || "N/A") +
"</p><p style=\"color:#94a3b8;margin-top:8px\">PromptArch Vibe Architect | " +
now +
"</p></div><div class=\"scores\"><div class=\"score-card\"><div class=\"score-value\" style=\"color:" +
sc(d.scores?.overall || 0) +
"\">" +
(d.scores?.overall || 0) +
"</div><div class=\"score-label\">Overall</div></div><div class=\"score-card\"><div class=\"score-value\" style=\"color:" +
sc(d.scores?.technical || 0) +
"\">" +
(d.scores?.technical || 0) +
"</div><div class=\"score-label\">Technical</div></div><div class=\"score-card\"><div class=\"score-value\" style=\"color:" +
sc(d.scores?.content || 0) +
"\">" +
(d.scores?.content || 0) +
"</div><div class=\"score-label\">Content</div></div><div class=\"score-card\"><div class=\"score-value\" style=\"color:" +
sc(d.scores?.performance || 0) +
"\">" +
(d.scores?.performance || 0) +
"</div><div class=\"score-label\">Performance</div></div><div class=\"score-card\"><div class=\"score-value\" style=\"color:" +
sc(d.scores?.social || 0) +
"\">" +
(d.scores?.social || 0) +
"</div><div class=\"score-label\">Social</div></div></div>" +
// Meta Tags
"<div class=\"section\"><h2>Meta Tags</h2><table><tr><th>Element</th><th>Value</th><th>Status</th></tr><tr><td>Title</td><td>" +
t(d.title, "MISSING") +
" (" +
(d.titleLength || 0) +
" chars)</td><td>" +
(d.titleStatus || "?") +
"</td></tr><tr><td>Description</td><td>" +
t(d.metaDescription, "MISSING") +
" (" +
(d.descLength || 0) +
" chars)</td><td>" +
(d.descStatus || "?") +
"</td></tr><tr><td>Viewport</td><td>" +
t(d.viewport, "MISSING") +
"</td><td>" +
(d.viewport ? "OK" : "Missing") +
"</td></tr><tr><td>Canonical</td><td>" +
(d.canonical || "None") +
"</td><td>" +
(d.hasCanonicalMismatch ? "MISMATCH" : d.canonical ? "OK" : "Missing") +
"</td></tr><tr><td>Robots</td><td>" +
(d.robotsDirectives || "None") +
"</td><td></td></tr></table></div>" +
// Social
"<div class=\"section\"><h2>Social / Open Graph</h2><table><tr><th>Property</th><th>Value</th></tr><tr><td>OG Title</td><td>" +
(d.openGraph?.title || "None") +
"</td></tr><tr><td>OG Description</td><td>" +
(d.openGraph?.description || "None") +
"</td></tr><tr><td>OG Image</td><td>" +
(d.openGraph?.image || "None") +
"</td></tr><tr><td>OG Type</td><td>" +
(d.openGraph?.type || "None") +
"</td></tr><tr><td>Twitter Card</td><td>" +
(d.twitterCard?.card || "None") +
"</td></tr></table></div>" +
// Headings
"<div class=\"section\"><h2>Headings</h2><p style=\"color:#94a3b8\">H1: " +
d.h1Count +
" | H2: " +
d.h2Count +
" | H3: " +
d.h3Count +
" | H4: " +
d.h4Count +
" [" +
(d.headingStatus || "?") +
"]</p>" +
(headingRows ? "<table><tr><th>Level</th><th>Text</th></tr>" + headingRows + "</table>" : "") +
"</div>" +
// Links & Images
"<div class=\"section\"><h2>Links & Images</h2><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Total Links</td><td>" +
(d.links?.total || 0) +
"</td></tr><tr><td>Internal</td><td>" +
(d.links?.internal || 0) +
"</td></tr><tr><td>External</td><td>" +
(d.links?.external || 0) +
"</td></tr><tr><td>Nofollow</td><td>" +
(d.links?.nofollow || 0) +
"</td></tr><tr><td>Images</td><td>" +
(d.images?.total || 0) +
" (" +
(d.images?.altCoverage || 0) +
"% alt)</td></tr><tr><td>Missing Alt</td><td>" +
(d.images?.withoutAlt || 0) +
"</td></tr><tr><td>Lazy Loaded</td><td>" +
(d.images?.lazyLoaded || 0) +
"</td></tr></table></div>" +
// Content
"<div class=\"section\"><h2>Content</h2><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Words</td><td>" +
(d.content?.wordCount || 0) +
"</td></tr><tr><td>Sentences</td><td>" +
(d.content?.sentenceCount || 0) +
"</td></tr><tr><td>Avg Words/Sentence</td><td>" +
(d.content?.avgWordsPerSentence || 0) +
"</td></tr></table></div>" +
// Performance
"<div class=\"section\"><h2>Performance</h2><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Server</td><td>" +
(d.server || "Unknown") +
"</td></tr><tr><td>Response Time</td><td>" +
d.responseTime +
"ms</td></tr><tr><td>HTML Size</td><td>" +
(d.htmlSize || 0).toLocaleString() +
" bytes</td></tr><tr><td>External Scripts</td><td>" +
(d.performance?.externalScripts || 0) +
"</td></tr><tr><td>Inline Styles</td><td>" +
(d.performance?.inlineStyles || 0) +
"</td></tr><tr><td>Preconnect</td><td>" +
(d.performance?.hasPreconnect ? "Yes" : "No") +
"</td></tr><tr><td>Async/Defer</td><td>" +
(d.performance?.usesAsyncScripts || d.performance?.usesDeferScripts ? "Yes" : "No") +
"</td></tr></table></div>" +
// Issues
"<div class=\"section\"><h2>Issues & How to Fix</h2><p style=\"color:#94a3b8\">" +
(d.issues?.length || 0) +
" issues found</p><table><tr><th>Severity</th><th>Category</th><th>Issue</th><th>How to Fix</th></tr>" +
(issueRows || "<tr><td colspan=\"4\">No issues.</td></tr>") +
"</table></div>" +
// Recommendations
"<div class=\"section\"><h2>SEO/GEO Recommendations</h2><ol style=\"padding-left:20px\"><li><strong>Title Tags:</strong> Keep under 60 chars, primary keyword near start, unique per page.</li><li><strong>Meta Descriptions:</strong> 150-160 chars with CTA, include target keywords naturally.</li><li><strong>Headings:</strong> One H1 per page, logical hierarchy, keywords in headings.</li><li><strong>Content:</strong> 1000+ words on core pages, authoritative, regularly updated.</li><li><strong>Images:</strong> Descriptive alt text, WebP format, lazy loading, compressed.</li><li><strong>Page Speed:</strong> Minimize render-blocking resources, enable compression, use CDN.</li><li><strong>Mobile:</strong> Responsive design, 48x48px tap targets, no horizontal scroll.</li><li><strong>Structured Data:</strong> JSON-LD schema, validate with Google Rich Results Test.</li><li><strong>Internal Linking:</strong> Logical architecture, descriptive anchors, 3-click rule.</li><li><strong>Technical:</strong> XML sitemap, robots.txt, fix broken links, canonical tags.</li><li><strong>GEO:</strong> Structure content with clear facts/lists/tables. Use schema for entities. Optimize for featured snippets. Create authoritative content AI models can cite.</li></ol></div><div style=\"text-align:center;padding:32px;color:#64748b;font-size:12px\"><p>PromptArch Vibe Architect | " +
now +
"</p><p><a href=\"https://rommark.dev/tools/promptarch/\">rommark.dev/tools/promptarch</a></p></div></div></body></html>";
}
export function downloadSeoReport(d: SeoAuditData, format: "html" | "pdf") {
const html = generateSeoReportHtml(d);
if (format === "html") {
const blob = new Blob([html], { type: "text/html" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "seo-audit-" + (d.domain || "report") + "-" + new Date().toISOString().slice(0, 10) + ".html";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} else {
const win = window.open("", "_blank");
if (win) {
win.document.write(html);
win.document.close();
setTimeout(() => win.print(), 500);
}
}
}