├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── app ├── action.tsx ├── config.tsx ├── error.tsx ├── globals.css ├── layout.tsx └── page.tsx ├── bun.lockb ├── components.json ├── components ├── AttributionComponent.tsx ├── Beams.tsx ├── GeneratedUI.tsx ├── Generations.tsx ├── Hero.tsx ├── InputComponent.tsx ├── Settings.tsx ├── Spinner.tsx ├── StreamText.tsx ├── Suggestions.tsx ├── tools │ ├── Clock.tsx │ ├── Spotify.tsx │ └── Weather.tsx └── ui │ ├── button.tsx │ ├── dropdown-menu.tsx │ └── switch.tsx ├── lib ├── types.ts └── utils.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public └── loading.svg ├── tailwind.config.ts ├── tsconfig.json └── utils ├── answerEngine.tsx ├── chatCompletionWithTools.tsx ├── generateChatCompletion.tsx ├── generateTTS.tsx ├── processImage.tsx ├── rateLimiting.tsx ├── tools ├── getSpotify.tsx ├── getTime.tsx └── getWeather.tsx └── transcribeAudio.tsx /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Achyut Krishna Byanjankar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /app/action.tsx: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import { createAI, createStreamableValue } from "ai/rsc"; 3 | import { config } from "./config"; 4 | import dotenv from "dotenv"; 5 | dotenv.config(); 6 | import { Ratelimit } from "@upstash/ratelimit"; 7 | import { Redis } from "@upstash/redis"; 8 | import { headers } from "next/headers"; 9 | import { answerEngine } from "@/utils/answerEngine"; 10 | import { chatCompletionWithTools } from "@/utils/chatCompletionWithTools"; 11 | import { generateChatCompletion } from "@/utils/generateChatCompletion"; 12 | import { generateTTS } from "@/utils/generateTTS"; 13 | import { 14 | processImageWithLllavaOnFalAI, 15 | processImageWithGPT4V, 16 | } from "@/utils/processImage"; 17 | import { initializeRateLimit, checkRateLimit } from "@/utils/rateLimiting"; 18 | import { transcribeAudio } from "@/utils/transcribeAudio"; 19 | let ratelimit: Ratelimit | undefined; 20 | if (config.useRateLimiting) { 21 | ratelimit = new Ratelimit({ 22 | redis: Redis.fromEnv(), 23 | limiter: Ratelimit.slidingWindow(10, "10 m"), 24 | }); 25 | } 26 | 27 | async function action(obj: FormData): Promise { 28 | "use server"; 29 | const streamable = createStreamableValue(); 30 | (async () => { 31 | if (config.useRateLimiting) { 32 | const identifier = 33 | headers().get("x-forwarded-for") || 34 | headers().get("x-real-ip") || 35 | headers().get("cf-connecting-ip") || 36 | headers().get("client-ip") || 37 | ""; 38 | initializeRateLimit(); 39 | if (!(await checkRateLimit(identifier))) 40 | return streamable.done({ 41 | result: "Rate Limit Reached. Please try again later.", 42 | }); 43 | } 44 | const formData = obj; 45 | const audioBlob = formData.get("audio"); 46 | const useTTS = formData.get("useTTS") === "true"; 47 | const useInternet = formData.get("useInternet") === "true"; 48 | const usePhotos = formData.get("usePhotos") === "true"; 49 | const useBasicMode = formData.get("useBasicMode") === "true"; 50 | 51 | const timestamp = Date.now(); 52 | const transcription = audioBlob 53 | ? await transcribeAudio(audioBlob as Blob, timestamp) 54 | : (formData.get("text") as string); 55 | streamable.update({ transcription: transcription }); 56 | 57 | let responseText = ""; 58 | if (useBasicMode) { 59 | const result = await generateChatCompletion(transcription); 60 | if (result !== undefined) { 61 | responseText = result; 62 | } 63 | } else { 64 | if (usePhotos) { 65 | const image = formData.get("image"); 66 | if (image instanceof File) { 67 | if (config.visionModelProvider === "fal.ai") { 68 | responseText = await processImageWithLllavaOnFalAI( 69 | image, 70 | transcription, 71 | ); 72 | } else { 73 | responseText = await processImageWithGPT4V(image, transcription); 74 | } 75 | } else { 76 | responseText = "You might have forgotten to upload an image"; 77 | } 78 | } else { 79 | let result; 80 | if (useInternet) { 81 | result = await answerEngine(transcription); 82 | } else { 83 | result = await generateChatCompletion(transcription); 84 | } 85 | 86 | if (result !== undefined) { 87 | responseText = result; 88 | } 89 | 90 | const tool_results = await chatCompletionWithTools(responseText); 91 | 92 | if (tool_results?.uiComponent) { 93 | if (tool_results.uiComponent.component === "weather") { 94 | streamable.update({ weather: tool_results.uiComponent.data }); 95 | } else if (tool_results.uiComponent.component === "spotify") { 96 | streamable.update({ spotify: tool_results.uiComponent.data }); 97 | } else if (tool_results.uiComponent.component === "time") { 98 | responseText = tool_results.uiComponent.data; 99 | streamable.update({ time: tool_results.uiComponent.data }); 100 | } 101 | } else { 102 | streamable.update({ message: tool_results?.message }); 103 | } 104 | } 105 | } 106 | 107 | streamable.update({ result: responseText }); 108 | useTTS && streamable.update({ audio: await generateTTS(responseText) }); 109 | streamable.done({ status: "done" }); 110 | })(); 111 | return streamable.value; 112 | } 113 | 114 | const initialAIState: { 115 | role: "user" | "assistant" | "system" | "function"; 116 | content: string; 117 | id?: string; 118 | name?: string; 119 | }[] = []; 120 | 121 | const initialUIState: { 122 | text: string; 123 | id?: string; 124 | }[] = []; 125 | 126 | export const AI = createAI({ 127 | actions: { action }, 128 | initialAIState, 129 | initialUIState, 130 | }); 131 | -------------------------------------------------------------------------------- /app/config.tsx: -------------------------------------------------------------------------------- 1 | export const config = { 2 | inferenceModelProvider: "groq", // 'groq' or 'openai' 3 | inferenceModel: "llama3-8b-8192", // Groq: 'llama3-70b-8192' or 'llama3-8b-8192'.. OpenAI: 'gpt-4-turbo etc 4 | 5 | whisperModelProvider: "openai", // 'groq' or 'openai' 6 | whisperModel: "whisper-1", // Groq: 'whisper-large-v3' OpenAI: 'whisper-1' 7 | 8 | ttsModelProvider: "openai", // only openai supported for now... 9 | ttsModel: "tts-1", // only openai supported for now... 10 | ttsvoice: "alloy", // only openai supported for now... [alloy, echo, fable, onyx, nova, and shimmer] 11 | 12 | visionModelProvider: "openai", // 'openai' or 'fal.ai' 13 | visionModel: "gpt-4o", // OpenAI: 'gpt-4-turbo' Fal.ai: 'llava-next' 14 | 15 | functionCallingModelProvider: "openai", // 'openai' current only 16 | functionCallingModel: "gpt-3.5-turbo", // OpenAI: 'gpt-3-5-turbo' 17 | 18 | enableResponseTimes: true, // Display response times for each message 19 | enableSettingsUIToggle: true, // Display the settings UI toggle 20 | enableTextToSpeechUIToggle: true, // Display the text to speech UI toggle 21 | enableInternetResultsUIToggle: true, // Display the internet results UI toggle 22 | enableUsePhotUIToggle: true, // Display the use photo UI toggle 23 | enabledRabbitMode: true, // Enable the rabbit mode UI toggle 24 | enabledBasicMode: true, // Enable the basic mode UI toggle 25 | useAttributionComponent: false, // Use the attribution component to display the attribution of the AI models/services used 26 | 27 | useRateLimiting: false, // Use Upstash rate limiting to limit the number of requests per user 28 | 29 | useLangSmith: true, // Use LangSmith by Langchain to trace the execution of the functions in the config.tsx set to true to use. 30 | 31 | searchEngine: "BRAVE", // Use Brave or Serper 32 | }; 33 | -------------------------------------------------------------------------------- /app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | 4 | export default function Error({ 5 | error, 6 | reset, 7 | }: { 8 | error: Error & { digest?: string }; 9 | reset: () => void; 10 | }) { 11 | return ( 12 |
13 |

14 | Rate limit reached! Please try again in a while. 15 |

16 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body, 7 | :root { 8 | height: 100%; 9 | } 10 | 11 | @layer base { 12 | :root { 13 | --background: 0 0% 100%; 14 | --foreground: 0 0% 3.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 0 0% 3.9%; 18 | 19 | --popover: 0 0% 100%; 20 | --popover-foreground: 0 0% 3.9%; 21 | 22 | --primary: 0 0% 9%; 23 | --primary-foreground: 0 0% 98%; 24 | 25 | --secondary: 0 0% 96.1%; 26 | --secondary-foreground: 0 0% 9%; 27 | 28 | --muted: 0 0% 96.1%; 29 | --muted-foreground: 0 0% 45.1%; 30 | 31 | --accent: 0 0% 96.1%; 32 | --accent-foreground: 0 0% 9%; 33 | 34 | --destructive: 0 84.2% 60.2%; 35 | --destructive-foreground: 0 0% 98%; 36 | 37 | --border: 0 0% 89.8%; 38 | --input: 0 0% 89.8%; 39 | --ring: 0 0% 3.9%; 40 | 41 | --radius: 0.5rem; 42 | } 43 | 44 | .dark { 45 | --background: 0 0% 3.9%; 46 | --foreground: 0 0% 98%; 47 | 48 | --card: 0 0% 3.9%; 49 | --card-foreground: 0 0% 98%; 50 | 51 | --popover: 0 0% 3.9%; 52 | --popover-foreground: 0 0% 98%; 53 | 54 | --primary: 0 0% 98%; 55 | --primary-foreground: 0 0% 9%; 56 | 57 | --secondary: 0 0% 14.9%; 58 | --secondary-foreground: 0 0% 98%; 59 | 60 | --muted: 0 0% 14.9%; 61 | --muted-foreground: 0 0% 63.9%; 62 | 63 | --accent: 0 0% 14.9%; 64 | --accent-foreground: 0 0% 98%; 65 | 66 | --destructive: 0 62.8% 30.6%; 67 | --destructive-foreground: 0 0% 98%; 68 | 69 | --border: 0 0% 14.9%; 70 | --input: 0 0% 14.9%; 71 | --ring: 0 0% 83.1%; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | 80 | body { 81 | @apply bg-background text-foreground; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { ThemeProvider } from "next-themes"; 4 | import { AI } from "./action"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Mimir", 8 | description: "ask anything.", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: Readonly<{ 14 | children: React.ReactNode; 15 | }>) { 16 | return ( 17 | 18 | 19 | 20 | 21 |
22 | {children} 23 |
24 |
25 |
26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import InputComponent from "@/components/InputComponent"; 3 | import { useActions, readStreamableValue } from "ai/rsc"; 4 | import React, { useEffect, useState } from "react"; 5 | import { AI } from "./action"; 6 | import { UIComponent, Message } from "@/lib/types"; 7 | import Generation from "@/components/Generations"; 8 | import { Settings } from "@/components/Settings"; 9 | import Spinner from "@/components/Spinner"; 10 | 11 | const Main = () => { 12 | const { action } = useActions(); 13 | const [useBasicMode, setUseBasicMode] = useState(false); 14 | const [useTTS, setUseTTS] = useState(true); 15 | const [useInternet, setUseInternet] = useState(true); 16 | const [usePhotos, setUsePhotos] = useState(false); 17 | const [useRabbitMode, setuseRabbitMode] = useState(false); 18 | const [useSpotify, setUseSpotify] = useState(""); 19 | const [currentTranscription, setCurrentTranscription] = useState< 20 | | { 21 | transcription: string; 22 | responseTime: number; 23 | } 24 | | null 25 | | undefined 26 | >(undefined); 27 | const [totalResponseTime, setTotalResponseTime] = useState( 28 | null, 29 | ); 30 | const [currentUIComponent, setCurrentUIComponent] = 31 | useState(null); 32 | const [message, setMessage] = useState< 33 | | { 34 | message: string; 35 | responseTime: number; 36 | } 37 | | null 38 | | undefined 39 | >(undefined); 40 | const [isClient, setIsClient] = useState(false); 41 | 42 | useEffect(() => { 43 | setIsClient(true); 44 | }, []); 45 | 46 | if (!isClient) { 47 | return ( 48 |
49 | 50 |
51 | ); 52 | } 53 | 54 | const handleClick = (suggestion: string) => { 55 | const formData = new FormData(); 56 | formData.append("text", suggestion); 57 | formData.append("useTTS", String(useTTS)); 58 | formData.append("useInternet", String(useInternet)); 59 | formData.append("usePhotos", String(usePhotos)); 60 | formData.append("useBasicMode", String(useBasicMode)); 61 | handleSubmit(formData); 62 | }; 63 | 64 | const handleSubmit = async (formData: FormData) => { 65 | const startTime = Date.now(); 66 | const streamableValue = await action(formData); 67 | let transcriptionResponseTime; 68 | let transcriptionCompletionTime; 69 | let messageResponseTime; 70 | let audioResponseTime; 71 | setCurrentUIComponent(null); 72 | setMessage(null); 73 | 74 | for await (const message of readStreamableValue(streamableValue)) { 75 | if ( 76 | message && 77 | message.rateLimitReached && 78 | typeof message.rateLimitReached === "string" 79 | ) { 80 | setMessage({ message: message.rateLimitReached, responseTime: 0 }); 81 | } 82 | if (message && message.time && typeof message.time === "string") { 83 | setCurrentUIComponent({ component: "time", data: message.time }); 84 | } 85 | if ( 86 | message && 87 | message.transcription && 88 | typeof message.transcription === "string" 89 | ) { 90 | transcriptionResponseTime = (Date.now() - startTime) / 1000; 91 | transcriptionCompletionTime = Date.now(); 92 | setCurrentTranscription({ 93 | transcription: message.transcription, 94 | responseTime: transcriptionResponseTime, 95 | }); 96 | } 97 | if (message && message.weather && typeof message.weather === "string") { 98 | setCurrentUIComponent({ 99 | component: "weather", 100 | data: JSON.parse(message.weather), 101 | }); 102 | } 103 | if (message && message.result && typeof message.result === "string") { 104 | messageResponseTime = 105 | (Date.now() - (transcriptionCompletionTime || startTime)) / 1000; 106 | setMessage({ 107 | message: message.result, 108 | responseTime: messageResponseTime, 109 | }); 110 | } 111 | if (message && message.audio && typeof message.audio === "string") { 112 | audioResponseTime = 113 | (Date.now() - (transcriptionCompletionTime || startTime)) / 1000; 114 | const audio = new Audio(message.audio); 115 | audio.play(); 116 | } 117 | if (message && message.spotify && typeof message.spotify === "string") { 118 | setUseSpotify(message.spotify); 119 | } 120 | } 121 | 122 | let totalResponseTime = 0; 123 | if (transcriptionResponseTime) { 124 | totalResponseTime += transcriptionResponseTime; 125 | } 126 | if (messageResponseTime) { 127 | totalResponseTime += messageResponseTime; 128 | } 129 | if (audioResponseTime) { 130 | totalResponseTime += audioResponseTime; 131 | } 132 | setTotalResponseTime(totalResponseTime); 133 | }; 134 | 135 | const handleReset = () => { 136 | setTotalResponseTime(null); 137 | setCurrentTranscription(null); 138 | setCurrentUIComponent(null); 139 | setMessage(undefined); 140 | }; 141 | 142 | return ( 143 |
144 |
148 | Mimir 149 |
150 | 159 | 167 | setUseBasicMode(!useBasicMode)} 174 | onTTSToggle={() => setUseTTS(!useTTS)} 175 | onInternetToggle={() => setUseInternet(!useInternet)} 176 | onPhotosToggle={() => setUsePhotos(!usePhotos)} 177 | onRabbitModeToggle={() => setuseRabbitMode(!useRabbitMode)} 178 | setBasicMode={setUseBasicMode} 179 | setTTS={setUseTTS} 180 | setInternet={setUseInternet} 181 | setPhotos={setUsePhotos} 182 | /> 183 |
184 | ); 185 | }; 186 | 187 | export default Main; 188 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/achyutbenz19/mimir/1721d4f389922a84e7fdb09d436fd3e2461c0e30/bun.lockb -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/AttributionComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { config } from "@/app/config"; 3 | import { AttributionComponentProps } from "@/lib/types"; 4 | 5 | export const AttributionComponent: React.FC = ({ 6 | usePhotos, 7 | useInternet, 8 | useTTS, 9 | }) => { 10 | const { 11 | whisperModelProvider, 12 | whisperModel, 13 | inferenceModelProvider, 14 | inferenceModel, 15 | ttsModelProvider, 16 | visionModelProvider, 17 | visionModel, 18 | useLangSmith, 19 | } = config; 20 | 21 | return ( 22 |
23 | {usePhotos && !useInternet && ( 24 | <> 25 | 26 | {whisperModel} by {whisperModelProvider} 27 | {" "} 28 | on{" "} 29 | 30 | groq 31 | 32 | , 33 | 34 | {" "} 35 | {visionModel} 36 | 37 | {useTTS && `, tts from ${ttsModelProvider}`}{" "} 38 | 39 | {useLangSmith ? "langsmith" : ""} 40 | 41 | , rate limiting by upstash redis. 42 | 43 | )} 44 | {usePhotos && useInternet && ( 45 | <> 46 | 50 | {whisperModel} by {whisperModelProvider} 51 | {" "} 52 | on{" "} 53 | 54 | groq 55 | 56 | , 57 | 58 | {" "} 59 | {visionModel} 60 | 61 | {useTTS && `, tts from ${ttsModelProvider}`}{" "} 62 | 63 | {useLangSmith ? "langsmith" : ""} 64 | 65 | , rate limiting by upstash redis. 66 | 67 | )} 68 | {!usePhotos && !useInternet && ( 69 | <> 70 | 74 | {whisperModel} by {whisperModelProvider} 75 | {" "} 76 | on{" "} 77 | 78 | groq 79 | 80 | ,{" "} 81 | 85 | {inferenceModel} by {inferenceModelProvider} 86 | {" "} 87 | on{" "} 88 | 89 | groq 90 | 91 | {useTTS && `, tts from ${ttsModelProvider}`}{" "} 92 | 93 | {useLangSmith ? "observability by langsmith" : ""} 94 | 95 | , rate limiting by upstash redis. 96 | 97 | )} 98 | {!usePhotos && useInternet && ( 99 | <> 100 | 104 | {whisperModel} by {whisperModelProvider} 105 | {" "} 106 | on{" "} 107 | 108 | groq 109 | 110 | ,{" "} 111 | 115 | {inferenceModel} by {inferenceModelProvider} 116 | {" "} 117 | on{" "} 118 | 119 | groq 120 | 121 | {useTTS && `, tts from ${ttsModelProvider}`}, internet search by{" "} 122 | 123 | serper, 124 | {" "} 125 | observability by{" "} 126 | 127 | langsmith. 128 | 129 | 130 | )} 131 |
132 | ); 133 | }; 134 | -------------------------------------------------------------------------------- /components/Beams.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { motion } from "framer-motion"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | export const BackgroundBeams = React.memo( 7 | ({ className }: { className?: string }) => { 8 | const paths = [ 9 | "M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875", 10 | "M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867", 11 | "M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859", 12 | "M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851", 13 | "M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843", 14 | "M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835", 15 | "M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827", 16 | "M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819", 17 | "M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811", 18 | "M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803", 19 | "M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795", 20 | "M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787", 21 | "M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779", 22 | "M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771", 23 | "M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763", 24 | "M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755", 25 | "M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747", 26 | "M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739", 27 | "M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731", 28 | "M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723", 29 | "M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715", 30 | "M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707", 31 | "M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699", 32 | "M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691", 33 | "M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683", 34 | "M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675", 35 | "M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667", 36 | "M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659", 37 | "M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651", 38 | "M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643", 39 | "M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635", 40 | "M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627", 41 | "M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619", 42 | "M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611", 43 | "M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603", 44 | "M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595", 45 | "M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587", 46 | "M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579", 47 | "M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571", 48 | "M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563", 49 | "M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555", 50 | "M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547", 51 | "M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539", 52 | "M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531", 53 | "M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523", 54 | "M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515", 55 | "M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507", 56 | "M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499", 57 | "M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491", 58 | "M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483", 59 | ]; 60 | return ( 61 |
67 | 75 | 81 | 82 | {paths.map((path, index) => ( 83 | 90 | ))} 91 | 92 | {paths.map((path, index) => ( 93 | 115 | 116 | 117 | 118 | 119 | 120 | ))} 121 | 122 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 | ); 138 | }, 139 | ); 140 | 141 | BackgroundBeams.displayName = "BackgroundBeams"; 142 | -------------------------------------------------------------------------------- /components/GeneratedUI.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { GeneratedUIProps } from "@/lib/types"; 3 | import { SpotifyTrack } from "./tools/Spotify"; 4 | import { WeatherData } from "./tools/Weather"; 5 | import { ClockComponent } from "./tools/Clock"; 6 | 7 | const GeneratedUI = ({ currentUIComponent, useSpotify }: GeneratedUIProps) => { 8 | return ( 9 |
10 | {useSpotify && ( 11 |
12 | 13 |
14 | )} 15 | {currentUIComponent && currentUIComponent.component === "weather" && ( 16 |
17 | 18 |
19 | )} 20 | {currentUIComponent && currentUIComponent.component === "time" && ( 21 | 22 | )} 23 |
24 | ); 25 | }; 26 | 27 | export default GeneratedUI; 28 | -------------------------------------------------------------------------------- /components/Generations.tsx: -------------------------------------------------------------------------------- 1 | import { GeneratedUIProps } from "@/lib/types"; 2 | import GeneratedUI from "./GeneratedUI"; 3 | import { StreamText } from "./StreamText"; 4 | import Hero from "./Hero"; 5 | import Spinner from "./Spinner"; 6 | import { Gauge } from "lucide-react"; 7 | 8 | const Generation = ({ 9 | transcription, 10 | message, 11 | useSpotify, 12 | currentUIComponent, 13 | totalResponseTime, 14 | handleClick, 15 | }: GeneratedUIProps) => { 16 | 17 | if (message === null) { 18 | return ( 19 |
20 | 21 |
22 | ); 23 | } 24 | 25 | return ( 26 |
27 | {message && ( 28 | <> 29 |
30 | 31 |
32 | 36 |
37 | {transcription} 38 |
39 | 40 | )} 41 | {message === undefined && } 42 |
43 | {totalResponseTime && ( 44 |
45 | 46 | {totalResponseTime.toFixed(3)}s 47 |
48 | )} 49 |
50 |
51 | ); 52 | }; 53 | 54 | export default Generation; 55 | -------------------------------------------------------------------------------- /components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BackgroundBeams } from "./Beams"; 3 | import Suggestions from "./Suggestions"; 4 | import { ClickProps } from "@/lib/types"; 5 | 6 | const Hero = ({ handleClick }: ClickProps) => { 7 | return ( 8 | <> 9 |
10 | Mimir 11 | 12 | ask anything. 13 | 14 |
15 | 16 |
17 |
18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Hero; 24 | -------------------------------------------------------------------------------- /components/InputComponent.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState, useRef } from "react"; 3 | import { Button } from "./ui/button"; 4 | import { FileUp, FileX2, MicIcon } from "lucide-react"; 5 | import { motion } from "framer-motion"; 6 | import { InputComponentProps } from "@/lib/types"; 7 | import { cn } from "@/lib/utils"; 8 | 9 | const InputComponent: React.FC = ({ 10 | onSubmit, 11 | useTTS, 12 | useInternet, 13 | usePhotos, 14 | useBasicMode, 15 | }) => { 16 | const [selectedImage, setSelectedImage] = useState(null); 17 | const [recording, setRecording] = useState(false); 18 | const mediaRecorderRef = useRef(null); 19 | const chunksRef = useRef([]); 20 | const fileInputRef = useRef(null); 21 | 22 | const handleFileChange = (event: React.ChangeEvent) => { 23 | const files = event.target.files; 24 | if (files && files[0]) { 25 | setSelectedImage(files[0]); 26 | } 27 | }; 28 | 29 | const toggleRecording = () => { 30 | if (recording) { 31 | stopRecording(); 32 | } else { 33 | startRecording(); 34 | } 35 | setRecording(!recording); 36 | }; 37 | 38 | const startRecording = () => { 39 | navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { 40 | const options = { mimeType: "audio/webm" }; 41 | mediaRecorderRef.current = new MediaRecorder(stream, options); 42 | mediaRecorderRef.current.addEventListener( 43 | "dataavailable", 44 | (event: BlobEvent) => { 45 | chunksRef.current.push(event.data); 46 | }, 47 | ); 48 | mediaRecorderRef.current.start(); 49 | }); 50 | }; 51 | 52 | const stopRecording = async () => { 53 | if (mediaRecorderRef.current) { 54 | mediaRecorderRef.current.stop(); 55 | mediaRecorderRef.current.addEventListener("stop", async () => { 56 | const audioBlob = new Blob(chunksRef.current, { type: "audio/webm" }); 57 | const formData = new FormData(); 58 | formData.append("audio", audioBlob); 59 | formData.append("useTTS", String(useTTS)); 60 | formData.append("useInternet", String(useInternet)); 61 | formData.append("usePhotos", String(usePhotos)); 62 | formData.append("useBasicMode", String(useBasicMode)); 63 | if (selectedImage) { 64 | formData.append("image", selectedImage, selectedImage.name); 65 | } 66 | onSubmit(formData); 67 | chunksRef.current = []; 68 | }); 69 | } 70 | }; 71 | 72 | return ( 73 | <> 74 |
75 | {recording && ( 76 | 82 | Listening... 83 | 84 | )} 85 |
86 | 96 |
97 |
98 |
99 | 106 |
107 | {usePhotos && ( 108 | 119 | )} 120 | 121 | ); 122 | }; 123 | 124 | export default InputComponent; 125 | -------------------------------------------------------------------------------- /components/Settings.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DropdownMenu, 3 | DropdownMenuContent, 4 | DropdownMenuItem, 5 | DropdownMenuSeparator, 6 | DropdownMenuTrigger, 7 | } from "@/components/ui/dropdown-menu"; 8 | import { SettingsIcon } from "lucide-react"; 9 | import { Button } from "./ui/button"; 10 | import { SettingsProps } from "@/lib/types"; 11 | import { MouseEvent } from "react"; 12 | import { Switch } from "./ui/switch"; 13 | 14 | export const Settings: React.FC = ({ 15 | useTTS, 16 | useInternet, 17 | usePhotos, 18 | useBasicMode, 19 | onTTSToggle, 20 | onInternetToggle, 21 | onPhotosToggle, 22 | onBasicModeToggle, 23 | setBasicMode, 24 | setTTS, 25 | setInternet, 26 | setPhotos, 27 | }) => { 28 | const handleEvent = (e: MouseEvent, type: string) => { 29 | e.preventDefault(); 30 | switch (type) { 31 | case "tts": 32 | onTTSToggle(); 33 | setBasicMode(false); 34 | break; 35 | case "internet": 36 | onInternetToggle(); 37 | setBasicMode(false); 38 | break; 39 | case "photos": 40 | onPhotosToggle(); 41 | setBasicMode(false); 42 | break; 43 | case "basic": 44 | onBasicModeToggle(); 45 | if (!useBasicMode) { 46 | setTTS(false); 47 | setInternet(false); 48 | setPhotos(false); 49 | } 50 | break; 51 | } 52 | }; 53 | 54 | console.log(useTTS); 55 | 56 | return ( 57 |
58 | 59 | 60 | 63 | 64 | 65 | handleEvent(e, "basic")} 68 | > 69 | 73 | Basic 74 | 75 | 76 | handleEvent(e, "tts")} 79 | > 80 | 81 | TTS 82 | 83 | handleEvent(e, "internet")} 86 | > 87 | 88 | Internet 89 | 90 | {/* handleEvent(e, "photos")} 93 | > 94 | 95 | Photos 96 | */} 97 | 98 | 99 |
100 | ); 101 | }; 102 | -------------------------------------------------------------------------------- /components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | const Spinner = () => { 4 | return spinner; 5 | }; 6 | 7 | export default Spinner; 8 | -------------------------------------------------------------------------------- /components/StreamText.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect } from "react"; 3 | import { motion, stagger, useAnimate } from "framer-motion"; 4 | 5 | export const StreamText = ({ 6 | words, 7 | className, 8 | }: { 9 | words: string; 10 | className?: string; 11 | }) => { 12 | const [scope, animate] = useAnimate(); 13 | let wordsArray = words.split(" "); 14 | useEffect(() => { 15 | animate( 16 | "span", 17 | { 18 | opacity: 1, 19 | }, 20 | { 21 | duration: 1, 22 | delay: stagger(0.1), 23 | }, 24 | ); 25 | }, [scope.current]); 26 | 27 | const renderWords = () => { 28 | return ( 29 | 30 | {wordsArray.map((word, idx) => { 31 | return ( 32 | 33 | {word}{" "} 34 | 35 | ); 36 | })} 37 | 38 | ); 39 | }; 40 | 41 | return
{renderWords()}
; 42 | }; 43 | -------------------------------------------------------------------------------- /components/Suggestions.tsx: -------------------------------------------------------------------------------- 1 | import { ClickProps } from "@/lib/types"; 2 | 3 | export const suggestions = [ 4 | "What is the time right now?", 5 | 'Play "Blinding Lights" by the Weeknd.', 6 | "What is the current weather in Chicago?", 7 | ]; 8 | 9 | const Suggestions = ({ handleClick }: ClickProps) => { 10 | return ( 11 |
12 | {suggestions.map((suggestion, index) => ( 13 |
handleClick(suggestion)} 17 | > 18 | {suggestion} 19 |
20 | ))} 21 |
22 | ); 23 | }; 24 | 25 | export default Suggestions; 26 | -------------------------------------------------------------------------------- /components/tools/Clock.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import Clock from "react-clock"; 3 | import "react-clock/dist/Clock.css"; 4 | 5 | export function ClockComponent() { 6 | const [value, setValue] = useState(new Date()); 7 | 8 | useEffect(() => { 9 | const interval = setInterval(() => setValue(new Date()), 1000); 10 | return () => { 11 | clearInterval(interval); 12 | }; 13 | }, []); 14 | 15 | return ; 16 | } 17 | -------------------------------------------------------------------------------- /components/tools/Spotify.tsx: -------------------------------------------------------------------------------- 1 | export const SpotifyTrack = ({ trackId }: { trackId: string }) => { 2 | if (!trackId) { 3 | return null; 4 | } 5 | 6 | return ( 7 |