From fcb3c04bbcae853d39607f9d8fb85dad6c5217c0 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 18 Mar 2026 21:34:13 +0000 Subject: [PATCH] feat: SEO audit export, default Vibe Architect, /vibe route (v2.0.0) --- CHANGELOG.md | 19 ++- README.md | 3 +- app/page.tsx | 2 +- components/AIAssist.tsx | 30 ++++- lib/seo-report.ts | 250 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 lib/seo-report.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a76e14a..1333fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index a28bb18..a3e1ede 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/app/page.tsx b/app/page.tsx index 356c24f..c7ced0c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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("enhance"); + const [currentView, setCurrentView] = useState("ai-assist"); useEffect(() => { console.log("[Home] Initializing Qwen OAuth service on client..."); diff --git a/components/AIAssist.tsx b/components/AIAssist.tsx index 601bacb..a260952 100644 --- a/components/AIAssist.tsx +++ b/components/AIAssist.tsx @@ -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 = { full: "100%", desktop: "1280px", tablet: "768px", mobile: "375px" }; const [isModifying, setIsModifying] = useState(false); + const [seoAuditData, setSeoAuditData] = useState(null); const [abortController, setAbortController] = useState(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 && ( + <>
+ {currentAgent === "seo" && seoAuditData && ( +
+ + +
+ )} + )} diff --git a/lib/seo-report.ts b/lib/seo-report.ts new file mode 100644 index 0000000..05cea2e --- /dev/null +++ b/lib/seo-report.ts @@ -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 = { + 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 . 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) => + "" + + issue.severity.toUpperCase() + + "" + + issue.category + + "" + + issue.message + + "" + + (fixInstructions[issue.category] || "Review and address this issue.") + + "" + ) + .join(""); + + const headingRows = (d.headings || []) + .map((h) => "H" + h.level + "" + h.text + "") + .join(""); + + const t = (v: string | null | undefined, fallback = "None") => v || "" + fallback + ""; + + return "SEO Audit - " + + (d.domain || "report") + + "

SEO/GEO Audit Report

" + + (d.url || "N/A") + + "

PromptArch Vibe Architect | " + + now + + "

" + + (d.scores?.overall || 0) + + "
Overall
" + + (d.scores?.technical || 0) + + "
Technical
" + + (d.scores?.content || 0) + + "
Content
" + + (d.scores?.performance || 0) + + "
Performance
" + + (d.scores?.social || 0) + + "
Social
" + + // Meta Tags + "

Meta Tags

ElementValueStatus
Title" + + t(d.title, "MISSING") + + " (" + + (d.titleLength || 0) + + " chars)" + + (d.titleStatus || "?") + + "
Description" + + t(d.metaDescription, "MISSING") + + " (" + + (d.descLength || 0) + + " chars)" + + (d.descStatus || "?") + + "
Viewport" + + t(d.viewport, "MISSING") + + "" + + (d.viewport ? "OK" : "Missing") + + "
Canonical" + + (d.canonical || "None") + + "" + + (d.hasCanonicalMismatch ? "MISMATCH" : d.canonical ? "OK" : "Missing") + + "
Robots" + + (d.robotsDirectives || "None") + + "
" + + // Social + "

Social / Open Graph

PropertyValue
OG Title" + + (d.openGraph?.title || "None") + + "
OG Description" + + (d.openGraph?.description || "None") + + "
OG Image" + + (d.openGraph?.image || "None") + + "
OG Type" + + (d.openGraph?.type || "None") + + "
Twitter Card" + + (d.twitterCard?.card || "None") + + "
" + + // Headings + "

Headings

H1: " + + d.h1Count + + " | H2: " + + d.h2Count + + " | H3: " + + d.h3Count + + " | H4: " + + d.h4Count + + " [" + + (d.headingStatus || "?") + + "]

" + + (headingRows ? "" + headingRows + "
LevelText
" : "") + + "
" + + // Links & Images + "

Links & Images

MetricValue
Total Links" + + (d.links?.total || 0) + + "
Internal" + + (d.links?.internal || 0) + + "
External" + + (d.links?.external || 0) + + "
Nofollow" + + (d.links?.nofollow || 0) + + "
Images" + + (d.images?.total || 0) + + " (" + + (d.images?.altCoverage || 0) + + "% alt)
Missing Alt" + + (d.images?.withoutAlt || 0) + + "
Lazy Loaded" + + (d.images?.lazyLoaded || 0) + + "
" + + // Content + "

Content

MetricValue
Words" + + (d.content?.wordCount || 0) + + "
Sentences" + + (d.content?.sentenceCount || 0) + + "
Avg Words/Sentence" + + (d.content?.avgWordsPerSentence || 0) + + "
" + + // Performance + "

Performance

MetricValue
Server" + + (d.server || "Unknown") + + "
Response Time" + + d.responseTime + + "ms
HTML Size" + + (d.htmlSize || 0).toLocaleString() + + " bytes
External Scripts" + + (d.performance?.externalScripts || 0) + + "
Inline Styles" + + (d.performance?.inlineStyles || 0) + + "
Preconnect" + + (d.performance?.hasPreconnect ? "Yes" : "No") + + "
Async/Defer" + + (d.performance?.usesAsyncScripts || d.performance?.usesDeferScripts ? "Yes" : "No") + + "
" + + // Issues + "

Issues & How to Fix

" + + (d.issues?.length || 0) + + " issues found

" + + (issueRows || "") + + "
SeverityCategoryIssueHow to Fix
No issues.
" + + // Recommendations + "

SEO/GEO Recommendations

  1. Title Tags: Keep under 60 chars, primary keyword near start, unique per page.
  2. Meta Descriptions: 150-160 chars with CTA, include target keywords naturally.
  3. Headings: One H1 per page, logical hierarchy, keywords in headings.
  4. Content: 1000+ words on core pages, authoritative, regularly updated.
  5. Images: Descriptive alt text, WebP format, lazy loading, compressed.
  6. Page Speed: Minimize render-blocking resources, enable compression, use CDN.
  7. Mobile: Responsive design, 48x48px tap targets, no horizontal scroll.
  8. Structured Data: JSON-LD schema, validate with Google Rich Results Test.
  9. Internal Linking: Logical architecture, descriptive anchors, 3-click rule.
  10. Technical: XML sitemap, robots.txt, fix broken links, canonical tags.
  11. GEO: Structure content with clear facts/lists/tables. Use schema for entities. Optimize for featured snippets. Create authoritative content AI models can cite.

PromptArch Vibe Architect | " + + now + + "

rommark.dev/tools/promptarch

"; +} + +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); + } + } +}