import styles from "./chatInputArea.module.css"; import React, { useEffect, useRef, useState } from "react"; import { uploadDataForIndexing } from "../../common/chatFunctions"; import { Progress } from "@/components/ui/progress"; import "katex/dist/katex.min.css"; import { ArrowRight, ArrowUp, Browser, ChatsTeardrop, GlobeSimple, Gps, Image, Microphone, Notebook, Paperclip, Question, Robot, Shapes, Stop, } from "@phosphor-icons/react"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@/components/ui/command"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Popover, PopoverContent } from "@/components/ui/popover"; import { PopoverTrigger } from "@radix-ui/react-popover"; import LoginPrompt from "../loginPrompt/loginPrompt"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { InlineLoading } from "../loading/loading"; import { convertToBGClass } from "@/app/common/colorUtils"; export interface ChatOptions { [key: string]: string; } interface ChatInputProps { sendMessage: (message: string) => void; sendDisabled: boolean; setUploadedFiles?: (files: string[]) => void; conversationId?: string | null; chatOptionsData?: ChatOptions | null; isMobileWidth?: boolean; isLoggedIn: boolean; agentColor?: string; } export default function ChatInputArea(props: ChatInputProps) { const [message, setMessage] = useState(""); const fileInputRef = useRef(null); const [warning, setWarning] = useState(null); const [error, setError] = useState(null); const [uploading, setUploading] = useState(false); const [loginRedirectMessage, setLoginRedirectMessage] = useState(null); const [showLoginPrompt, setShowLoginPrompt] = useState(false); const [recording, setRecording] = useState(false); const [mediaRecorder, setMediaRecorder] = useState(null); const [progressValue, setProgressValue] = useState(0); useEffect(() => { if (!uploading) { setProgressValue(0); } if (uploading) { const interval = setInterval(() => { setProgressValue((prev) => { const increment = Math.floor(Math.random() * 5) + 1; // Generates a random number between 1 and 5 const nextValue = prev + increment; return nextValue < 100 ? nextValue : 100; // Ensures progress does not exceed 100 }); }, 800); return () => clearInterval(interval); } }, [uploading]); function onSendMessage() { if (!message.trim()) return; if (!props.isLoggedIn) { setLoginRedirectMessage( "Hey there, you need to be signed in to send messages to Khoj AI", ); setShowLoginPrompt(true); return; } props.sendMessage(message.trim()); setMessage(""); } function handleSlashCommandClick(command: string) { setMessage(`/${command} `); } function handleFileButtonClick() { if (!fileInputRef.current) return; fileInputRef.current.click(); } function handleFileChange(event: React.ChangeEvent) { if (!event.target.files) return; if (!props.isLoggedIn) { setLoginRedirectMessage("Whoa! You need to login to upload files"); setShowLoginPrompt(true); return; } uploadDataForIndexing( event.target.files, setWarning, setUploading, setError, props.setUploadedFiles, props.conversationId, ); } function getIconForSlashCommand(command: string) { const className = "h-4 w-4 mr-2"; if (command.includes("summarize")) { return ; } if (command.includes("help")) { return ; } if (command.includes("automation")) { return ; } if (command.includes("webpage")) { return ; } if (command.includes("notes")) { return ; } if (command.includes("image")) { return ; } if (command.includes("default")) { return ; } if (command.includes("general")) { return ; } if (command.includes("online")) { return ; } return ; } // Assuming this function is added within the same context as the provided excerpt async function startRecordingAndTranscribe() { try { const microphone = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(microphone, { mimeType: "audio/webm" }); const audioChunks: Blob[] = []; mediaRecorder.ondataavailable = async (event) => { audioChunks.push(event.data); const audioBlob = new Blob(audioChunks, { type: "audio/webm" }); const formData = new FormData(); formData.append("file", audioBlob); // Send the incremental audio blob to the server try { const response = await fetch("/api/transcribe", { method: "POST", body: formData, }); if (!response.ok) { throw new Error("Network response was not ok"); } const transcription = await response.json(); setMessage(transcription.text.trim()); } catch (error) { console.error("Error sending audio to server:", error); } }; // Send an audio blob every 1.5 seconds mediaRecorder.start(1500); mediaRecorder.onstop = async () => { const audioBlob = new Blob(audioChunks, { type: "audio/webm" }); const formData = new FormData(); formData.append("file", audioBlob); // Send the audio blob to the server try { const response = await fetch("/api/transcribe", { method: "POST", body: formData, }); if (!response.ok) { throw new Error("Network response was not ok"); } const transcription = await response.json(); mediaRecorder.stream.getTracks().forEach((track) => track.stop()); setMediaRecorder(null); setMessage(transcription.text.trim()); } catch (error) { console.error("Error sending audio to server:", error); } }; setMediaRecorder(mediaRecorder); } catch (error) { console.error("Error getting microphone", error); } } useEffect(() => { if (!recording && mediaRecorder) { mediaRecorder.stop(); } if (recording && !mediaRecorder) { startRecordingAndTranscribe(); } }, [recording, mediaRecorder]); const chatInputRef = useRef(null); useEffect(() => { if (!chatInputRef.current) return; chatInputRef.current.style.height = "auto"; chatInputRef.current.style.height = Math.max(chatInputRef.current.scrollHeight - 24, 64) + "px"; }, [message]); return ( <> {showLoginPrompt && loginRedirectMessage && ( )} {uploading && ( Uploading data. Please wait. setUploading(false)} > Dismiss )} {warning && ( Data Upload Warning {warning} setWarning(null)} > Close )} {error && ( Oh no! Something went wrong while uploading your data {error} setError(null)} > Close )} {message.startsWith("/") && message.split(" ").length === 1 && (
e.preventDefault()} className={`${props.isMobileWidth ? "w-[100vw]" : "w-full"} rounded-md`} > No matching commands. {props.chatOptionsData && Object.entries(props.chatOptionsData).map( ([key, value]) => ( handleSlashCommandClick(key) } >
{getIconForSlashCommand(key)}/{key}
{value}
), )}
)}