"use client" import React, { useEffect, useRef, useState } from "react"; import ConversationFrontend from '../components/ConversationFrontend'; import InputFrontend from "../components/InputFrontend"; import { sendToVoiceRecognition } from "./voice_backend" import axios from "axios"; import { changeHistory, checkCredentials, getHistory } from './database'; import { useChatHistory } from '../hooks/useChatHistory'; const InputOutputBackend: React.FC = () => { // # variables type Message = { role: string content: string } // Define state variables for user preferences and messages const [chatHistory, setChatHistory, setSelectedIndex, updateMessage] = useChatHistory() const [preferredCurrency, setPreferredCurrency] = useState("USD"); const [preferredLanguage, setPreferredLanguage] = useState("english"); const [timeFormat, setTimeFormat] = useState("24-hour"); const [preferredMeasurement, setPreferredMeasurement] = useState("metric"); const [timeZone, setTimeZone] = useState("GMT"); const [dateFormat, setDateFormat] = useState("DD-MM-YYYY"); const [messages, setMessages] = useState(chatHistory.chats[chatHistory.selectedIndex]?.messages || []); const [myBoolean, setMyBoolean] = useState(false); const [systemMessage, setSystemMessage] = useState("You are a helpful assistant") const apiURL = new URL("http://localhost:5000/interstellar_ai/api/ai_create") if (typeof window !== 'undefined') { apiURL.hostname = window.location.hostname; } else { apiURL.hostname = "localhost" } useEffect(() => { console.log("History", chatHistory); console.log("Messages", messages); // Get the current chat's messages const currentMessages = chatHistory.chats[chatHistory.selectedIndex]?.messages || []; // If currentMessages is not empty, update messages only if it's not the same if (currentMessages.length > 0 && JSON.stringify(currentMessages) !== JSON.stringify(messages)) { setMessages(currentMessages); } else if (messages.length === 0) { setMessages([{ role: "system", content: systemMessage }, { role: "assistant", content: "Hello! How can I help you?" }]); } }, [chatHistory, setSelectedIndex]); // Update messages when any of the settings change useEffect(() => { if (typeof localStorage !== 'undefined') { setPreferredCurrency(localStorage.getItem("preferredCurrency") || "USD"); setPreferredLanguage(localStorage.getItem("preferredLanguage") || "english"); setTimeFormat(localStorage.getItem("timeFormat") || "24-hour"); setPreferredMeasurement(localStorage.getItem("preferredMeasurement") || "metric"); setTimeZone(localStorage.getItem("timeZone") || "GMT"); setDateFormat(localStorage.getItem("dateFormat") || "DD-MM-YYYY"); setMyBoolean(localStorage.getItem('myBoolean') === 'true'); } },[]) useEffect(() => { const measurementString = (preferredMeasurement == "Metric") ? "All measurements follow the metric system. Refuse to use any other measurement system." : "All measurements follow the imperial system. Refuse to use any other measurement system."; const newSystemMessage = myBoolean ? `You are operating in the timezone: ${timeZone}. Use the ${timeFormat} time format and ${dateFormat} for dates. ${measurementString} The currency is ${preferredCurrency}. Communicate in the language specified by the user (country code: ${preferredLanguage}), and only in this language. You are only able to change language if the user specifically states you must. Do not answer in multiple languages or multiple measurement systems under any circumstances other than the user requesting it.` : `You are a helpful assistant`; setSystemMessage(newSystemMessage) }, [preferredCurrency, preferredLanguage, timeFormat, preferredMeasurement, timeZone, dateFormat, myBoolean]); useEffect(() => { const updateSystemprompt = (prompt: string) => { setMessages(prevMessages => { const newMessage = { role: "system", content: prompt } return [newMessage, ...prevMessages] }) } updateSystemprompt },[systemMessage]) const conversationRef = useRef(null) const [copyClicked, setCopyClicked] = useState(false) const [accessToken, setAccessToken] = useState("") const postWorkerRef = useRef(null) const getWorkerRef = useRef(null) const [inputMessage, setInputMessage] = useState("") const [inputDisabled, setInputDisabled] = useState(false) const [isRecording, setIsRecording] = useState(false) const mediaRecorderRef = useRef(null) const audioChunks = useRef([]) useEffect(() => { getNewToken() postWorkerRef.current = new Worker(new URL("./threads/PostWorker.ts", import.meta.url)) postWorkerRef.current.onmessage = (event) => { const status = event.data.status if (status == 200) { setInputDisabled(false) endGetWorker() } else if (status == 500) { setInputDisabled(false) if (getWorkerRef.current) { addMessage("assistant", "There was an Error with the AI response") getWorkerRef.current.postMessage("terminate") getWorkerRef.current.terminate() } } } return () => { if (postWorkerRef.current) { postWorkerRef.current.terminate() } if (getWorkerRef.current) { getWorkerRef.current.postMessage("terminate") getWorkerRef.current.terminate() } } }, []) const getNewToken = () => { axios.get(apiURL.href) .then(response => { setAccessToken(response.data.access_token) }) .catch(error => { console.log("error:", error.message); }) } const startGetWorker = () => { if (!getWorkerRef.current) { getWorkerRef.current = new Worker(new URL("./threads/GetWorker.ts", import.meta.url)) let windowname = "localhost" if (typeof window !== 'undefined') { windowname = window.location.hostname } else { windowname = "localhost" } getWorkerRef.current.postMessage({ action: "start", access_token: accessToken, windowname }) addMessage("assistant", "") getWorkerRef.current.onmessage = (event) => { const data = event.data if (event.data == "error") { console.log("Error getting ai message.") } else { console.log("Received data:", data); editLastMessage(data.response) } } getWorkerRef.current.onerror = (error) => { console.error("Worker error:", error) } } } const endGetWorker = () => { if (getWorkerRef.current) { getWorkerRef.current.postMessage({ action: "terminate" }) getWorkerRef.current.terminate() getWorkerRef.current = null } } const editLastMessage = (newContent: string) => { if (newContent == "") { newContent = "Generating answer..." } const messageIndex = chatHistory.chats[chatHistory.selectedIndex].messages.length-1 updateMessage(messageIndex,newContent) // setMessages((prevMessages) => { // const updatedMessages = prevMessages.slice(); // Create a shallow copy of the current messages // if (updatedMessages.length > 0) { // const lastMessage = updatedMessages[updatedMessages.length - 1]; // updatedMessages[updatedMessages.length - 1] = { // ...lastMessage, // Keep the existing role and other properties // content: newContent, // Update only the content // }; // } // return updatedMessages; // Return the updated array // }); }; const addMessage = (role: string, content: string) => { const newMessage: Message = { role: role, content: content } setMessages((prevMessages) => [...prevMessages, newMessage]) const updatedChats = [...chatHistory.chats] updatedChats[chatHistory.selectedIndex].messages.push(newMessage) setChatHistory({...chatHistory, chats:updatedChats}) } const handleSendClick = (inputValue: string, override: boolean) => { if (inputValue != "") { if (!inputDisabled || override) { setInputDisabled(true) if (postWorkerRef.current) { addMessage("user", inputValue) let type:string = "local" let api_key: string = "" if (typeof localStorage !== 'undefined') { type = localStorage.getItem('type') || "local" if (type != null && type != 'local') { const try_key = localStorage.getItem(type) if (try_key) { api_key = try_key } } } setInputMessage("") const windowname = window.location.hostname postWorkerRef.current.postMessage({ messages: [...messages, { role: "user", content: inputValue }], ai_model: "llama3.2", model_type: type, access_token: accessToken, api_key: api_key, windowname }) startGetWorker() } } } } const startRecording = async (): Promise => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream); mediaRecorderRef.current = mediaRecorder; audioChunks.current = []; // Initialize audio chunks // Create a promise that resolves when the onstop event is done const stopRecordingPromise = new Promise((resolve) => { mediaRecorder.ondataavailable = (event) => { audioChunks.current.push(event.data); }; mediaRecorder.onstop = async () => { const audioBlob = new Blob(audioChunks.current, { type: "audio/ogg" }); audioChunks.current = []; const text_voice = await sendToVoiceRecognition(audioBlob); resolve(text_voice); // Resolve the promise with the recognized text }; }); mediaRecorder.start(); setIsRecording(true); // Wait for the recording to stop and get the recognized text return stopRecordingPromise; }; const stopRecording = () => { mediaRecorderRef.current?.stop(); setIsRecording(false); }; const handleMicClick = async () => { if (!isRecording) { const recognizedText = await startRecording(); setInputMessage(recognizedText); // Set the recognized text after recording } else { stopRecording(); } }; const handleStopClick = () => { endGetWorker() getNewToken() setInputDisabled(false) } const handleResendClick = () => { const temporary_message = messages[messages.length - 2]['content'] const updatedMessages = messages.slice(0, -2) setMessages(updatedMessages) endGetWorker() getNewToken() setInputDisabled(false) handleSendClick(temporary_message, true) } const handleEditClick = () => { const newestMessage = messages[messages.length - 2].content setInputMessage(newestMessage) const updatedMessages = messages.slice(0, messages.length - 2) setMessages(updatedMessages) endGetWorker() getNewToken() setInputDisabled(false) } const handleCopyClick = async () => { setCopyClicked(false) try { await navigator.clipboard.writeText(messages[messages.length - 1]['content']); fadeCopyText() } catch (err) { console.error('Failed to copy: ', err); } } const wait = (time: number) => { return new Promise(resolve => setTimeout(resolve, time)); } const fadeCopyText = async () => { setCopyClicked(true) await wait(1000) setCopyClicked(false) } return ( <> ) } export default InputOutputBackend