Spaces:
Running
Running
import os | |
import logging | |
from flask import Flask, render_template_string, send_file, abort, request, jsonify | |
from huggingface_hub import hf_hub_download, login as hf_login | |
from dotenv import load_dotenv | |
load_dotenv() | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
hf_token = os.getenv("HF_TOKEN") | |
try: | |
hf_login(token=hf_token) | |
logger.info("Hugging Face Login Successful") | |
except Exception as e: | |
logger.error(f"Hugging Face Login Error: {e}") | |
app = Flask(__name__) | |
MODEL_FILENAME = 'gemma3-1b-it-int4.task' | |
HUGGINGFACE_REPO = 'litert-community/Gemma3-1B-IT' | |
MODEL_LOCAL_PATH = os.path.join(os.getcwd(), MODEL_FILENAME) | |
loaded_model = None | |
def download_model_file(): | |
if not os.path.exists(MODEL_LOCAL_PATH): | |
logger.info("Model file not found locally. Downloading from Hugging Face...") | |
try: | |
hf_hub_download(repo_id=HUGGINGFACE_REPO, filename=MODEL_FILENAME, local_dir=".", local_dir_use_symlinks=False) | |
logger.info(f"Download Completed: {MODEL_LOCAL_PATH}") | |
except Exception as e: | |
logger.error(f"Error downloading model file: {e}") | |
raise | |
else: | |
logger.info("Model file already exists locally.") | |
return MODEL_LOCAL_PATH | |
download_model_file() | |
def download_model(): | |
if os.path.exists(MODEL_LOCAL_PATH): | |
return send_file(MODEL_LOCAL_PATH, as_attachment=True, download_name=MODEL_FILENAME) | |
else: | |
abort(404) | |
def perform_inference(input_text: str) -> str: | |
return "Backend inference is not used with MediaPipe. Inference happens in the browser." | |
HTML_CONTENT = """<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>LLM Chatbot Demo with MediaPipe</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
<style> | |
:root { | |
--primary-color: #2E4053; | |
--secondary-color: #3498db; | |
--background-color: #f8f9fa; | |
--text-color: #343a40; | |
--light-gray: #e0e0e0; | |
--message-user-bg: #e2f0cb; | |
--message-bot-bg: #fff; | |
} | |
body { | |
font-family: 'Roboto', sans-serif; | |
margin: 0; | |
padding: 0; | |
background-color: var(--background-color); | |
color: var(--text-color); | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
width: 100vw; | |
overflow: hidden; | |
} | |
#app-container { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 100%; | |
height: 100%; | |
} | |
.chatbot-container { | |
background-color: #fff; | |
border-radius: 12px; | |
box-shadow: 0 10px 30px rgba(0,0,0,0.1); | |
width: 95%; | |
height: 95%; | |
max-width: 800px; | |
max-height: 700px; | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
animation: fadeIn 0.5s ease-out; | |
border: 1px solid var(--light-gray); | |
} | |
.chat-header { | |
background-color: var(--primary-color); | |
color: #fff; | |
padding: 20px; | |
text-align: left; | |
border-bottom: 1px solid var(--light-gray); | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
border-top-left-radius: 12px; | |
border-top-right-radius: 12px; | |
} | |
.header-info { | |
display: flex; | |
align-items: center; | |
} | |
.header-icon { | |
width: 45px; | |
height: 45px; | |
background-color: #fff; | |
color: var(--primary-color); | |
border-radius: 50%; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
font-size: 1.4rem; | |
margin-right: 15px; | |
} | |
.header-text h1 { | |
font-size: 1.5rem; | |
margin-bottom: 5px; | |
color: #fff; | |
font-weight: 500; | |
} | |
.header-text p { | |
font-size: 0.95rem; | |
color: rgba(255,255,255,0.8); | |
margin: 0; | |
} | |
.status-indicator { | |
font-size: 0.9rem; | |
color: #5cb85c; | |
} | |
.header-actions button { | |
background: none; | |
border: none; | |
color: rgba(255,255,255,0.7); | |
cursor: pointer; | |
font-size: 1rem; | |
transition: color 0.2s; | |
margin-left: 15px; | |
} | |
.header-actions button:hover { | |
color: #fff; | |
} | |
.chat-history { | |
padding: 20px; | |
overflow-y: auto; | |
flex-grow: 1; | |
display: flex; | |
flex-direction: column; | |
scrollbar-width: thin; | |
scrollbar-color: var(--light-gray) var(--background-color); | |
} | |
.chat-history::-webkit-scrollbar { | |
width: 6px; | |
} | |
.chat-history::-webkit-scrollbar-track { | |
background: var(--background-color); | |
border-radius: 5px; | |
} | |
.chat-history::-webkit-scrollbar-thumb { | |
background: var(--light-gray); | |
border-radius: 5px; | |
} | |
.message { | |
background-color: var(--message-bot-bg); | |
color: var(--text-color); | |
border-radius: 18px; | |
padding: 14px 18px; | |
margin-bottom: 12px; | |
width: fit-content; | |
max-width: 75%; | |
word-wrap: break-word; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
position: relative; | |
border: 1px solid var(--light-gray); | |
} | |
.message.user-message { | |
background-color: var(--message-user-bg); | |
color: var(--text-color); | |
align-self: flex-end; | |
border-bottom-right-radius: 5px; | |
border-top-right-radius: 18px; | |
border-top-left-radius: 18px; | |
border-bottom-left-radius: 18px; | |
border: none; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.08); | |
} | |
.message.bot-message { | |
align-self: flex-start; | |
border-bottom-left-radius: 5px; | |
border-top-left-radius: 18px; | |
border-top-right-radius: 18px; | |
border-bottom-right-radius: 18px; | |
border: 1px solid var(--light-gray); | |
} | |
.message .timestamp { | |
position: absolute; | |
bottom: -15px; | |
right: 8px; | |
font-size: 0.7rem; | |
color: #777; | |
} | |
.chat-input-area { | |
padding: 20px; | |
background-color: #fff; | |
border-top: 1px solid var(--light-gray); | |
display: flex; | |
align-items: center; | |
border-bottom-left-radius: 12px; | |
border-bottom-right-radius: 12px; | |
box-shadow: inset 0 2px 5px rgba(0,0,0,0.03); | |
} | |
.chat-input { | |
flex-grow: 1; | |
border: 1px solid var(--light-gray); | |
border-radius: 8px; | |
padding: 12px 15px; | |
font-size: 1rem; | |
font-family: 'Roboto Mono', monospace; | |
margin-right: 10px; | |
box-shadow: none; | |
transition: border-color 0.2s ease; | |
color: var(--text-color); | |
} | |
.chat-input:focus { | |
outline: none; | |
border-color: var(--secondary-color); | |
box-shadow: 0 0 0 2px rgba(var(--secondary-color-rgb), 0.2); | |
} | |
.send-button { | |
background-color: var(--secondary-color); | |
color: white; | |
border: none; | |
border-radius: 8px; | |
padding: 12px 18px; | |
cursor: pointer; | |
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.2s ease; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
font-size: 1rem; | |
} | |
.send-button:hover { | |
background-color: #257ab5; | |
transform: translateY(-1px); | |
box-shadow: 0 4px 8px rgba(0,0,0,0.12); | |
} | |
.send-button:focus { | |
outline: none; | |
} | |
.send-button:disabled { | |
background-color: var(--light-gray); | |
color: #777; | |
cursor: not-allowed; | |
box-shadow: none; | |
transform: none; | |
} | |
#loading-indicator { | |
margin-left: 10px; | |
font-style: italic; | |
color: #777; | |
opacity: 0.8; | |
animation: pulse 1.5s infinite; | |
display: none; | |
font-size: 0.9rem; | |
} | |
#model-status { | |
margin-left: 10px; | |
font-size: 0.9rem; | |
color: #777; | |
} | |
#model-status.loaded { | |
color: #5cb85c; | |
} | |
#model-status.loading { | |
font-style: italic; | |
} | |
#output { | |
white-space: pre-wrap; | |
word-break: break-word; | |
display: none; | |
} | |
</style> | |
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/dist/tasks-genai.js" crossorigin="anonymous"></script> | |
</head> | |
<body> | |
<div id="app-container"> | |
<div class="chatbot-container"> | |
<div class="chat-header"> | |
<div class="header-info"> | |
<div class="header-icon"><i class="fas fa-robot"></i></div> | |
<div class="header-text"> | |
<h1>AI Chat Assistant</h1> | |
<p>Powered by MediaPipe GenAI</p> | |
<span id="model-status" class="loading">Loading Model...</span> | |
</div> | |
</div> | |
<div class="header-actions"> | |
<button id="clear-chat-button" title="Clear Chat"><i class="fas fa-trash"></i></button> | |
</div> | |
</div> | |
<div class="chat-history" id="chat-history"> | |
</div> | |
<div class="chat-input-area"> | |
<input type="text" id="input" class="chat-input" placeholder="Type your message..." disabled> | |
<button id="submit" class="send-button" disabled><i class="fas fa-paper-plane"></i> Send</button> | |
<span id="loading-indicator">Thinking...</span> | |
<div id="output" style="display: none;"></div> | |
</div> | |
</div> | |
</div> | |
<script type="module" src="/index.js"></script> | |
</body> | |
</html> | |
""" | |
JS_CONTENT = """ | |
import { FilesetResolver, LlmInference } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/dist/tasks-genai.js'; | |
const chatInput = document.getElementById('input'); | |
const sendButton = document.getElementById('submit'); | |
const chatHistory = document.getElementById('chat-history'); | |
const loadingIndicator = document.getElementById('loading-indicator'); | |
const clearChatButton = document.getElementById('clear-chat-button'); | |
const modelStatus = document.getElementById('model-status'); | |
const outputElement = document.getElementById('output'); | |
let isModelLoaded = false; | |
let messageHistory = []; | |
let llmInference; | |
function createMessageElement(text, isUserMessage) { | |
const messageDiv = document.createElement('div'); | |
messageDiv.classList.add('message'); | |
text = text.replace(/\\n/g, '<br>'); | |
messageDiv.innerHTML = text; | |
if (isUserMessage) { | |
messageDiv.classList.add('user-message'); | |
} else { | |
messageDiv.classList.add('bot-message'); | |
} | |
const timestampDiv = document.createElement('div'); | |
timestampDiv.classList.add('timestamp'); | |
const now = new Date(); | |
const hours = String(now.getHours()).padStart(2, '0'); | |
const minutes = String(now.getMinutes()).padStart(2, '0'); | |
timestampDiv.textContent = `${hours}:${minutes}`; | |
messageDiv.appendChild(timestampDiv); | |
return messageDiv; | |
} | |
function displayBotMessage(text) { | |
const botMessageElement = createMessageElement(text, false); | |
chatHistory.appendChild(botMessageElement); | |
chatHistory.scrollTop = chatHistory.scrollHeight; | |
messageHistory.push({ text: text, isUserMessage: false }); | |
sendButton.disabled = false; | |
loadingIndicator.style.display = 'none'; | |
} | |
function renderMessageHistory() { | |
chatHistory.innerHTML = ''; | |
messageHistory.forEach(message => { | |
const messageElement = createMessageElement(message.text, message.isUserMessage); | |
chatHistory.appendChild(messageElement); | |
}); | |
chatHistory.scrollTop = chatHistory.scrollHeight; | |
} | |
function clearChatHistory() { | |
messageHistory = []; | |
renderMessageHistory(); | |
} | |
function displayPartialResults(partialResults, complete) { | |
outputElement.textContent += partialResults; | |
if (complete) { | |
if (!outputElement.textContent) { | |
outputElement.textContent = 'Result is empty'; | |
} | |
sendButton.disabled = false; | |
loadingIndicator.style.display = 'none'; | |
outputElement.style.display = 'none'; | |
displayBotMessage(outputElement.textContent); | |
} | |
} | |
async function initializeChatbot() { | |
try { | |
const filesetResolver = await FilesetResolver.forGenAiTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/dist/wasm'); | |
llmInference = await LlmInference.createFromFileset(filesetResolver, '/download'); | |
isModelLoaded = true; | |
sendButton.disabled = false; | |
chatInput.disabled = false; | |
modelStatus.textContent = "Model Loaded"; | |
modelStatus.classList.remove('loading'); | |
modelStatus.classList.add('loaded'); | |
console.log('MediaPipe GenAI Model Initialized.'); | |
} catch (error) { | |
console.error("Error initializing MediaPipe GenAI:", error); | |
modelStatus.textContent = "Model Load Error"; | |
modelStatus.classList.remove('loading'); | |
modelStatus.classList.add('error'); | |
} finally { | |
loadingIndicator.style.display = 'none'; | |
renderMessageHistory(); | |
} | |
} | |
sendButton.onclick = async () => { | |
if (!isModelLoaded) { | |
alert('Chatbot is not initialized yet.'); | |
return; | |
} | |
const userMessageText = chatInput.value.trim(); | |
if (!userMessageText) return; | |
const userMessageElement = createMessageElement(userMessageText, true); | |
chatHistory.appendChild(userMessageElement); | |
chatHistory.scrollTop = chatHistory.scrollHeight; | |
messageHistory.push({ text: userMessageText, isUserMessage: true }); | |
chatInput.value = ''; | |
sendButton.disabled = true; | |
loadingIndicator.style.display = 'inline-block'; | |
outputElement.style.display = 'block'; | |
outputElement.textContent = ''; | |
try { | |
await llmInference.generateResponse(userMessageText, displayPartialResults); | |
} catch (error) { | |
console.error("Inference error:", error); | |
displayBotMessage('Error generating response. Please try again.'); | |
sendButton.disabled = false; | |
loadingIndicator.style.display = 'none'; | |
outputElement.style.display = 'none'; | |
} | |
}; | |
chatInput.addEventListener('keydown', (event) => { | |
if (event.key === 'Enter' && !event.shiftKey) { | |
event.preventDefault(); | |
sendButton.click(); | |
} | |
}); | |
clearChatButton.onclick = clearChatHistory; | |
document.addEventListener('DOMContentLoaded', initializeChatbot); | |
""" | |
def index(): | |
return render_template_string(HTML_CONTENT) | |
def serve_js(): | |
return JS_CONTENT, 200, {'Content-Type': 'application/javascript'} | |
def api_infer(): | |
return jsonify({'error': 'Backend inference is not used with MediaPipe. Inference happens in the browser.'}), 501 | |
if __name__ == '__main__': | |
logger.info("Starting Flask application on port 7860") | |
app.run(debug=True, host="0.0.0.0", port=7860) |