Backend and frontend tweaks #16

Merged
sageTheDm merged 14 commits from React-Group/interstellar_ai:main into main 2024-09-24 15:02:53 +02:00
9 changed files with 181 additions and 55 deletions

View file

@ -3,7 +3,6 @@ import React, { useEffect, useRef, useState } from "react";
import ConversationFrontend from "../components/ConversationFrontend"; import ConversationFrontend from "../components/ConversationFrontend";
import InputFrontend from "../components/InputFrontend"; import InputFrontend from "../components/InputFrontend";
import axios from "axios"; import axios from "axios";
import { log } from 'console';
const InputOutputBackend: React.FC = () => { const InputOutputBackend: React.FC = () => {
type Message = { type Message = {
@ -16,6 +15,7 @@ const InputOutputBackend: React.FC = () => {
const getWorkerRef = useRef<Worker | null>(null) const getWorkerRef = useRef<Worker | null>(null)
const [messages, setMessages] = useState<Message[]>([{role:"assistant", content:"Hello! How can I help you?"}]) const [messages, setMessages] = useState<Message[]>([{role:"assistant", content:"Hello! How can I help you?"}])
const [liveMessage, setLiveMessage] = useState("") const [liveMessage, setLiveMessage] = useState("")
const [inputDisabled, setInputDisabled] = useState(false)
console.log(messages); console.log(messages);
@ -37,8 +37,10 @@ const InputOutputBackend: React.FC = () => {
postWorkerRef.current.onmessage = (event) => { postWorkerRef.current.onmessage = (event) => {
const status = event.data.status const status = event.data.status
if (status == 200) { if (status == 200) {
setInputDisabled(false)
endGetWorker() endGetWorker()
} else if (status == 500) { } else if (status == 500) {
setInputDisabled(false)
if (getWorkerRef.current) { if (getWorkerRef.current) {
addMessage("assistant", "There was an Error with the AI response") addMessage("assistant", "There was an Error with the AI response")
getWorkerRef.current.postMessage("terminate") getWorkerRef.current.postMessage("terminate")
@ -86,11 +88,15 @@ const InputOutputBackend: React.FC = () => {
if (getWorkerRef.current) { if (getWorkerRef.current) {
getWorkerRef.current.postMessage({action:"terminate"}) getWorkerRef.current.postMessage({action:"terminate"})
getWorkerRef.current.terminate() getWorkerRef.current.terminate()
getWorkerRef.current = null
console.log(messages); console.log(messages);
} }
} }
const editLastMessage = (newContent: string) => { const editLastMessage = (newContent: string) => {
if (newContent == "") {
newContent = "Generating answer..."
}
setMessages((prevMessages) => { setMessages((prevMessages) => {
const updatedMessages = prevMessages.slice(); // Create a shallow copy of the current messages const updatedMessages = prevMessages.slice(); // Create a shallow copy of the current messages
if (updatedMessages.length > 0) { if (updatedMessages.length > 0) {
@ -109,11 +115,16 @@ const InputOutputBackend: React.FC = () => {
setMessages(previous => [...previous,{role,content}]) setMessages(previous => [...previous,{role,content}])
} }
const handleSendClick = (inputValue: string) => { const handleSendClick = (inputValue: string) => {
if (postWorkerRef.current) { if (inputValue != "") {
addMessage("user", inputValue) if (!inputDisabled) {
console.log("input:",inputValue); setInputDisabled(true)
postWorkerRef.current.postMessage({messages:[...messages, { role: "user", content: inputValue }], ai_model:"phi3.5", access_token:accessToken}) if (postWorkerRef.current) {
startGetWorker() addMessage("user", inputValue)
console.log("input:",inputValue);
postWorkerRef.current.postMessage({messages:[...messages, { role: "user", content: inputValue }], ai_model:"phi3.5", access_token:accessToken})
startGetWorker()
}
}
} }
} }
@ -145,6 +156,7 @@ const InputOutputBackend: React.FC = () => {
message="" message=""
onSendClick={handleSendClick} onSendClick={handleSendClick}
onMicClick={handleMicClick} onMicClick={handleMicClick}
inputDisabled={inputDisabled}
/> />
</div> </div>
) )

View file

@ -29,6 +29,6 @@ const fetchData = () => {
.catch(error => { .catch(error => {
console.log('Error fetching data:', error); console.log('Error fetching data:', error);
postMessage({error:"failed fetching data"}) postMessage({error:"failed fetching data"})
setTimeout(() => fetchData(),1000)
}) })
} }

View file

@ -2,7 +2,7 @@ import axios from "axios";
onmessage = (e) => { onmessage = (e) => {
const { messages = [{ role: "assistant", content: "Hello! How can I help you?" }], ai_model = "phi3.5", access_token } = e.data 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" }) messages.unshift({ role: "system", content: "You are a Helpful assistant. you give short answers" })
const Message = { const Message = {
messages: messages, messages: messages,

View file

@ -1,5 +1,5 @@
// Header.tsx // Header.tsx
import React from 'react'; import React, { useState } from 'react';
import Login from './Login'; import Login from './Login';
interface HeaderProps { interface HeaderProps {
@ -11,29 +11,32 @@ interface HeaderProps {
} }
const Header: React.FC<HeaderProps> = ({ onViewChange, showDivs, toggleDivs, showHistoryModelsToggle, showToggle }) => { const Header: React.FC<HeaderProps> = ({ onViewChange, showDivs, toggleDivs, showHistoryModelsToggle, showToggle }) => {
const [menuOpen, setMenuOpen] = useState(false)
const toggleMenu = () => {
setMenuOpen(!menuOpen)
}
return ( return (
<> <>
<header> <header>
<ul> <div className={`hamburger ${menuOpen ? "open" : ""}`} onClick={toggleMenu}>
<li> <span></span>
<button onClick={() => onViewChange('AI')} className="header-button header-logo"> <span></span>
<img src="/img/logo.png" alt="logo" className="header-logo" /> <span></span>
</button> </div>
</li> <nav className={`nav-links ${menuOpen ? "active":""}`}>
<li> <button onClick={() => onViewChange('FAQ')} className="nav-btn">FAQ</button>
<button onClick={() => onViewChange('FAQ')} className="header-button">FAQ</button> <button onClick={() => onViewChange('Documentation')} className="nav-btn">Documentation</button>
</li> {showToggle && showHistoryModelsToggle && (
<li> <button onClick={toggleDivs} className="nav-btn">
<button onClick={() => onViewChange('Documentation')} className="header-button">Documentation</button>
</li>
{showToggle && showHistoryModelsToggle && (
<li>
<button onClick={toggleDivs} className="header-button">
{showDivs ? 'Hide History/Models' : 'Show History/Models'} {showDivs ? 'Hide History/Models' : 'Show History/Models'}
</button> </button>
</li> )}
)} </nav>
</ul> {/* <button onClick={() => onViewChange('AI')} className="header-button header-logo">
<img src="/img/logo.png" alt="logo" className="header-logo" />
</button> */}
<Login /> <Login />
</header> </header>
</> </>

View file

@ -4,21 +4,25 @@ interface InputProps {
message: string; message: string;
onSendClick: (message: string) => void; onSendClick: (message: string) => void;
onMicClick: () => void; onMicClick: () => void;
inputDisabled:boolean
} }
const InputFrontend = React.forwardRef<HTMLDivElement, InputProps>( const InputFrontend = React.forwardRef<HTMLDivElement, InputProps>(
({ message, onSendClick, onMicClick }, ref: ForwardedRef<HTMLDivElement>) => { ({ message, onSendClick, onMicClick, inputDisabled }, ref: ForwardedRef<HTMLDivElement>) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); setInputValue(e.target.value);
}; };
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') { if (!inputDisabled) {
onSendClick(inputValue); // Call the function passed via props if (event.key === 'Enter') {
setInputValue(''); // Optionally clear input after submission onSendClick(inputValue); // Call the function passed via props
event.preventDefault(); // Prevent default action (e.g., form submission) setInputValue(''); // Optionally clear input after submission
event.preventDefault(); // Prevent default action (e.g., form submission)
}
} }
}; };
@ -32,7 +36,7 @@ const InputFrontend = React.forwardRef<HTMLDivElement, InputProps>(
onChange={handleInputChange} onChange={handleInputChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> />
<button type="button" onClick={() => onSendClick(inputValue)}> <button type="button" onClick={() => onSendClick(inputValue)} disabled={inputDisabled?true:false}>
<img src="/img/send.svg" alt="send" /> <img src="/img/send.svg" alt="send" />
</button> </button>
<button type="button" onClick={onMicClick}> <button type="button" onClick={onMicClick}>

View file

@ -10,29 +10,84 @@ header {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
z-index: 1000; z-index: 1000;
font-family: var(--font-family); font-family: var(--font-family);
display: flex;
justify-content: space-between;
align-items: center;
} }
header li { .nav-links{
display: inline-block; display: flex;
margin: 0 15px; gap: 15px;
} }
header img { .nav-btn{
height: 2em; background: transparent;
vertical-align: middle;
}
header a,
header li button {
color: var(--header-text-color); /* Use the new header text color */
text-decoration: none;
transition: color 0.3s;
border: none; border: none;
background-color: transparent; cursor: pointer;
font-size: 1em; /* color */
} }
header a:hover, .nav-btn:hover{
header li button:hover { /* color */
color: var(--input-button-color); /* Keep the hover color */ }
.hamburger{
display: none;
flex-direction: column;
cursor: pointer;
}
.hamburger span{
width: 25px;
height: 3px;
background-color: var(--header-text-color);
margin: 4px;
transition: 0.3s;
}
.hamburger.open span:nth-child(1){
transform: rotate(45deg) translate(5px, 5px);
}
.hamburger.open span:nth-child(2){
opacity: 0;
}
.hamburger.open span:nth-child(3){
transform: rotate(-45deg) translate(5px, -5px);
}
.header-button{
}
.header-button img{
height: 8vh;
}
@media (max-width:768px) {
.nav-links{
display: none;
position: absolute;
top: 60px;
right: 0;
/* background color */
width: 100%;
flex-direction: column;
align-items: flex-start;
padding: 10px;
}
.nav-links.active{
display: flex;
}
.nav-btn{
width: 100%;
text-align: center;
padding: 10px;
}
.hamburger{
display: flex;
}
} }

View file

@ -4,16 +4,18 @@ import secrets
import threading import threading
from ai import AI from ai import AI
from db import DB from db import DB
from weather import Weather
from voice import VoiceRecognition from voice import VoiceRecognition
class API: class API:
def __init__(self): def __init__(self):
self.crypt_size = 1 self.crypt_size = 4096
self.app = Flask(__name__) self.app = Flask(__name__)
self.ai_response = {} self.ai_response = {}
self.ai = AI() self.ai = AI()
self.db = DB() self.db = DB()
self.weather = Weather()
self.voice = VoiceRecognition() self.voice = VoiceRecognition()
self.db.load_database() self.db.load_database()
self.ai_response_lock = threading.Lock() self.ai_response_lock = threading.Lock()
@ -23,8 +25,12 @@ class API:
@self.app.route('/interstellar_ai/api/ai_create', methods=['GET']) @self.app.route('/interstellar_ai/api/ai_create', methods=['GET'])
def create_ai(): def create_ai():
access_token = secrets.token_urlsafe(self.crypt_size) access_token = secrets.token_urlsafe(self.crypt_size)
self.ai_response[access_token] = ""
return jsonify({'status': 200, 'access_token': access_token}) if access_token not in self.ai_response:
self.ai_response[access_token] = ""
return jsonify({'status': 200, 'access_token': access_token})
return jsonify({'status': 401, 'error': 'An error occurred, please try again.'})
@self.app.route('/interstellar_ai/api/ai_send', methods=['POST']) @self.app.route('/interstellar_ai/api/ai_send', methods=['POST'])
def send_ai(): def send_ai():
@ -99,6 +105,12 @@ class API:
return jsonify({'status': 401, 'response': "Invalid type"}) return jsonify({'status': 401, 'response': "Invalid type"})
@self.app.route('/interstellar_ai/api/weather', methods=['POST'])
def get_weather():
unit_type = request.args.get('unit_type')
city = request.args.get('city')
return jsonify({'status': 200, 'response': self.weather.getweather(unit_type, city)})
self.app.run(debug=True, host='0.0.0.0', port=5000) self.app.run(debug=True, host='0.0.0.0', port=5000)

View file

@ -9,3 +9,4 @@ SpeechRecognition
PocketSphinx PocketSphinx
google-cloud-speech google-cloud-speech
google-generativeai google-generativeai
python-weather

39
py/weather.py Normal file
View file

@ -0,0 +1,39 @@
import python_weather
class Weather:
@staticmethod
async def getweather(unit_type, city):
if unit_type == "imperial":
unit_type = python_weather.IMPERIAL
elif unit_type == "metric":
unit_type = python_weather.METRIC
async with python_weather.Client(unit=unit_type) as client:
weather = await client.get(city)
data = {
'temperature': weather.temperature,
'humidity': weather.humidity,
'unit': weather.unit,
'datetime': weather.datetime,
'coordinates': weather.coordinates,
'country': weather.country,
'daily_forecasts': weather.daily_forecasts,
'description': weather.description,
'feels_like': weather.feels_like,
'kind': weather.kind,
'local_population': weather.local_population,
'locale': weather.locale,
'location': weather.location,
'precipitation': weather.precipitation,
'pressure': weather.pressure,
'region': weather.region,
'ultraviolet': weather.ultraviolet,
'visibility': weather.visibility,
'wind_direction': weather.wind_direction,
'wind_speed': weather.wind_speed,
}
return data