feat: v1.4.0 — review code, web search grounding, responsive preview

New features:
- Review Code button sends generated code back to AI for review
- Web search grounding via SearXNG (toggle in toolbar, enriches prompts)
- Responsive preview with device size selector (Full/Desktop/Tablet/Mobile)
- /api/search proxy route

Fixes:
- Model selector text color now white on dark theme
- Post-coding button text overflow (shorter labels + min-w-0)
- Duplicate activateArtifact button suppressed when action row shows
This commit is contained in:
admin
2026-03-18 19:58:52 +00:00
Unverified
parent d2a99e6f44
commit 7266920593
6 changed files with 214 additions and 12 deletions

View File

@@ -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<SearchResult[]> {
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<string, string>) => ({
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<SearchResult[]> {
// 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);
}