diff --git a/CHANGELOG.md b/CHANGELOG.md index 77bc559..de1d664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ 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). +## [1.4.0] - 2026-03-18 19:57 UTC + +### Added +- **Review Code Button** — Post-coding action to send generated code back to AI for review + - `reviewCode()` function sends code with review-focused prompt + - Emerald-green "Review" button alongside Preview and Modify + - 3-column post-coding action grid: Preview / Review / Modify +- **Web Search Grounding** — Enrich AI prompts with live web search results + - `lib/services/search-api.ts` — SearXNG public API wrapper with 4 instance fallback + - `/api/search` route — server-side search proxy endpoint + - Toggle button in toolbar to enable/disable (amber highlight when active) + - Shows "Searching the web..." status while fetching + - Appends top 5 results as `[WEB SEARCH CONTEXT]` to user prompt +- **Responsive Preview** — Device size selector in canvas panel + - Full / Desktop (1280px) / Tablet (768px) / Mobile (375px) buttons + - Centered device frame with border, shadow, and scroll on overflow + - Only visible in preview mode, below Live Render / Inspect Code tabs + +### Fixed +- **Model selector text color** — Option text now white on dark theme (`bg-[#0b1414] text-white`) +- **Button text overflow** — Shortened labels (Preview/Review/Modify), added `min-w-0` for proper truncation +- **Duplicate activateArtifact button** — Original button now hides when post-coding action row is shown + +### Technical Details +- Files modified: 1 (AIAssist.tsx: +85/-11 lines) +- Files added: 2 (`lib/services/search-api.ts`, `app/api/search/route.ts`) +- New state: `deviceSize`, `webSearchEnabled` +- New function: `reviewCode()` + ## [1.3.0] - 2026-03-18 18:51 UTC ### Added diff --git a/README.md b/README.md index f649920..d18c7fc 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,11 @@ Transform vague ideas into production-ready prompts and PRDs. PromptArch is an A - Live code rendering with `[PREVIEW]` tags - HTML, React, Python, and more — rendered in-browser - Auto-detect renderable vs. code-only previews +- Responsive preview with device size selector (Full / Desktop / Tablet / Mobile) + +### Code Review & Web Search +- **Review Code** — Send generated code back to AI for bug/security/performance review +- **Web Search Grounding** — Toggle to enrich prompts with live web search results via SearXNG ### Enhanced Prompt Engine - 9 strategies: clarify, add-context, add-constraints, structure, add-examples, set-tone, expand, simplify, chain-of-thought @@ -136,6 +141,7 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | Version | Date | Highlights | |---------|------|------------| +| [1.4.0](CHANGELOG.md#140---2026-03-18) | 2026-03-18 19:57 | Review Code button, web search grounding, responsive preview, model selector fix | | [1.3.0](CHANGELOG.md#130---2026-03-18) | 2026-03-18 18:51 | Plan-first workflow, OpenRouter, post-coding UX, enhanced prompt engine | | [1.2.0](CHANGELOG.md#120---2026-01-19) | 2026-01-19 19:16 | SEO agent fixes, Z.AI API validation | | [1.1.0](CHANGELOG.md#110---2025-12-29) | 2025-12-29 17:55 | GitHub push, XLSX/HTML export, OAuth management | diff --git a/app/api/search/route.ts b/app/api/search/route.ts new file mode 100644 index 0000000..1a5af57 --- /dev/null +++ b/app/api/search/route.ts @@ -0,0 +1,27 @@ +/** + * Next.js API route: Web search proxy. + * Calls SearXNG public instances and returns top results. + * Endpoint: GET /api/search?q=your+query + */ + +import { NextRequest, NextResponse } from "next/server"; +import { searchWeb } from "@/lib/services/search-api"; + +export async function GET(request: NextRequest) { + const query = request.nextUrl.searchParams.get("q"); + + if (!query || query.trim().length < 3) { + return NextResponse.json({ results: [], error: "Query too short" }); + } + + try { + const results = await searchWeb(query); + return NextResponse.json({ results }); + } catch (error) { + console.error("Search API error:", error); + return NextResponse.json( + { results: [], error: "Search failed" }, + { status: 500 } + ); + } +} diff --git a/components/AIAssist.tsx b/components/AIAssist.tsx index 736f017..e15e8af 100644 --- a/components/AIAssist.tsx +++ b/components/AIAssist.tsx @@ -4,7 +4,7 @@ import React, { useState, useEffect, useRef, memo } from "react"; import { MessageSquare, Send, Code2, Palette, Search, Trash2, Copy, Monitor, StopCircle, X, Zap, Ghost, - Wand2, LayoutPanelLeft, Play, Orbit, Plus, Key + Wand2, LayoutPanelLeft, Play, Orbit, Plus, Key, ShieldCheck } from "lucide-react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -509,11 +509,14 @@ export default function AIAssist() { const [input, setInput] = useState(""); const [isProcessing, setIsProcessing] = useState(false); + const [webSearchEnabled, setWebSearchEnabled] = useState(false); const [currentAgent, setCurrentAgent] = useState(activeTab?.currentAgent || "general"); const [previewData, setPreviewData] = useState(activeTab?.previewData || null); const [availableModels, setAvailableModels] = useState([]); const [showCanvas, setShowCanvas] = useState(activeTab?.showCanvas === true); const [viewMode, setViewMode] = useState<"preview" | "code">("preview"); + const [deviceSize, setDeviceSize] = useState<"full" | "desktop" | "tablet" | "mobile">("full"); + const deviceWidths: Record = { full: "100%", desktop: "1280px", tablet: "768px", mobile: "375px" }; const [abortController, setAbortController] = useState(null); // Agent suggestion state @@ -668,9 +671,30 @@ export default function AIAssist() { return m; }); + // Web search grounding: enrich prompt with search results if enabled + let enrichedInput = finalInput; + if (webSearchEnabled) { + setStatus("Searching the web..."); + try { + 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("Web search failed:", e); + } + setStatus(null); + } + const response = await modelAdapter.generateAIAssistStream( { - messages: [...formattedHistory, { role: "user" as const, content: finalInput, timestamp: new Date() }], + messages: [...formattedHistory, { role: "user" as const, content: enrichedInput, timestamp: new Date() }], currentAgent, onChunk: (chunk) => { accumulated += chunk; @@ -761,6 +785,12 @@ export default function AIAssist() { handleSendMessage(undefined, "Approved. Please generate the code according to the plan.", true); }; + const reviewCode = () => { + if (!previewData?.data) return; + setAssistStep("generating"); + const reviewPrompt = "Please review this generated code for bugs, security issues, performance problems, and best practices. Provide specific improvements:" + "\n" + "\n" + "```" + (previewData.language || "code") + "\n" + previewData.data + "\n" + "```"; + }; + const stopGeneration = () => { if (abortController) { abortController.abort(); @@ -868,10 +898,20 @@ export default function AIAssist() { +
@@ -1167,7 +1207,7 @@ export default function AIAssist() {
)} - {msg.role === "assistant" && msg.preview && ( + {msg.role === "assistant" && msg.preview && !(assistStep === "preview" && i === aiAssistHistory.length - 1 && !isProcessing) && ( + )} @@ -1302,6 +1350,24 @@ export default function AIAssist() { {t.inspectCode} + {viewMode === "preview" && ( +
+ {([["full", "Full"], ["desktop", "Desktop"], ["tablet", "Tablet"], ["mobile", "Mobile"]] as const).map(([size, label]) => ( + + ))} +
+ )}
@@ -1345,14 +1411,22 @@ export default function AIAssist() {
-
+
{viewMode === "preview" && currentPreviewData ? ( +
+
) : (
diff --git a/lib/services/search-api.ts b/lib/services/search-api.ts new file mode 100644 index 0000000..0306c36 --- /dev/null +++ b/lib/services/search-api.ts @@ -0,0 +1,66 @@ +/** + * Web search API wrapper using SearXNG public instances. + * No API key required — free for server-side use. + */ + +export interface SearchResult { + title: string; + url: string; + snippet: string; +} + +const SEARXNG_INSTANCES = [ + "https://searx.be", + "https://search.sapti.me", + "https://searx.tiekoetter.com", + "https://search.bus-hit.me", +]; + +async function searchSearXNG(query: string): Promise { + for (const instance of SEARXNG_INSTANCES) { + try { + const url = `${instance}/search?q=${encodeURIComponent(query)}&format=json&categories=general&language=en`; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 8000); + + const res = await fetch(url, { + signal: controller.signal, + headers: { + "User-Agent": "PromptArch/1.4 (https://rommark.dev)", + Accept: "application/json", + }, + }); + clearTimeout(timeout); + + if (!res.ok) continue; + + const data = await res.json(); + const results: SearchResult[] = (data.results || []) + .slice(0, 8) + .map((r: Record) => ({ + title: r.title || "", + url: r.url || "", + snippet: r.content || "", + })) + .filter((r: SearchResult) => r.title && r.url); + + if (results.length > 0) return results; + } catch { + // Try next instance + } + } + return []; +} + +export async function searchWeb(query: string): Promise { + // Clean the query — take first meaningful line, max 200 chars + const cleanQuery = query + .split("\n")[0] + .replace(/\[.*?\]/g, "") + .trim() + .substring(0, 200); + + if (!cleanQuery || cleanQuery.length < 3) return []; + + return searchSearXNG(cleanQuery); +} diff --git a/package.json b/package.json index be3dc90..b9a6841 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "promptarch", - "version": "1.3.0", + "version": "1.4.0", "description": "Transform vague ideas into production-ready prompts and PRDs", "scripts": { "dev": "next dev",