starchat-ui / pages /api /google.ts
matthoffner's picture
Duplicate from matthoffner/chatbot
13095e0
raw
history blame
4.71 kB
import { NextApiRequest, NextApiResponse } from 'next';
import { OPENAI_API_HOST } from '@/utils/app/const';
import { cleanSourceText } from '@/utils/server/google';
import { Message } from '@/types/chat';
import { GoogleBody, GoogleSource } from '@/types/google';
import { Readability } from '@mozilla/readability';
import endent from 'endent';
import jsdom, { JSDOM } from 'jsdom';
const handler = async (req: NextApiRequest, res: NextApiResponse<any>) => {
try {
const { messages, key, model, googleAPIKey, googleCSEId } =
req.body as GoogleBody;
const userMessage = messages[messages.length - 1];
const query = encodeURIComponent(userMessage.content.trim());
const googleRes = await fetch(
`https://customsearch.googleapis.com/customsearch/v1?key=${
googleAPIKey ? googleAPIKey : process.env.GOOGLE_API_KEY
}&cx=${
googleCSEId ? googleCSEId : process.env.GOOGLE_CSE_ID
}&q=${query}&num=5`,
);
const googleData = await googleRes.json();
const sources: GoogleSource[] = googleData.items.map((item: any) => ({
title: item.title,
link: item.link,
displayLink: item.displayLink,
snippet: item.snippet,
image: item.pagemap?.cse_image?.[0]?.src,
text: '',
}));
const sourcesWithText: any = await Promise.all(
sources.map(async (source) => {
try {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timed out')), 5000),
);
const res = (await Promise.race([
fetch(source.link),
timeoutPromise,
])) as any;
// if (res) {
const html = await res.text();
const virtualConsole = new jsdom.VirtualConsole();
virtualConsole.on('error', (error) => {
if (!error.message.includes('Could not parse CSS stylesheet')) {
console.error(error);
}
});
const dom = new JSDOM(html, { virtualConsole });
const doc = dom.window.document;
const parsed = new Readability(doc).parse();
if (parsed) {
let sourceText = cleanSourceText(parsed.textContent);
return {
...source,
// TODO: switch to tokens
text: sourceText.slice(0, 2000),
} as GoogleSource;
}
// }
return null;
} catch (error) {
console.error(error);
return null;
}
}),
);
const filteredSources: GoogleSource[] = sourcesWithText.filter(Boolean);
const answerPrompt = endent`
Provide me with the information I requested. Use the sources to provide an accurate response. Respond in markdown format. Cite the sources you used as a markdown link as you use them at the end of each sentence by number of the source (ex: [[1]](link.com)). Provide an accurate response and then stop. Today's date is ${new Date().toLocaleDateString()}.
Example Input:
What's the weather in San Francisco today?
Example Sources:
[Weather in San Francisco](https://www.google.com/search?q=weather+san+francisco)
Example Response:
It's 70 degrees and sunny in San Francisco today. [[1]](https://www.google.com/search?q=weather+san+francisco)
Input:
${userMessage.content.trim()}
Sources:
${filteredSources.map((source) => {
return endent`
${source.title} (${source.link}):
${source.text}
`;
})}
Response:
`;
const answerMessage: Message = { role: 'user', content: answerPrompt };
const answerRes = await fetch(`${OPENAI_API_HOST}/v1/chat/completions`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${key ? key : process.env.OPENAI_API_KEY}`,
...(process.env.OPENAI_ORGANIZATION && {
'OpenAI-Organization': process.env.OPENAI_ORGANIZATION,
}),
},
method: 'POST',
body: JSON.stringify({
model: model.id,
messages: [
{
role: 'system',
content: `Use the sources to provide an accurate response. Respond in markdown format. Cite the sources you used as [1](link), etc, as you use them. Maximum 4 sentences.`,
},
answerMessage,
],
max_tokens: 1000,
temperature: 1,
stream: false,
}),
});
const { choices: choices2 } = await answerRes.json();
const answer = choices2[0].message.content;
res.status(200).json({ answer });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Error'})
}
};
export default handler;