import { authCondition } from "$lib/server/auth"; import { collections } from "$lib/server/database"; import { defaultModel } from "$lib/server/models"; import { searchWeb } from "$lib/server/websearch/searchWeb"; import type { Message } from "$lib/types/Message"; import { error } from "@sveltejs/kit"; import { ObjectId } from "mongodb"; import { z } from "zod"; import type { WebSearch } from "$lib/types/WebSearch"; import { generateQuery } from "$lib/server/websearch/generateQuery"; import { parseWeb } from "$lib/server/websearch/parseWeb"; import { summarizeWeb } from "$lib/server/websearch/summarizeWeb"; interface GenericObject { [key: string]: GenericObject | unknown; } function removeLinks(obj: GenericObject) { for (const prop in obj) { if (prop.endsWith("link")) delete obj[prop]; else if (typeof obj[prop] === "object") removeLinks(obj[prop] as GenericObject); } return obj; } export async function GET({ params, locals, url }) { const model = defaultModel; const convId = new ObjectId(params.id); const searchId = new ObjectId(); const conv = await collections.conversations.findOne({ _id: convId, ...authCondition(locals), }); if (!conv) { throw error(404, "Conversation not found"); } const prompt = z.string().trim().min(1).parse(url.searchParams.get("prompt")); const messages = (() => { return [...conv.messages, { content: prompt, from: "user", id: crypto.randomUUID() }]; })() satisfies Message[]; const stream = new ReadableStream({ async start(controller) { const webSearch: WebSearch = { _id: searchId, convId: convId, prompt: prompt, searchQuery: "", knowledgeGraph: "", answerBox: "", results: [], summary: "", messages: [], createdAt: new Date(), updatedAt: new Date(), }; function appendUpdate(message: string, args?: string[], type?: "error" | "update") { webSearch.messages.push({ type: type ?? "update", message, args, }); controller.enqueue(JSON.stringify({ messages: webSearch.messages })); } try { appendUpdate("Generating search query"); webSearch.searchQuery = await generateQuery(messages, model); appendUpdate("Searching Google", [webSearch.searchQuery]); const results = await searchWeb(webSearch.searchQuery); let text = ""; webSearch.results = (results.organic_results && results.organic_results.map((el: { link: string }) => el.link)) ?? []; if (results.answer_box) { // if google returns an answer box, we use it webSearch.answerBox = JSON.stringify(removeLinks(results.answer_box)); text = webSearch.answerBox; appendUpdate("Found a Google answer box"); } else if (results.knowledge_graph) { // if google returns a knowledge graph, we use it webSearch.knowledgeGraph = JSON.stringify(removeLinks(results.knowledge_graph)); text = webSearch.knowledgeGraph; appendUpdate("Found a Google knowledge page"); } else if (webSearch.results.length > 0) { let tries = 0; while (!text && tries < 3) { const searchUrl = webSearch.results[tries]; appendUpdate("Browsing result", [JSON.stringify(searchUrl)]); try { text = await parseWeb(searchUrl); if (!text) throw new Error("text of the webpage is null"); } catch (e) { appendUpdate("Error parsing webpage", [], "error"); tries++; } } if (!text) throw new Error("No text found on the first 3 results"); } else { throw new Error("No results found for this search query"); } appendUpdate("Creating summary"); webSearch.summary = await summarizeWeb(text, webSearch.searchQuery, model); appendUpdate("Injecting summary", [JSON.stringify(webSearch.summary)]); } catch (searchError) { if (searchError instanceof Error) { webSearch.messages.push({ type: "error", message: "An error occurred with the web search", args: [JSON.stringify(searchError.message)], }); } } const res = await collections.webSearches.insertOne(webSearch); webSearch.messages.push({ type: "result", id: res.insertedId.toString(), }); controller.enqueue(JSON.stringify({ messages: webSearch.messages })); }, }); return new Response(stream, { headers: { "Content-Type": "application/json" } }); }