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
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|