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:
66
lib/services/search-api.ts
Normal file
66
lib/services/search-api.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user