Update AI SDK Example (#2271)

This commit is contained in:
Saket Aryan
2025-02-28 07:41:59 +05:30
committed by GitHub
parent d200691e9b
commit f8071a753b
8 changed files with 408 additions and 288 deletions

View File

@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@mem0/vercel-ai-provider": "^0.0.7",
"@mem0/vercel-ai-provider": "0.0.12",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.1",
@@ -18,7 +18,7 @@
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"ai": "^3.4.31",
"ai": "4.1.42",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",

View File

@@ -5,7 +5,7 @@ import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import GlobalContext from '@/contexts/GlobalContext'
import { Provider } from '@/constants/messages'
export default function ApiSettingsPopup(props: { isOpen: boolean, setIsOpen: Dispatch<SetStateAction<boolean>> }) {
const {isOpen, setIsOpen} = props
const [mem0ApiKey, setMem0ApiKey] = useState('')
@@ -15,7 +15,7 @@ export default function ApiSettingsPopup(props: { isOpen: boolean, setIsOpen: Di
const handleSave = () => {
// Here you would typically save the settings to your backend or local storage
selectorHandler(mem0ApiKey, providerApiKey, provider);
selectorHandler(mem0ApiKey, providerApiKey, provider as Provider);
setIsOpen(false)
}

View File

@@ -0,0 +1,31 @@
import { Message } from "@/types";
export const WELCOME_MESSAGE: Message = {
id: "1",
content: "👋 Hi there! I'm your personal assistant. How can I help you today? 😊",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
export const INVALID_CONFIG_MESSAGE: Message = {
id: "2",
content: "Invalid configuration. Please check your API keys, and add a user and try again.",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
export const ERROR_MESSAGE: Message = {
id: "3",
content: "Something went wrong. Please try again.",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
export const AI_MODELS = {
openai: "gpt-4o",
anthropic: "claude-3-haiku-20240307",
cohere: "command-r-plus",
groq: "gemma2-9b-it",
} as const;
export type Provider = keyof typeof AI_MODELS;

View File

@@ -1,281 +1,84 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { createContext, useEffect, useState } from "react";
import { createMem0, searchMemories } from "@mem0/vercel-ai-provider";
import { LanguageModelV1Prompt, streamText } from "ai";
import { Message, Memory, FileInfo } from "@/types";
import { Buffer } from 'buffer';
import { createContext } from 'react';
import { Message, Memory, FileInfo } from '@/types';
import { useAuth } from '@/hooks/useAuth';
import { useChat } from '@/hooks/useChat';
import { useFileHandler } from '@/hooks/useFileHandler';
import { Provider } from '@/constants/messages';
const GlobalContext = createContext<any>({});
const WelcomeMessage: Message = {
id: "1",
content:
"👋 Hi there! I'm your personal assistant. How can I help you today? 😊",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
const InvalidConfigMessage: Message = {
id: "2",
content:
"Invalid configuration. Please check your API keys, and add a user and try again.",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
const SomethingWentWrongMessage: Message = {
id: "3",
content: "Something went wrong. Please try again.",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
const models = {
"openai": "gpt-4o",
"anthropic": "claude-3-haiku-20240307",
"cohere": "command-r-plus",
"groq": "gemma2-9b-it"
interface GlobalContextType {
selectedUser: string;
selectUserHandler: (user: string) => void;
clearUserHandler: () => void;
messages: Message[];
memories: Memory[];
handleSend: (content: string) => Promise<void>;
thinking: boolean;
selectedMem0Key: string;
selectedOpenAIKey: string;
selectedProvider: Provider;
selectorHandler: (mem0: string, openai: string, provider: Provider) => void;
clearConfiguration: () => void;
selectedFile: FileInfo | null;
setSelectedFile: (file: FileInfo | null) => void;
file: File | null;
setFile: (file: File | null) => void;
}
const getModel = (provider: string) => {
switch (provider) {
case "openai":
return models.openai;
case "anthropic":
return models.anthropic;
case "cohere":
return models.cohere;
case "groq":
return models.groq;
default:
return models.openai;
}
}
const GlobalContext = createContext<GlobalContextType>({} as GlobalContextType);
const GlobalState = (props: any) => {
const [memories, setMemories] = useState<Memory[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [selectedUser, setSelectedUser] = useState<string>("");
const [thinking, setThinking] = useState<boolean>(false);
const [selectedOpenAIKey, setSelectedOpenAIKey] = useState<string>("");
const [selectedMem0Key, setSelectedMem0Key] = useState<string>("");
const [selectedProvider, setSelectedProvider] = useState<string>("openai");
const [selectedFile, setSelectedFile] = useState<FileInfo | null>(null)
const [file, setFile] = useState<any>(null)
const mem0 = createMem0({
provider: selectedProvider,
const GlobalState = (props: { children: React.ReactNode }) => {
const {
mem0ApiKey: selectedMem0Key,
apiKey: selectedOpenAIKey,
openaiApiKey: selectedOpenAIKey,
provider: selectedProvider,
user: selectedUser,
setAuth: selectorHandler,
setUser: selectUserHandler,
clearAuth: clearConfiguration,
clearUser: clearUserHandler,
} = useAuth();
const {
selectedFile,
file,
fileData,
setSelectedFile,
handleFile,
clearFile,
} = useFileHandler();
const {
messages,
memories,
thinking,
sendMessage,
} = useChat({
user: selectedUser,
mem0ApiKey: selectedMem0Key,
openaiApiKey: selectedOpenAIKey,
provider: selectedProvider,
});
const clearConfiguration = () => {
localStorage.removeItem("mem0ApiKey");
localStorage.removeItem("openaiApiKey");
localStorage.removeItem("provider");
setSelectedMem0Key("");
setSelectedOpenAIKey("");
setSelectedProvider("openai");
setSelectedUser("");
setMessages([WelcomeMessage]);
setMemories([]);
setFile(null);
};
const selectorHandler = (mem0: string, openai: string, provider: string) => {
setSelectedMem0Key(mem0);
setSelectedOpenAIKey(openai);
setSelectedProvider(provider);
localStorage.setItem("mem0ApiKey", mem0);
localStorage.setItem("openaiApiKey", openai);
localStorage.setItem("provider", provider);
};
useEffect(() => {
const mem0 = localStorage.getItem("mem0ApiKey");
const openai = localStorage.getItem("openaiApiKey");
const provider = localStorage.getItem("provider");
const user = localStorage.getItem("user");
if (mem0 && openai && provider) {
selectorHandler(mem0, openai, provider);
}
if (user) {
setSelectedUser(user);
}
}, []);
const selectUserHandler = (user: string) => {
setSelectedUser(user);
localStorage.setItem("user", user);
};
const clearUserHandler = () => {
setSelectedUser("");
setMemories([]);
};
const getMemories = async (messages: LanguageModelV1Prompt) => {
try {
const smemories = await searchMemories(messages, {
user_id: selectedUser || "",
mem0ApiKey: selectedMem0Key,
});
const newMemories = smemories.map((memory: any) => ({
id: memory.id,
content: memory.memory,
timestamp: memory.updated_at,
tags: memory.categories,
}));
setMemories(newMemories);
} catch (error) {
console.error("Error in getMemories:", error);
}
};
const handleSend = async (inputValue: string) => {
if (!inputValue.trim() && !file) return;
if (!selectedUser) {
const newMessage: Message = {
id: Date.now().toString(),
content: inputValue,
sender: "user",
timestamp: new Date().toLocaleTimeString(),
};
setMessages((prev) => [...prev, newMessage, InvalidConfigMessage]);
return;
}
const userMessage: Message = {
id: Date.now().toString(),
content: inputValue,
sender: "user",
timestamp: new Date().toLocaleTimeString(),
};
let fileData;
const handleSend = async (content: string) => {
if (file) {
if (file.type.startsWith("image/")) {
// Convert image to Base64
fileData = await convertToBase64(file);
userMessage.image = fileData;
} else if (file.type.startsWith("audio/")) {
// Convert audio to ArrayBuffer
fileData = await getFileBuffer(file);
userMessage.audio = fileData;
}
}
// Update the state with the new user message
setMessages((prev) => [...prev, userMessage]);
setThinking(true);
// Transform messages into the required format
const messagesForPrompt: LanguageModelV1Prompt = [];
messages.map((message) => {
const messageContent: any = {
role: message.sender,
content: [
{
type: "text",
text: message.content,
},
],
};
if (message.image) {
messageContent.content.push({
type: "image",
image: message.image,
});
}
if (message.audio) {
messageContent.content.push({
type: 'file',
mimeType: 'audio/mpeg',
data: message.audio,
});
}
if(!message.audio) messagesForPrompt.push(messageContent);
});
const newMessage: any = {
role: "user",
content: [
{
type: "text",
text: inputValue,
},
],
};
if (file) {
if (file.type.startsWith("image/")) {
newMessage.content.push({
type: "image",
image: userMessage.image,
});
} else if (file.type.startsWith("audio/")) {
newMessage.content.push({
type: 'file',
mimeType: 'audio/mpeg',
data: userMessage.audio,
});
}
}
messagesForPrompt.push(newMessage);
getMemories(messagesForPrompt);
setFile(null);
setSelectedFile(null);
try {
const { textStream } = await streamText({
model: mem0(getModel(selectedProvider), {
user_id: selectedUser || "",
}),
messages: messagesForPrompt,
await sendMessage(content, {
type: file.type,
data: fileData!,
});
const assistantMessageId = Date.now() + 1;
const assistantMessage: Message = {
id: assistantMessageId.toString(),
content: "",
sender: "assistant",
timestamp: new Date().toLocaleTimeString(),
};
setMessages((prev) => [...prev, assistantMessage]);
// Stream the text part by part
for await (const textPart of textStream) {
assistantMessage.content += textPart;
setThinking(false);
setFile(null);
setSelectedFile(null);
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantMessageId.toString()
? { ...msg, content: assistantMessage.content }
: msg
)
);
}
setThinking(false);
} catch (error) {
console.error("Error in handleSend:", error);
setMessages((prev) => [...prev, SomethingWentWrongMessage]);
setThinking(false);
setFile(null);
setSelectedFile(null);
clearFile();
} else {
await sendMessage(content);
}
};
useEffect(() => {
setMessages([WelcomeMessage]);
}, []);
const setFile = async (newFile: File | null) => {
if (newFile) {
await handleFile(newFile);
} else {
clearFile();
}
};
return (
<GlobalContext.Provider
@@ -295,7 +98,7 @@ const GlobalState = (props: any) => {
selectedFile,
setSelectedFile,
file,
setFile
setFile,
}}
>
{props.children}
@@ -304,21 +107,4 @@ const GlobalState = (props: any) => {
};
export default GlobalContext;
export { GlobalState };
const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string); // Resolve with Base64 string
reader.onerror = error => reject(error); // Reject on error
});
};
async function getFileBuffer(file: any) {
const response = await fetch(file);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
return buffer;
}
export { GlobalState };

View File

@@ -0,0 +1,73 @@
import { useState, useEffect } from 'react';
import { Provider } from '@/constants/messages';
interface UseAuthReturn {
mem0ApiKey: string;
openaiApiKey: string;
provider: Provider;
user: string;
setAuth: (mem0: string, openai: string, provider: Provider) => void;
setUser: (user: string) => void;
clearAuth: () => void;
clearUser: () => void;
}
export const useAuth = (): UseAuthReturn => {
const [mem0ApiKey, setMem0ApiKey] = useState<string>('');
const [openaiApiKey, setOpenaiApiKey] = useState<string>('');
const [provider, setProvider] = useState<Provider>('openai');
const [user, setUser] = useState<string>('');
useEffect(() => {
const mem0 = localStorage.getItem('mem0ApiKey');
const openai = localStorage.getItem('openaiApiKey');
const savedProvider = localStorage.getItem('provider') as Provider;
const savedUser = localStorage.getItem('user');
if (mem0 && openai && savedProvider) {
setAuth(mem0, openai, savedProvider);
}
if (savedUser) {
setUser(savedUser);
}
}, []);
const setAuth = (mem0: string, openai: string, provider: Provider) => {
setMem0ApiKey(mem0);
setOpenaiApiKey(openai);
setProvider(provider);
localStorage.setItem('mem0ApiKey', mem0);
localStorage.setItem('openaiApiKey', openai);
localStorage.setItem('provider', provider);
};
const clearAuth = () => {
localStorage.removeItem('mem0ApiKey');
localStorage.removeItem('openaiApiKey');
localStorage.removeItem('provider');
setMem0ApiKey('');
setOpenaiApiKey('');
setProvider('openai');
};
const updateUser = (user: string) => {
setUser(user);
localStorage.setItem('user', user);
};
const clearUser = () => {
localStorage.removeItem('user');
setUser('');
};
return {
mem0ApiKey,
openaiApiKey,
provider,
user,
setAuth,
setUser: updateUser,
clearAuth,
clearUser,
};
};

View File

@@ -0,0 +1,169 @@
import { useState } from 'react';
import { createMem0, getMemories } from '@mem0/vercel-ai-provider';
import { LanguageModelV1Prompt, streamText } from 'ai';
import { Message, Memory } from '@/types';
import { WELCOME_MESSAGE, INVALID_CONFIG_MESSAGE, ERROR_MESSAGE, AI_MODELS, Provider } from '@/constants/messages';
interface UseChatProps {
user: string;
mem0ApiKey: string;
openaiApiKey: string;
provider: Provider;
}
interface UseChatReturn {
messages: Message[];
memories: Memory[];
thinking: boolean;
sendMessage: (content: string, fileData?: { type: string; data: string | Buffer }) => Promise<void>;
}
interface MemoryResponse {
id: string;
memory: string;
updated_at: string;
categories: string[];
}
type MessageContent =
| { type: 'text'; text: string }
| { type: 'image'; image: string }
| { type: 'file'; mimeType: string; data: Buffer };
interface PromptMessage {
role: string;
content: MessageContent[];
}
export const useChat = ({ user, mem0ApiKey, openaiApiKey, provider }: UseChatProps): UseChatReturn => {
const [messages, setMessages] = useState<Message[]>([WELCOME_MESSAGE]);
const [memories, setMemories] = useState<Memory[]>([]);
const [thinking, setThinking] = useState(false);
const mem0 = createMem0({
provider,
mem0ApiKey,
apiKey: openaiApiKey,
});
const updateMemories = async (messages: LanguageModelV1Prompt) => {
try {
const fetchedMemories = await getMemories(messages, {
user_id: user,
mem0ApiKey,
});
const newMemories = fetchedMemories.map((memory: MemoryResponse) => ({
id: memory.id,
content: memory.memory,
timestamp: memory.updated_at,
tags: memory.categories,
}));
setMemories(newMemories);
} catch (error) {
console.error('Error in getMemories:', error);
}
};
const formatMessagesForPrompt = (messages: Message[]): PromptMessage[] => {
return messages.map((message) => {
const messageContent: MessageContent[] = [
{ type: 'text', text: message.content }
];
if (message.image) {
messageContent.push({
type: 'image',
image: message.image,
});
}
if (message.audio) {
messageContent.push({
type: 'file',
mimeType: 'audio/mpeg',
data: message.audio as Buffer,
});
}
return {
role: message.sender,
content: messageContent,
};
});
};
const sendMessage = async (content: string, fileData?: { type: string; data: string | Buffer }) => {
if (!content.trim() && !fileData) return;
if (!user) {
const newMessage: Message = {
id: Date.now().toString(),
content,
sender: 'user',
timestamp: new Date().toLocaleTimeString(),
};
setMessages((prev) => [...prev, newMessage, INVALID_CONFIG_MESSAGE]);
return;
}
const userMessage: Message = {
id: Date.now().toString(),
content,
sender: 'user',
timestamp: new Date().toLocaleTimeString(),
...(fileData?.type.startsWith('image/') && { image: fileData.data.toString() }),
...(fileData?.type.startsWith('audio/') && { audio: fileData.data as Buffer }),
};
setMessages((prev) => [...prev, userMessage]);
setThinking(true);
const messagesForPrompt = formatMessagesForPrompt([...messages, userMessage]);
await updateMemories(messagesForPrompt as LanguageModelV1Prompt);
try {
const { textStream } = await streamText({
model: mem0(AI_MODELS[provider], {
user_id: user,
}),
messages: messagesForPrompt as LanguageModelV1Prompt,
});
const assistantMessageId = Date.now() + 1;
const assistantMessage: Message = {
id: assistantMessageId.toString(),
content: '',
sender: 'assistant',
timestamp: new Date().toLocaleTimeString(),
};
setMessages((prev) => [...prev, assistantMessage]);
for await (const textPart of textStream) {
assistantMessage.content += textPart;
setThinking(false);
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantMessageId.toString()
? { ...msg, content: assistantMessage.content }
: msg
)
);
}
} catch (error) {
console.error('Error in sendMessage:', error);
setMessages((prev) => [...prev, ERROR_MESSAGE]);
} finally {
setThinking(false);
}
};
return {
messages,
memories,
thinking,
sendMessage,
};
};

View File

@@ -0,0 +1,45 @@
import { useState } from 'react';
import { FileInfo } from '@/types';
import { convertToBase64, getFileBuffer } from '@/utils/fileUtils';
interface UseFileHandlerReturn {
selectedFile: FileInfo | null;
file: File | null;
fileData: string | Buffer | null;
setSelectedFile: (file: FileInfo | null) => void;
handleFile: (file: File) => Promise<void>;
clearFile: () => void;
}
export const useFileHandler = (): UseFileHandlerReturn => {
const [selectedFile, setSelectedFile] = useState<FileInfo | null>(null);
const [file, setFile] = useState<File | null>(null);
const [fileData, setFileData] = useState<string | Buffer | null>(null);
const handleFile = async (file: File) => {
setFile(file);
if (file.type.startsWith('image/')) {
const base64Data = await convertToBase64(file);
setFileData(base64Data);
} else if (file.type.startsWith('audio/')) {
const bufferData = await getFileBuffer(file);
setFileData(bufferData);
}
};
const clearFile = () => {
setSelectedFile(null);
setFile(null);
setFileData(null);
};
return {
selectedFile,
file,
fileData,
setSelectedFile,
handleFile,
clearFile,
};
};

View File

@@ -0,0 +1,16 @@
import { Buffer } from 'buffer';
export const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = error => reject(error);
});
};
export const getFileBuffer = async (file: File): Promise<Buffer> => {
const response = await fetch(URL.createObjectURL(file));
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer);
};