diff --git a/env.example b/env.example index 5b78c1a..fc7d984 100644 --- a/env.example +++ b/env.example @@ -181,6 +181,7 @@ CRON_JOBS_PASSWORD="" # AI # https://mksaas.com/docs/ai # ----------------------------------------------------------------------------- +AI_GATEWAY_API_KEY="" FAL_API_KEY="" FIREWORKS_API_KEY="" OPENAI_API_KEY="" diff --git a/src/app/[locale]/(marketing)/ai/chat/page.tsx b/src/app/[locale]/(marketing)/ai/chat/page.tsx new file mode 100644 index 0000000..eb95e05 --- /dev/null +++ b/src/app/[locale]/(marketing)/ai/chat/page.tsx @@ -0,0 +1,183 @@ +'use client'; + +import { + Conversation, + ConversationContent, + ConversationScrollButton, +} from '@/components/ai-elements/conversation'; +import { Loader } from '@/components/ai-elements/loader'; +import { Message, MessageContent } from '@/components/ai-elements/message'; +import { + PromptInput, + PromptInputButton, + PromptInputModelSelect, + PromptInputModelSelectContent, + PromptInputModelSelectItem, + PromptInputModelSelectTrigger, + PromptInputModelSelectValue, + PromptInputSubmit, + PromptInputTextarea, + PromptInputToolbar, + PromptInputTools, +} from '@/components/ai-elements/prompt-input'; +import { + Reasoning, + ReasoningContent, + ReasoningTrigger, +} from '@/components/ai-elements/reasoning'; +import { Response } from '@/components/ai-elements/response'; +import { + Source, + Sources, + SourcesContent, + SourcesTrigger, +} from '@/components/ai-elements/source'; +import { useChat } from '@ai-sdk/react'; +import { GlobeIcon } from 'lucide-react'; +import { useState } from 'react'; + +const models = [ + { + name: 'GPT 4o', + value: 'openai/gpt-4o', + }, + { + name: 'Deepseek R1', + value: 'deepseek/deepseek-r1', + }, +]; + +const ChatBotDemo = () => { + const [input, setInput] = useState(''); + const [model, setModel] = useState(models[0].value); + const [webSearch, setWebSearch] = useState(false); + const { messages, sendMessage, status } = useChat(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (input.trim()) { + sendMessage( + { text: input }, + { + body: { + model: model, + webSearch: webSearch, + }, + } + ); + setInput(''); + } + }; + + return ( +
+
+ + + {messages.map((message) => ( +
+ {message.role === 'assistant' && ( + + {message.parts.map((part, i) => { + switch (part.type) { + case 'source-url': + return ( + <> + part.type === 'source-url' + ).length + } + /> + + + + + ); + } + })} + + )} + + + {message.parts.map((part, i) => { + switch (part.type) { + case 'text': + return ( + + {part.text} + + ); + case 'reasoning': + return ( + + + {part.text} + + ); + default: + return null; + } + })} + + +
+ ))} + {status === 'submitted' && } +
+ +
+ + + setInput(e.target.value)} + value={input} + /> + + + setWebSearch(!webSearch)} + > + + Search + + { + setModel(value); + }} + value={model} + > + + + + + {models.map((model) => ( + + {model.name} + + ))} + + + + + + +
+
+ ); +}; + +export default ChatBotDemo; diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts new file mode 100644 index 0000000..2dae7a1 --- /dev/null +++ b/src/app/api/chat/route.ts @@ -0,0 +1,26 @@ +import { type UIMessage, convertToModelMessages, streamText } from 'ai'; + +// Allow streaming responses up to 30 seconds +export const maxDuration = 30; + +export async function POST(req: Request) { + const { + messages, + model, + webSearch, + }: { messages: UIMessage[]; model: string; webSearch: boolean } = + await req.json(); + + const result = streamText({ + model: webSearch ? 'perplexity/sonar' : model, + messages: convertToModelMessages(messages), + system: + 'You are a helpful assistant that can answer questions and help with tasks', + }); + + // send sources and reasoning back to the client + return result.toUIMessageStreamResponse({ + sendSources: true, + sendReasoning: true, + }); +}