diff --git a/app/src/components/Chat.tsx b/app/src/components/Chat.tsx index 422ea0f..511c0ab 100644 --- a/app/src/components/Chat.tsx +++ b/app/src/components/Chat.tsx @@ -7,10 +7,7 @@ import PromptForm from "./PromptForm"; import { toast } from "sonner"; import NewConversationButton from "./NewConversationButton"; import { NavMenu } from "./NavMenu"; -import { - useQuery, - useMutation, -} from '@tanstack/react-query' +import { useQuery, useMutation } from "@tanstack/react-query"; import SideMenu from "./SideMenu"; export default function Chat({ @@ -25,32 +22,35 @@ export default function Chat({ const [messages, setMessages] = useState([]); const [conversationId, setConversationId] = useState(null); const [waitingForResponse, setWaitingForResponse] = useState(false); - + const sendMessageAndReceiveResponse = useMutation({ mutationFn: async (userMessage: Message) => { - const { data: sendMessageData, error: sendMessageError } = await supabaseClient - .from('conversations') - .update({ context: [...messages, userMessage] }) - .eq('id', conversationId); - + const { data: sendMessageData, error: sendMessageError } = + await supabaseClient + .from("conversations") + .update({ context: [...messages, userMessage] }) + .eq("id", conversationId); + if (sendMessageError) throw sendMessageError; - + setMessages([...messages, userMessage]); setWaitingForResponse(true); - const { data: aiResponseData, error: aiResponseError } = await supabaseClient.functions.invoke("chat", { - body: { messageHistory: [...messages, userMessage] }, - }); + const { data: aiResponseData, error: aiResponseError } = + await supabaseClient.functions.invoke("chat", { + body: { messageHistory: [...messages, userMessage] }, + }); if (aiResponseError) throw aiResponseError; - - const {data: updateConversationData, error: updateConversationError} = await supabaseClient - .from('conversations') - .update({ context: [...messages, userMessage, aiResponseData.msg] }) - .eq('id', conversationId); - + + const { data: updateConversationData, error: updateConversationError } = + await supabaseClient + .from("conversations") + .update({ context: [...messages, userMessage, aiResponseData.msg] }) + .eq("id", conversationId); + if (updateConversationError) throw updateConversationError; - + return aiResponseData; }, onError: (error) => { @@ -58,11 +58,12 @@ export default function Chat({ setWaitingForResponse(false); }, onSuccess: (aiResponse) => { - setMessages(currentMessages => { + setMessages((currentMessages) => { return [...currentMessages, aiResponse.msg as Message]; }); + setWaitingForResponse(false); - } + }, }); const newConversation = useMutation({ @@ -94,16 +95,16 @@ export default function Chat({ setConversationId(data[0].id); setWaitingForResponse(false); }, - }) + }); const getConversation = useQuery({ - queryKey: ['conversation', conversationId], + queryKey: ["conversation", conversationId], queryFn: async () => { if (conversationId === null) { const { data, error } = await supabaseClient - .from('conversations') - .select('*') - .order('created_at', { ascending: false }) + .from("conversations") + .select("*") + .order("created_at", { ascending: false }) .limit(1); if (error) { throw error; @@ -118,17 +119,17 @@ export default function Chat({ } else { setMessages([]); const { data, error } = await supabaseClient - .from('conversations') - .select('*') - .eq('id', conversationId) + .from("conversations") + .select("*") + .eq("id", conversationId) .single(); if (error) { throw error; } return data; } - } - }) + }, + }); useEffect(() => { if (getConversation.data) { @@ -147,10 +148,10 @@ export default function Chat({ <>
- +
@@ -165,10 +166,7 @@ export default function Chat({
- +
diff --git a/docs/getting_started.md b/docs/getting_started.md index e753a24..0f941ce 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -60,7 +60,8 @@ We will use Supabase as our database (with vector search, pgvector), authenticat 10. Now when we have the CLI, we need to login with oour Supabase account, running `supabase login` - this should pop up a browser window, which should prompt you through the auth 11. And link our Supabase CLI to a specific project, our newly created one, by running `supabase link --project-ref ` (you can check what the project id is from the Supabase web UI, or by running `supabase projects list`, and it will be under "reference id") - you can skip (enter) the database password, it's not needed. 12. Now let's deploy our functions! ([see guide for more details](https://supabase.com/../guides/functions/deploy)) `supabase functions deploy --no-verify-jwt` (see [issue re:security](https://github.com/adamcohenhillel/AdDeus/issues/3)) -13. Lasly - if you're planning to first use OpenAI as your Foundation model provider, then you'd need to also run the following command, to make sure the functions have everything they need to run properly: `supabase secrets set OPENAI_API_KEY=` (Ollama setup guide is coming out soon) +13. If you're planning to first use OpenAI as your Foundation model provider, then you'd need to also run the following command, to make sure the functions have everything they need to run properly: `supabase secrets set OPENAI_API_KEY=` (Ollama setup guide is coming out soon) +14. If you want access to tons of AI Models, both Open & Closed Source, set up your OpenRouter API Key. Go to [OpenRouter](https://openrouter.ai/) to get your API Key, then run `supabase secrets set OPENROUTER_API_KEY=`. If everything worked, we should now be able to start chatting with our personal AI via the app - so let's set that up! diff --git a/docs/images/openrouter.png b/docs/images/openrouter.png new file mode 100644 index 0000000..72f69d8 Binary files /dev/null and b/docs/images/openrouter.png differ diff --git a/supabase/functions/chat/index.ts b/supabase/functions/chat/index.ts index 7c93eac..18bd4f0 100644 --- a/supabase/functions/chat/index.ts +++ b/supabase/functions/chat/index.ts @@ -5,6 +5,25 @@ import { corsHeaders } from "../common/cors.ts"; import { supabaseClient } from "../common/supabaseClient.ts"; import { ApplicationError, UserError } from "../common/errors.ts"; +async function generateResponse( + useOpenRouter, + openaiClient, + openRouterClient, + messages +) { + const client = useOpenRouter ? openRouterClient : openaiClient; + const modelName = useOpenRouter + ? "nousresearch/nous-capybara-34b" + : "gpt-4-1106-preview"; + + const { choices } = await client.chat.completions.create({ + model: modelName, + messages, + }); + console.log("Completion: ", choices[0]); + return choices[0].message; +} + const chat = async (req) => { if (req.method === "OPTIONS") { return new Response("ok", { headers: corsHeaders }); @@ -21,16 +40,29 @@ const chat = async (req) => { throw new ApplicationError( "Unable to get auth user details in request data" ); - const { messageHistory } = await req.json(); + const requestBody = await req.json(); + const { messageHistory } = requestBody; + if (!messageHistory) throw new UserError("Missing query in request data"); const openaiClient = new OpenAI({ apiKey: Deno.env.get("OPENAI_API_KEY"), }); + const openRouterApiKey = Deno.env.get("OPENROUTER_API_KEY"); + const useOpenRouter = Boolean(openRouterApiKey); // Use OpenRouter if API key is available + + let openRouterClient; + if (useOpenRouter) { + openRouterClient = new OpenAI({ + baseURL: "https://openrouter.ai/api/v1", + apiKey: openRouterApiKey, + }); + } + console.log("messageHistory: ", messageHistory); - // embed the last messageHistory message + // Embed the last messageHistory message using OpenAI's embeddings API const embeddingsResponse = await openaiClient.embeddings.create({ model: "text-embedding-ada-002", input: messageHistory[messageHistory.length - 1].content, @@ -38,12 +70,13 @@ const chat = async (req) => { const embeddings = embeddingsResponse.data[0].embedding; console.log("Embeddings:", embeddings); + // Retrieve records from Supabase based on embeddings similarity const { data: relevantRecords, error: recordsError } = await supabase.rpc( "match_records_embeddings_similarity", { - query_embedding: JSON.stringify(embeddings), // Pass the embedding you want to compare - match_threshold: 0.8, // Choose an appropriate threshold for your data - match_count: 10, // Choose the number of matches + query_embedding: JSON.stringify(embeddings), + match_threshold: 0.8, + match_count: 10, } ); @@ -67,27 +100,25 @@ const chat = async (req) => { console.log("messages: ", messages); try { - let completion = await openaiClient.chat.completions.create({ - model: "gpt-4-1106-preview", - messages: messages, - }); - console.log("completion: ", completion); - console.log( - "completion.choices[0].content: ", - completion.choices[0].content + const responseMessage = await generateResponse( + useOpenRouter, + openaiClient, + openRouterClient, + messages ); + return new Response( JSON.stringify({ - msg: completion.choices[0].message, + msg: responseMessage, }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 200, } ); - } catch (openAiError) { - console.log("!!! Error in OpenAI fallback: ", openAiError); - throw openAiError; + } catch (error) { + console.log("Error: ", error); + throw new ApplicationError("Error processing chat completion"); } return new Response(