interstellar_ai/app/backend/InputOutputHandler.tsx

299 lines
10 KiB
TypeScript
Raw Normal View History

2024-09-19 13:02:56 +02:00
"use client"
2024-09-27 10:57:18 +02:00
import React, { use, 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-01 12:46:21 +02:00
import { resolve } from "path";
2024-09-27 10:57:18 +02:00
import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile, toBlobURL } from "@ffmpeg/util"
2024-09-24 09:51:16 +02:00
2024-09-19 13:02:56 +02:00
const InputOutputBackend: React.FC = () => {
2024-09-30 12:52:58 +02:00
// # 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
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-09-30 12:45:26 +02:00
const [messages, setMessages] = useState<Message[]>([]);
const [myBoolean, setMyBoolean] = useState<boolean>(() => localStorage.getItem('myBoolean') === 'true') || false;
2024-09-30 12:52:58 +02:00
// Fetch local storage values and update state on component mount
2024-09-30 12:52:58 +02:00
useEffect(() => {
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-09-30 12:52:58 +02:00
}, []);
// Update messages when any of the settings change
2024-09-30 12:52:58 +02:00
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.";
2024-09-30 12:52:58 +02:00
const systemMessage = 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";
setMessages([
{ role: "system", content: systemMessage },
{ role: "assistant", content: "Hello! How may I help you?" },
]);
}, [preferredCurrency, preferredLanguage, timeFormat, preferredMeasurement, timeZone, dateFormat, myBoolean]);
2024-09-30 12:52:58 +02:00
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)
const [liveMessage, setLiveMessage] = useState("")
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-09-25 12:33:52 +02:00
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.js", import.meta.url))
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("http://localhost:5000/interstellar_ai/api/ai_create")
.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.js", import.meta.url))
2024-09-24 16:42:36 +02:00
getWorkerRef.current.postMessage({ action: "start", access_token: accessToken })
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-09-24 16:42:36 +02:00
setLiveMessage("error getting AI response: " + data.error)
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-09-24 16:42:36 +02:00
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
});
};
2024-09-23 16:34:55 +02:00
2024-09-23 11:11:45 +02:00
const addMessage = (role: string, content: string) => {
2024-09-24 16:42:36 +02:00
setMessages(previous => [...previous, { role, content }])
}
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-09-30 15:26:31 +02:00
const type = localStorage.getItem('type')
var api_key: string = ""
if (type != null && type != 'local') {
const try_key = localStorage.getItem(type)
if (try_key) {
api_key = try_key
}
}
2024-10-01 08:36:08 +02:00
setInputMessage("")
2024-10-01 14:29:15 +02:00
postWorkerRef.current.postMessage({ messages: [...messages, { role: "user", content: inputValue }], ai_model: "llama3.2", model_type: type, access_token: accessToken, api_key: api_key })
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-09-19 15:56:26 +02:00
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-09-19 15:56:26 +02:00
2024-10-01 14:29:15 +02:00
const handleStopClick = () => {
endGetWorker()
getNewToken()
}
2024-09-23 11:11:45 +02:00
const handleResendClick = () => {
2024-09-24 16:42:36 +02:00
var temporary_message = messages[messages.length - 2]['content']
const updatedMessages = messages.slice(0, -2)
setMessages(updatedMessages)
endGetWorker()
getNewToken()
setInputDisabled(false)
handleSendClick(temporary_message, 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-01 09:22:33 +02:00
let newestMessage = messages[messages.length - 2].content
setInputMessage(newestMessage)
const updatedMessages = messages.slice(0, messages.length-2)
2024-09-24 16:42:36 +02:00
setMessages(updatedMessages)
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-09-26 13:42:22 +02:00
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