interstellar_ai/app/backend/InputOutputHandler.tsx

383 lines
13 KiB
TypeScript
Raw Normal View History

2024-09-19 13:02:56 +02:00
"use client"
2024-10-07 08:57:34 +02:00
import React, { useEffect, useRef, useState } from "react";
2024-10-01 10:39:21 +02:00
import ConversationFrontend from '../components/ConversationFrontend';
2024-09-20 10:34:16 +02:00
import InputFrontend from "../components/InputFrontend";
2024-09-30 12:52:58 +02:00
import { sendToVoiceRecognition } from "./voice_backend"
2024-09-23 11:11:45 +02:00
import axios from "axios";
2024-10-08 15:33:21 +02:00
import { useChatHistory } from '../hooks/useChatHistory';
2024-10-09 16:36:08 +02:00
import { getWeather } from "./weather";
2024-10-09 20:20:04 +02:00
import { changeHistory, getHistory } from "./database";
2024-09-24 09:51:16 +02:00
2024-10-08 15:33:21 +02:00
const InputOutputBackend: React.FC = () => {
2024-10-11 09:24:40 +02:00
//#region variables
2024-09-23 16:34:55 +02:00
type Message = {
role: string
2024-09-24 16:42:36 +02:00
content: string
2024-09-23 16:34:55 +02:00
}
// Define state variables for user preferences and messages
2024-10-09 14:22:31 +02:00
const [chatHistory, setSelectedIndex, setChatHistory, updateMessage] = useChatHistory()
const [preferredCurrency, setPreferredCurrency] = useState<string>("USD");
const [preferredLanguage, setPreferredLanguage] = useState<string>("english");
const [timeFormat, setTimeFormat] = useState<string>("24-hour");
const [preferredMeasurement, setPreferredMeasurement] = useState<string>("metric");
const [timeZone, setTimeZone] = useState<string>("GMT");
const [dateFormat, setDateFormat] = useState<string>("DD-MM-YYYY");
2024-10-10 10:37:02 +02:00
const [messages, setMessages] = useState<Message[]>([]);
2024-10-07 11:16:51 +02:00
const [myBoolean, setMyBoolean] = useState<boolean>(false);
2024-10-08 15:33:21 +02:00
const [systemMessage, setSystemMessage] = useState<string>("You are a helpful assistant")
2024-10-09 20:20:04 +02:00
const [weatherData, setWeatherData] = useState<string>("")
const [weatherTriggered, setWeatherTriggered] = useState<boolean>(false)
const [chatHistoryTriggered, setChatHistoryTriggered] = useState<boolean>(false)
const apiURL = new URL("http://localhost:5000/interstellar_ai/api/ai_create")
2024-10-07 11:16:51 +02:00
if (typeof window !== 'undefined') {
apiURL.hostname = window.location.hostname;
} else {
apiURL.hostname = "localhost"
}
2024-10-08 15:33:21 +02:00
2024-10-09 15:56:06 +02:00
console.log(setSelectedIndex)
2024-10-11 09:24:40 +02:00
//#region useEffect
2024-10-10 10:37:02 +02:00
useEffect(() => {
setMessages(chatHistory.chats[chatHistory.selectedIndex].messages)
}, [chatHistory.selectedIndex])
2024-10-08 15:33:21 +02:00
2024-10-09 16:36:08 +02:00
useEffect(() => {
2024-10-08 15:33:21 +02:00
console.log("History", chatHistory);
console.log("Messages", messages);
2024-10-10 10:37:02 +02:00
2024-10-08 15:33:21 +02:00
// Get the current chat's messages
2024-10-09 15:56:06 +02:00
const currentMessages = chatHistory.chats[chatHistory.selectedIndex].messages || [];
2024-10-08 15:33:21 +02:00
2024-10-09 15:56:06 +02:00
// If the selected chat has messages, set them
if (currentMessages.length > 0) {
2024-10-09 16:36:08 +02:00
setMessages(currentMessages);
2024-10-09 15:56:06 +02:00
} else if (currentMessages.length === 0) {
// When creating a new chat and no messages exist yet, set default messages
addMessage("system", systemMessage)
addMessage("assistant", "Hello! How can I help you?")
console.log(systemMessage)
2024-10-08 15:33:21 +02:00
}
2024-10-09 16:36:08 +02:00
}, [chatHistory, chatHistory.selectedIndex, systemMessage]);
2024-10-08 15:33:21 +02:00
// Update messages when any of the settings change
2024-09-30 12:52:58 +02:00
useEffect(() => {
2024-10-07 11:16:51 +02:00
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');
2024-10-09 16:36:08 +02:00
getWeatherHere()
2024-10-09 20:20:04 +02:00
getChatHistory()
2024-10-07 11:16:51 +02:00
}
2024-10-09 16:36:08 +02:00
}, [])
2024-10-09 20:20:04 +02:00
useEffect(() => {
const username = localStorage.getItem("accountName")
const password = localStorage.getItem("accountPassword")
if (username && password && chatHistoryTriggered) {
changeHistory(username, password, chatHistory)
console.log("changed history in backend")
}
}, [chatHistory])
2024-10-11 09:24:40 +02:00
//#region functions
2024-10-09 16:36:08 +02:00
const getWeatherHere = async () => {
setWeatherData(await getWeather({ "unit_type": preferredMeasurement, "city": localStorage.getItem("weatherInfo") || "New York" }))
2024-10-09 20:20:04 +02:00
console.log("Got the Data!")
setWeatherTriggered(true)
}
const getChatHistory = async () => {
const username = localStorage.getItem("accountName")
const password = localStorage.getItem("accountPassword")
if (username && password) {
const tempChatHistory = await getHistory(username, password)
if (tempChatHistory && typeof tempChatHistory == "object") {
setChatHistory(tempChatHistory)
console.log("got history from backend")
}
}
setChatHistoryTriggered(true)
2024-10-09 16:36:08 +02:00
}
2024-10-11 09:24:40 +02:00
//#region system-prompt
2024-10-08 15:33:21 +02:00
useEffect(() => {
2024-10-09 20:20:04 +02:00
console.log("creating system prompt")
console.log(weatherData)
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.";
2024-09-30 12:52:58 +02:00
2024-10-08 15:33:21 +02:00
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.
2024-10-09 16:49:44 +02:00
Do not answer in multiple languages or multiple measurement systems under any circumstances other than the user requesting it.
2024-10-10 10:33:33 +02:00
These are the currently newest Weather infos for the region. Only for the case when the user asks about anything weather related,
you can use the following data to help the user: ${weatherData}. If there is nothing there say there is no data`
2024-10-09 16:49:44 +02:00
: `You are a helpful assistant.`;
2024-10-09 20:20:04 +02:00
console.log(newSystemMessage)
2024-10-08 15:33:21 +02:00
setSystemMessage(newSystemMessage)
2024-10-09 20:20:04 +02:00
}, [preferredCurrency, preferredLanguage, timeFormat, preferredMeasurement, timeZone, dateFormat, myBoolean, weatherTriggered]);
2024-10-08 15:33:21 +02:00
useEffect(() => {
2024-10-09 14:01:48 +02:00
const messageIndex = 0 // system prompt is the first so index 0
updateMessage(messageIndex, systemMessage)
console.log(messages)
2024-10-09 16:36:08 +02:00
}, [systemMessage])
2024-10-08 15:33:21 +02:00
2024-10-11 09:24:40 +02:00
//#region more variables and functions
2024-10-01 10:39:21 +02:00
const conversationRef = useRef<HTMLDivElement>(null)
2024-09-26 13:42:22 +02:00
const [copyClicked, setCopyClicked] = useState(false)
2024-09-23 11:11:45 +02:00
const [accessToken, setAccessToken] = useState("")
2024-09-24 16:42:36 +02:00
const postWorkerRef = useRef<Worker | null>(null)
2024-09-23 11:11:45 +02:00
const getWorkerRef = useRef<Worker | null>(null)
2024-09-24 16:42:36 +02:00
const [inputMessage, setInputMessage] = useState<string>("")
2024-09-24 13:50:46 +02:00
const [inputDisabled, setInputDisabled] = useState(false)
2024-09-26 08:57:28 +02:00
const [isRecording, setIsRecording] = useState(false)
2024-09-30 12:52:58 +02:00
const mediaRecorderRef = useRef<MediaRecorder | null>(null)
2024-09-26 08:57:28 +02:00
const audioChunks = useRef<Blob[]>([])
2024-10-11 09:24:40 +02:00
//#region chat functions
2024-09-23 11:11:45 +02:00
useEffect(() => {
2024-09-24 16:42:36 +02:00
getNewToken()
2024-09-23 11:11:45 +02:00
postWorkerRef.current = new Worker(new URL("./threads/PostWorker.ts", import.meta.url))
2024-09-23 11:11:45 +02:00
postWorkerRef.current.onmessage = (event) => {
const status = event.data.status
if (status == 200) {
2024-09-24 13:50:46 +02:00
setInputDisabled(false)
2024-09-23 11:11:45 +02:00
endGetWorker()
} else if (status == 500) {
2024-09-24 13:50:46 +02:00
setInputDisabled(false)
2024-09-23 11:11:45 +02:00
if (getWorkerRef.current) {
addMessage("assistant", "There was an Error with the AI response")
getWorkerRef.current.postMessage("terminate")
getWorkerRef.current.terminate()
}
}
}
2024-09-24 16:42:36 +02:00
2024-09-23 11:11:45 +02:00
return () => {
if (postWorkerRef.current) {
postWorkerRef.current.terminate()
}
if (getWorkerRef.current) {
getWorkerRef.current.postMessage("terminate")
getWorkerRef.current.terminate()
}
}
2024-09-24 16:42:36 +02:00
}, [])
const getNewToken = () => {
axios.get(apiURL.href)
2024-09-24 16:42:36 +02:00
.then(response => {
setAccessToken(response.data.access_token)
})
.catch(error => {
console.log("error:", error.message);
})
}
2024-09-23 11:11:45 +02:00
const startGetWorker = () => {
if (!getWorkerRef.current) {
getWorkerRef.current = new Worker(new URL("./threads/GetWorker.ts", import.meta.url))
2024-10-07 11:16:51 +02:00
let windowname = "localhost"
if (typeof window !== 'undefined') {
windowname = window.location.hostname
} else {
windowname = "localhost"
}
2024-09-24 16:42:36 +02:00
getWorkerRef.current.postMessage({ action: "start", access_token: accessToken, windowname })
2024-09-24 16:42:36 +02:00
addMessage("assistant", "")
2024-09-23 11:11:45 +02:00
getWorkerRef.current.onmessage = (event) => {
const data = event.data
2024-09-24 16:42:36 +02:00
2024-09-23 14:54:13 +02:00
if (event.data == "error") {
2024-10-07 08:57:34 +02:00
console.log("Error getting ai message.")
2024-09-23 11:11:45 +02:00
} else {
console.log("Received data:", data);
2024-09-23 16:34:55 +02:00
editLastMessage(data.response)
2024-09-23 11:11:45 +02:00
}
}
getWorkerRef.current.onerror = (error) => {
console.error("Worker error:", error)
}
2024-09-24 16:42:36 +02:00
}
2024-09-23 11:11:45 +02:00
}
const endGetWorker = () => {
if (getWorkerRef.current) {
2024-09-24 16:42:36 +02:00
getWorkerRef.current.postMessage({ action: "terminate" })
2024-09-23 11:11:45 +02:00
getWorkerRef.current.terminate()
2024-09-24 10:58:14 +02:00
getWorkerRef.current = null
2024-09-23 11:11:45 +02:00
}
}
2024-09-23 16:34:55 +02:00
const editLastMessage = (newContent: string) => {
2024-09-24 10:58:14 +02:00
if (newContent == "") {
2024-09-24 13:50:46 +02:00
newContent = "Generating answer..."
2024-09-24 10:58:14 +02:00
}
2024-10-09 16:36:08 +02:00
const messageIndex = chatHistory.chats[chatHistory.selectedIndex].messages.length - 1
updateMessage(messageIndex, newContent)
2024-09-24 16:42:36 +02:00
};
2024-09-23 16:34:55 +02:00
2024-09-23 11:11:45 +02:00
const addMessage = (role: string, content: string) => {
2024-10-08 15:33:21 +02:00
const newMessage: Message = { role: role, content: content }
setMessages((prevMessages) => [...prevMessages, newMessage])
const updatedChats = [...chatHistory.chats]
updatedChats[chatHistory.selectedIndex].messages.push(newMessage)
2024-10-09 16:36:08 +02:00
setChatHistory({ ...chatHistory, chats: updatedChats })
2024-09-24 16:42:36 +02:00
}
const handleSendClick = (inputValue: string, override: boolean) => {
2024-09-24 10:22:50 +02:00
if (inputValue != "") {
2024-09-24 16:42:36 +02:00
if (!inputDisabled || override) {
2024-09-24 13:50:46 +02:00
setInputDisabled(true)
2024-09-24 10:22:50 +02:00
if (postWorkerRef.current) {
addMessage("user", inputValue)
2024-10-09 16:36:08 +02:00
let type: string = "local"
2024-10-07 08:57:34 +02:00
let api_key: string = ""
2024-10-07 11:16:51 +02:00
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
}
2024-09-30 15:26:31 +02:00
}
}
2024-10-01 08:36:08 +02:00
setInputMessage("")
const windowname = window.location.hostname
2024-10-09 22:04:06 +02:00
postWorkerRef.current.postMessage({ messages: [...messages, { role: "user", content: inputValue }], ai_model: localStorage.getItem("model") || "llama3.2", model_type: type, access_token: accessToken, api_key: api_key, windowname })
2024-09-24 10:22:50 +02:00
startGetWorker()
}
2024-09-24 09:51:16 +02:00
}
2024-09-23 11:11:45 +02:00
}
}
2024-10-11 09:24:40 +02:00
//#region speech recognition
2024-09-30 12:52:58 +02:00
const startRecording = async (): Promise<string> => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream);
mediaRecorderRef.current = mediaRecorder;
2024-09-25 12:33:52 +02:00
2024-09-30 12:52:58 +02:00
audioChunks.current = []; // Initialize audio chunks
2024-09-25 12:33:52 +02:00
2024-09-30 12:52:58 +02:00
// Create a promise that resolves when the onstop event is done
const stopRecordingPromise = new Promise<string>((resolve) => {
mediaRecorder.ondataavailable = (event) => {
audioChunks.current.push(event.data);
};
2024-09-25 12:33:52 +02:00
2024-09-30 12:52:58 +02:00
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 () => {
2024-09-25 12:33:52 +02:00
if (!isRecording) {
2024-09-30 12:52:58 +02:00
const recognizedText = await startRecording();
setInputMessage(recognizedText); // Set the recognized text after recording
2024-09-25 12:33:52 +02:00
} else {
stopRecording();
}
};
2024-10-11 09:24:40 +02:00
//#region chat buttons
const handleStopClick = () => {
2024-10-01 14:29:15 +02:00
endGetWorker()
getNewToken()
2024-10-07 16:41:31 +02:00
setInputDisabled(false)
2024-10-01 14:29:15 +02:00
}
2024-09-23 11:11:45 +02:00
const handleResendClick = () => {
2024-10-11 08:44:30 +02:00
const msg = chatHistory.chats[chatHistory.selectedIndex].messages
const lastUserMessage = msg[msg.length-2].content
msg.splice(msg.length-2,2)
2024-09-24 16:42:36 +02:00
endGetWorker()
getNewToken()
setInputDisabled(false)
2024-10-11 08:44:30 +02:00
handleSendClick(lastUserMessage, true)
2024-09-23 11:11:45 +02:00
}
2024-09-19 15:56:26 +02:00
2024-09-23 11:11:45 +02:00
const handleEditClick = () => {
2024-10-11 08:44:30 +02:00
const msg = chatHistory.chats[chatHistory.selectedIndex].messages
const lastUserMessage = msg[msg.length-2].content
setInputMessage(lastUserMessage)
msg.splice(msg.length-2,2)
2024-09-24 16:42:36 +02:00
endGetWorker()
getNewToken()
setInputDisabled(false)
2024-09-23 11:11:45 +02:00
}
2024-09-19 13:02:56 +02:00
2024-09-24 16:42:36 +02:00
const handleCopyClick = async () => {
2024-09-26 13:42:22 +02:00
setCopyClicked(false)
2024-09-24 16:42:36 +02:00
try {
await navigator.clipboard.writeText(messages[messages.length - 1]['content']);
2024-09-26 13:42:22 +02:00
fadeCopyText()
2024-09-24 16:42:36 +02:00
} catch (err) {
console.error('Failed to copy: ', err);
}
2024-09-23 11:11:45 +02:00
}
2024-09-20 09:17:28 +02:00
2024-09-26 13:42:22 +02:00
const wait = (time: number) => {
return new Promise(resolve => setTimeout(resolve, time));
}
const fadeCopyText = async () => {
setCopyClicked(true)
await wait(1000)
setCopyClicked(false)
}
2024-09-30 12:52:58 +02:00
2024-10-11 09:24:40 +02:00
//#region The "html" return
2024-09-24 16:42:36 +02:00
return (
2024-09-30 12:52:58 +02:00
<>
2024-09-19 15:56:26 +02:00
<ConversationFrontend
2024-10-01 10:39:21 +02:00
ref={conversationRef}
2024-09-19 15:56:26 +02:00
messages={messages}
2024-10-01 14:29:15 +02:00
onStopClick={handleStopClick}
2024-09-19 15:56:26 +02:00
onResendClick={handleResendClick}
onEditClick={handleEditClick}
onCopyClick={handleCopyClick}
2024-09-26 13:42:22 +02:00
isClicked={copyClicked}
2024-09-19 15:56:26 +02:00
/>
<InputFrontend
2024-09-24 16:42:36 +02:00
message={inputMessage}
2024-09-19 15:56:26 +02:00
onSendClick={handleSendClick}
onMicClick={handleMicClick}
2024-09-24 13:50:46 +02:00
inputDisabled={inputDisabled}
2024-09-26 08:57:28 +02:00
isRecording={isRecording}
2024-09-30 12:52:58 +02:00
/>
</>
2024-09-24 16:42:36 +02:00
)
2024-09-19 13:02:56 +02:00
}
2024-09-27 13:59:27 +02:00
export default InputOutputBackend