diff --git a/README.md b/README.md index e69de29..0efe25b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,46 @@ +# Installation Guide + +In order to use this app, you need quite a lot of things, so buckle up. + +## Dependencies + +Install the following: + +https://ollama.com/download + +https://www.python.org/downloads/ + +https://nodejs.org/en/download/prebuilt-installer + +For Linux and NodeJS, see: + +https://nodejs.org/en/download/package-manager/all + +## Running the project for the first time + +In order to now actually run the program, you have to open a terminal in the project folder, then you can run: + +``` +npm install +cd py +pip install -R requirements.txt +``` + +In case you need a virtual environment however, we provide a custom install.sh file for GNU/Linux. + +``` +sh install.sh +``` + +From then on, you can run the project by: +1. Opening two terminal windows, one in the main project folder, and one in the py subfolder. +2. In the py subfolder, you will run: +``` +python3 api.py +``` +3. In the main project folder, you will run: +``` +npm run dev +``` +4. Open http://localhost:3000/ in your browser. +5. Enjoy! \ No newline at end of file diff --git a/app/backend/InputOutputHandler.tsx b/app/backend/InputOutputHandler.tsx index ed09bb4..bf8a191 100644 --- a/app/backend/InputOutputHandler.tsx +++ b/app/backend/InputOutputHandler.tsx @@ -2,78 +2,137 @@ import React, { useEffect, useRef, useState } from "react"; import ConversationFrontend from "../components/ConversationFrontend"; import InputFrontend from "../components/InputFrontend"; - -const handleMicClick = () => { - console.log('Mic clicked!'); - // Do something when the mic button is clicked -}; - - -const handleResendClick = () => { - console.log('Resend button clicked'); - // Handle resend action -}; - -const handleEditClick = () => { - console.log('Edit button clicked'); - // Handle edit action -}; - -const handleCopyClick = () => { - console.log('Copy button clicked'); - // Handle copy action -}; +import axios from "axios"; +import { log } from 'console'; const InputOutputBackend: React.FC = () => { - const [accessToken, setAccessToken] = useState("") - const workerRef = useRef(null) - type Message = { - role: string - content: string - } + type Message = { + role: string + content:string + } - const handleSendClick = (message:string) => { - var system:Message = {role:"system" ,content:"You are a helpful assistant."} + const [accessToken, setAccessToken] = useState("") + const postWorkerRef = useRef(null) + const getWorkerRef = useRef(null) + const [messages, setMessages] = useState([{role:"assistant", content:"Hello! How can I help you?"}]) + const [liveMessage, setLiveMessage] = useState("") - addMessage("user", message); - console.log("added User Message") + console.log(messages); + + + useEffect(() => { + console.log("getting access"); + axios.get("http://localhost:5000/interstellar/api/ai_create") + .then(response => { + setAccessToken(response.data.access_token) + console.log(response.data.access_token); + }) + .catch(error => { + console.log("error:", error.message); - HandlePostRequest([...messages, { role: "user", content: message }], "phi3.5", system); - }; + }) - const [messages, setMessages] = useState([ - { role:"assistant", content:'Hello. I\'m Your AI Virtual Assistant' } - ]); + postWorkerRef.current = new Worker(new URL("./threads/PostWorker.js", import.meta.url)) - const addMessage = (role:string ,content: string) => { - setMessages((prevMessages) => [...prevMessages, {role,content}]); - }; - - - - useEffect(() => { - workerRef.current = new Worker(new URL("./ProcessAPI.js", import.meta.url)) - workerRef.current.postMessage({}) - workerRef.current.onmessage = (e) => { - setAccessToken(e.data) - } - - return () => { - if (workerRef.current) { - workerRef.current.terminate() - } - } - },[]) - - const HandlePostRequest = (messages: Message[], ai_model: string, system_prompt: Message) => { - if (workerRef.current) { - workerRef.current.postMessage({ functionName: "postRequest", access_token: accessToken, messages: messages, ai_model: ai_model, system_prompt: system_prompt }) - workerRef.current.onmessage = (e) => { - addMessage("assistant",e.data) - } + postWorkerRef.current.onmessage = (event) => { + const status = event.data.status + if (status == 200) { + endGetWorker() + } else if (status == 500) { + 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 startGetWorker = () => { + if (!getWorkerRef.current) { + getWorkerRef.current = new Worker(new URL("./threads/GetWorker.js", import.meta.url)) + + getWorkerRef.current.postMessage({ action: "start", access_token:accessToken}) + + addMessage("assistant","") + getWorkerRef.current.onmessage = (event) => { + const data = event.data + + if (event.data == "error") { + setLiveMessage("error getting AI response: "+ data.error) + } 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() + console.log(messages); + } + } + + const editLastMessage = (newContent: string) => { + 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) => { + setMessages(previous => [...previous,{role,content}]) + } + const handleSendClick = (inputValue: string) => { + if (postWorkerRef.current) { + addMessage("user", inputValue) + console.log("input:",inputValue); + postWorkerRef.current.postMessage({messages:[...messages, { role: "user", content: inputValue }], ai_model:"phi3.5", access_token:accessToken}) + startGetWorker() + } + } + + const handleMicClick = () => { + // do stuff + } + + const handleResendClick = () => { + // do stuff + } + + const handleEditClick = () => { + // do stuff + } + + const handleCopyClick = () => { + // do stuff + } + return (
{ - messageComplete:boolean = false - while(!messageComplete) - axios.get('https://localhost:5000/interstellar/api/ai_get?access_token=' + access_token) - .then(Response => { - postMessage(Response.data.response) - if (Response.data.status == 200) { - messageComplete = true - } - }).catch(error => { - console.error("Error with GET response request:", error) - }) - } - - switch (functionName) { - case "getAccess": - console.log("getting access...") - axios.get('https://localhost:5000/interstellar/api/ai_create') - .then(Response => { - postMessage(Response.data.access_token) - }).catch(error => { - console.error("Error with GET Token request:", error) - }) - break - case "postRequest": - messages.unshift(system_prompt) - console.log("sending...") - console.log(messages) - axios.post('https://localhost:5000/interstellar/api/ai_send', data) - .then(Response => { - getResponse() - }).catch(error => { - console.error("Error:", error) - }) - break - } - - -} \ No newline at end of file diff --git a/app/backend/threads/GetWorker.js b/app/backend/threads/GetWorker.js new file mode 100644 index 0000000..23d8e63 --- /dev/null +++ b/app/backend/threads/GetWorker.js @@ -0,0 +1,34 @@ +import axios from "axios"; + +let accesstoken +onmessage = (event) => { + const { action, access_token } = event.data + accesstoken=access_token + + if (action === "start") { + fetchData() + } else if (action === "terminate") { + } +} + +console.log('starting get loop'); + +const fetchData = () => { + console.log(accesstoken); + + + const apiURL = "http://localhost:5000/interstellar/api/ai_get?access_token="+accesstoken + + axios.get(apiURL) + .then(response => { + const data = response.data + console.log(data); + postMessage(data) + setTimeout(fetchData,100) + }) + .catch(error => { + console.log('Error fetching data:', error); + postMessage({error:"failed fetching data"}) + + }) +} \ No newline at end of file diff --git a/app/backend/threads/PostWorker.js b/app/backend/threads/PostWorker.js new file mode 100644 index 0000000..730c852 --- /dev/null +++ b/app/backend/threads/PostWorker.js @@ -0,0 +1,29 @@ +import axios from "axios"; + +onmessage = (e) => { + const { messages = [{ role: "assistant", content: "Hello! How can I help you?" }], ai_model = "phi3.5", access_token } = e.data + messages.unshift({ role: "system", content: "You are a Helpful assistant" }) + + const Message = { + messages: messages, + ai_model: "phi3.5", + model_type:"local", + access_token:access_token + } + + console.log(Message); + + + axios.post("http://localhost:5000/interstellar/api/ai_send",Message) + .then(response => { + const status = response.data.status + console.log(status); + postMessage({ status }) + console.log('message posted'); + + }) + .catch(error => { + console.log("Error calling API:", error) + postMessage({status:500}) + }) +} \ No newline at end of file diff --git a/py/api.py b/py/api.py index 55d0483..be44561 100644 --- a/py/api.py +++ b/py/api.py @@ -118,8 +118,3 @@ class API: ssl_context = ("cert.pem", "key.pem") self.app.run(debug=True, host='0.0.0.0', port=5000) - - -if __name__ == '__main__': - api = API() - api.run() diff --git a/py/requirements.txt b/py/requirements.txt index bd93ad7..8a79dc1 100644 --- a/py/requirements.txt +++ b/py/requirements.txt @@ -4,4 +4,5 @@ ollama mistralai openai anthropic -pyOpenSSL \ No newline at end of file +pyOpenSSL +pywebview \ No newline at end of file diff --git a/py/run.py b/py/run.py new file mode 100644 index 0000000..612d72e --- /dev/null +++ b/py/run.py @@ -0,0 +1,7 @@ +import os +import webview + +os.system("python api.py") +webview.create_window('Hello world', 'http://localhost:3000') +webview.start() + diff --git a/py/webapp.py b/py/webapp.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/py/webapp.py @@ -0,0 +1 @@ +