NASA-AI-Voice / app.py
CCockrum's picture
Update app.py
5fb66c1 verified
raw
history blame
10.7 kB
import os
import re
import requests
import torch
import streamlit as st
from langchain_huggingface import HuggingFaceEndpoint
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from transformers import pipeline
from langdetect import detect # Ensure this package is installed
# βœ… Check for GPU or Default to CPU
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"βœ… Using device: {device}") # Debugging info
# βœ… Environment Variables
HF_TOKEN = os.getenv("HF_TOKEN")
if HF_TOKEN is None:
raise ValueError("HF_TOKEN is not set. Please add it to your environment variables.")
NASA_API_KEY = os.getenv("NASA_API_KEY")
if NASA_API_KEY is None:
raise ValueError("NASA_API_KEY is not set. Please add it to your environment variables.")
# βœ… Set Up Streamlit
st.set_page_config(page_title="HAL - NASA ChatBot", page_icon="πŸš€")
# βœ… Initialize Session State Variables (Ensuring Chat History Persists)
if "chat_history" not in st.session_state:
st.session_state.chat_history = [{"role": "assistant", "content": "Hello! How can I assist you today?"}]
if "listening" not in st.session_state:
st.session_state.listening = False
# βœ… Initialize Hugging Face Model (Explicitly Set to CPU/GPU)
def get_llm_hf_inference(model_id="meta-llama/Llama-2-7b-chat-hf", max_new_tokens=800, temperature=0.3):
return HuggingFaceEndpoint(
repo_id=model_id,
max_new_tokens=max_new_tokens,
temperature=temperature, # πŸ”₯ Lowered temperature for more factual and structured responses
token=HF_TOKEN,
task="text-generation",
device=-1 if device == "cpu" else 0 # βœ… Force CPU (-1) or GPU (0)
)
# βœ… Ensure English Responses
def ensure_english(text):
try:
detected_lang = detect(text)
if detected_lang != "en":
return "⚠️ Sorry, I only respond in English. Can you rephrase your question?"
except:
return "⚠️ Language detection failed. Please ask your question again."
return text
# βœ… Main Response Function (Fixing Repetition & Context)
def get_response(system_message, chat_history, user_text, max_new_tokens=800):
# βœ… Ensure conversation history is included correctly
filtered_history = "\n".join(
f"{msg['role'].capitalize()}: {msg['content']}"
for msg in chat_history[-5:] # βœ… Only keep the last 5 exchanges to prevent overflow
)
prompt = PromptTemplate.from_template(
"[INST] You are a highly knowledgeable AI assistant. Answer concisely, avoid repetition, and structure responses well."
"\n\nCONTEXT:\n{chat_history}\n"
"\nLATEST USER INPUT:\nUser: {user_text}\n"
"\n[END CONTEXT]\n"
"Assistant:"
)
# βœ… Invoke Hugging Face Model
hf = get_llm_hf_inference(max_new_tokens=max_new_tokens, temperature=0.3) # πŸ”₯ Lowered temperature
chat = prompt | hf.bind(skip_prompt=True) | StrOutputParser(output_key='content')
response = chat.invoke(input=dict(system_message=system_message, user_text=user_text, chat_history=filtered_history))
# Clean up the response - remove any "HAL:" prefix if present
response = response.split("HAL:")[-1].strip() if "HAL:" in response else response.strip()
response = ensure_english(response)
if not response:
response = "I'm sorry, but I couldn't generate a response. Can you rephrase your question?"
# βœ… Update conversation history
chat_history.append({'role': 'user', 'content': user_text})
chat_history.append({'role': 'assistant', 'content': response})
# βœ… Keep only last 10 exchanges to prevent unnecessary repetition
return response, chat_history[-10:]
# βœ… Streamlit UI
st.title("πŸš€ HAL - NASA AI Assistant")
# βœ… Add styles and speech recognition JavaScript
st.markdown("""
<style>
.user-msg, .assistant-msg {
padding: 11px;
border-radius: 10px;
margin-bottom: 5px;
width: fit-content;
max-width: 80%;
text-align: justify;
}
.user-msg { background-color: #696969; color: white; }
.assistant-msg { background-color: #333333; color: white; }
.container { display: flex; flex-direction: column; align-items: flex-start; }
.speech-button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 12px;
}
.speak-button {
background-color: #2196F3;
border: none;
color: white;
padding: 5px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 12px;
margin: 2px 2px;
cursor: pointer;
border-radius: 12px;
}
@media (max-width: 600px) { .user-msg, .assistant-msg { font-size: 16px; max-width: 100%; } }
</style>
<script>
// Speech Recognition Setup
let recognition;
let isListening = false;
function setupSpeechRecognition() {
try {
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.lang = 'en-US';
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.onresult = function(event) {
const speechResult = event.results[0][0].transcript;
document.getElementById('speech-result').value = speechResult;
document.getElementById('submit-speech').click();
};
recognition.onerror = function(event) {
console.error('Speech recognition error:', event.error);
isListening = false;
updateMicButton();
};
recognition.onend = function() {
isListening = false;
updateMicButton();
};
return true;
} catch (error) {
console.error('Speech recognition not supported:', error);
return false;
}
}
function toggleSpeechRecognition() {
if (!recognition) {
if (!setupSpeechRecognition()) {
alert('Speech recognition is not supported in your browser.');
return;
}
}
if (isListening) {
recognition.stop();
isListening = false;
} else {
recognition.start();
isListening = true;
}
updateMicButton();
}
function updateMicButton() {
const micButton = document.getElementById('mic-button');
if (micButton) {
micButton.textContent = isListening ? 'πŸ›‘ Stop Listening' : '🎀 Start Voice Input';
micButton.style.backgroundColor = isListening ? '#f44336' : '#4CAF50';
}
}
// Text-to-Speech functionality
function speakText(text) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
utterance.pitch = 1;
utterance.rate = 1;
window.speechSynthesis.speak(utterance);
}
// Initialize after the page loads
document.addEventListener('DOMContentLoaded', function() {
setupSpeechRecognition();
});
</script>
""", unsafe_allow_html=True)
# Add voice control components
col1, col2 = st.columns([4, 1])
with col1:
user_input = st.chat_input("Type your message here...")
with col2:
st.markdown("""
<button id="mic-button" onclick="toggleSpeechRecognition()" class="speech-button">
🎀 Start Voice Input
</button>
<input type="hidden" id="speech-result">
<button id="submit-speech" style="display:none;"></button>
""", unsafe_allow_html=True)
# Handle form for speech input (hidden)
speech_result = st.text_input("Speech Result", key="speech_input", label_visibility="collapsed")
if speech_result:
user_input = speech_result
# Reset the speech input
st.session_state.speech_input = ""
if user_input:
# Get response and update chat history
response, st.session_state.chat_history = get_response(
system_message="You are a helpful AI assistant specializing in NASA and space information.",
user_text=user_input,
chat_history=st.session_state.chat_history
)
# βœ… Display chat history with speak buttons
st.markdown("<div class='container'>", unsafe_allow_html=True)
for i, message in enumerate(st.session_state.chat_history):
if message["role"] == "user":
st.markdown(f"<div class='user-msg'><strong>You:</strong> {message['content']}</div>", unsafe_allow_html=True)
else:
# Fix: Use JavaScript data attribute instead of trying to escape in f-string
content_for_id = f"msg-{i}"
st.markdown(
f"""<div class='assistant-msg'>
<strong>HAL:</strong> {message['content']}
<button onclick="speakText(document.getElementById('{content_for_id}').textContent)" class="speak-button">
πŸ”Š Speak
</button>
<span id="{content_for_id}" style="display:none">{message['content']}</span>
</div>""",
unsafe_allow_html=True
)
st.markdown("</div>", unsafe_allow_html=True)
# Add JavaScript event listener for the submit button
components_js = """
<script>
document.getElementById('submit-speech').addEventListener('click', function() {
const speechResult = document.getElementById('speech-result').value;
if (speechResult) {
// Update the Streamlit text input with the speech result
const textInputs = document.querySelectorAll('input[type="text"]');
if (textInputs.length > 0) {
const lastInput = textInputs[0];
lastInput.value = speechResult;
lastInput.dispatchEvent(new Event('input', { bubbles: true }));
// Find and click the submit button
setTimeout(() => {
const buttons = document.querySelectorAll('button[kind="primaryForm"]');
for (const button of buttons) {
if (button.textContent.includes('Submit')) {
button.click();
break;
}
}
}, 100);
}
}
});
</script>
"""
st.components.v1.html(components_js, height=0)