Spaces:
Sleeping
Sleeping
import os | |
import uvicorn | |
from fastapi import FastAPI, HTTPException | |
from fastapi.responses import HTMLResponse | |
from fastapi.staticfiles import StaticFiles | |
from pydantic import BaseModel | |
from transformers import pipeline | |
import torch | |
from typing import Optional | |
# Inisialisasi FastAPI | |
app = FastAPI(title="LyonPoy AI Chat") | |
# All 11 models configuration | |
MODELS = { | |
"tinny-llama": { | |
"name": "Tinny Llama", | |
"model_path": "Lyon28/Tinny-Llama", | |
"task": "text-generation" | |
}, | |
"pythia": { | |
"name": "Pythia", | |
"model_path": "Lyon28/Pythia", | |
"task": "text-generation" | |
}, | |
"bert-tinny": { | |
"name": "BERT Tinny", | |
"model_path": "Lyon28/Bert-Tinny", | |
"task": "text-classification" | |
}, | |
"albert-base-v2": { | |
"name": "ALBERT Base V2", | |
"model_path": "Lyon28/Albert-Base-V2", | |
"task": "text-classification" | |
}, | |
"t5-small": { | |
"name": "T5 Small", | |
"model_path": "Lyon28/T5-Small", | |
"task": "text2text-generation" | |
}, | |
"gpt-2": { | |
"name": "GPT-2", | |
"model_path": "Lyon28/GPT-2", | |
"task": "text-generation" | |
}, | |
"gpt-neo": { | |
"name": "GPT-Neo", | |
"model_path": "Lyon28/GPT-Neo", | |
"task": "text-generation" | |
}, | |
"distilbert-base-uncased": { | |
"name": "DistilBERT", | |
"model_path": "Lyon28/Distilbert-Base-Uncased", | |
"task": "text-classification" | |
}, | |
"distil-gpt-2": { | |
"name": "DistilGPT-2", | |
"model_path": "Lyon28/Distil_GPT-2", | |
"task": "text-generation" | |
}, | |
"gpt-2-tinny": { | |
"name": "GPT-2 Tinny", | |
"model_path": "Lyon28/GPT-2-Tinny", | |
"task": "text-generation" | |
}, | |
"electra-small": { | |
"name": "ELECTRA Small", | |
"model_path": "Lyon28/Electra-Small", | |
"task": "text-classification" | |
} | |
} | |
class ChatRequest(BaseModel): | |
message: str | |
model: Optional[str] = "gpt-2" | |
# Startup | |
async def load_models(): | |
app.state.pipelines = {} | |
os.environ['HF_HOME'] = '/tmp/.cache/huggingface' | |
os.makedirs(os.environ['HF_HOME'], exist_ok=True) | |
print("π€ LyonPoy AI Chat Ready!") | |
# Frontend route | |
async def get_frontend(): | |
html_content = ''' | |
<!DOCTYPE html> | |
<html lang="id"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LyonPoy AI Chat</title> | |
<style> | |
* { margin: 0; padding: 0; box-sizing: border-box; } | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
height: 100vh; display: flex; justify-content: center; align-items: center; | |
} | |
.chat-container { | |
width: 400px; height: 600px; background: #fff; border-radius: 15px; | |
box-shadow: 0 20px 40px rgba(0,0,0,0.15); display: flex; flex-direction: column; overflow: hidden; | |
} | |
.chat-header { | |
background: linear-gradient(135deg, #25d366, #128c7e); color: white; | |
padding: 20px; text-align: center; | |
} | |
.chat-header h1 { font-size: 18px; font-weight: 600; margin-bottom: 8px; } | |
.model-selector { | |
background: rgba(255,255,255,0.2); border: none; color: white; | |
padding: 8px 12px; border-radius: 20px; font-size: 12px; cursor: pointer; | |
} | |
.chat-messages { | |
flex: 1; padding: 20px; overflow-y: auto; background: #f0f0f0; | |
display: flex; flex-direction: column; gap: 15px; | |
} | |
.message { | |
max-width: 80%; padding: 12px 16px; border-radius: 15px; | |
font-size: 14px; line-height: 1.4; animation: slideIn 0.3s ease; | |
} | |
.message.user { | |
background: #25d366; color: white; align-self: flex-end; border-bottom-right-radius: 5px; | |
} | |
.message.bot { | |
background: white; color: #333; align-self: flex-start; | |
border-bottom-left-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
} | |
.message-time { font-size: 11px; opacity: 0.7; margin-top: 5px; } | |
.chat-input-container { | |
padding: 20px; background: white; border-top: 1px solid #e0e0e0; | |
display: flex; gap: 10px; align-items: center; | |
} | |
.chat-input { | |
flex: 1; padding: 12px 16px; border: 1px solid #e0e0e0; | |
border-radius: 25px; font-size: 14px; outline: none; | |
} | |
.chat-input:focus { border-color: #25d366; box-shadow: 0 0 0 2px rgba(37, 211, 102, 0.2); } | |
.send-button { | |
background: #25d366; color: white; border: none; border-radius: 50%; | |
width: 45px; height: 45px; cursor: pointer; display: flex; | |
align-items: center; justify-content: center; | |
} | |
.send-button:hover { background: #128c7e; } | |
.send-button:disabled { background: #ccc; cursor: not-allowed; } | |
.welcome-message { | |
text-align: center; color: #666; font-size: 13px; | |
padding: 20px; border-radius: 10px; background: rgba(255,255,255,0.7); | |
} | |
.typing-indicator { | |
display: none; align-items: center; gap: 5px; padding: 12px 16px; | |
background: white; border-radius: 15px; align-self: flex-start; | |
} | |
.typing-dot { | |
width: 8px; height: 8px; background: #999; border-radius: 50%; | |
animation: typing 1.4s infinite; | |
} | |
.typing-dot:nth-child(2) { animation-delay: 0.2s; } | |
.typing-dot:nth-child(3) { animation-delay: 0.4s; } | |
@keyframes typing { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-10px); } } | |
@keyframes slideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } | |
@media (max-width: 480px) { .chat-container { width: 100vw; height: 100vh; border-radius: 0; } } | |
</style> | |
</head> | |
<body> | |
<div class="chat-container"> | |
<div class="chat-header"> | |
<h1>π€ LyonPoy AI Chat</h1> | |
<select class="model-selector" id="modelSelect"> | |
<option value="gpt-2">GPT-2 (General)</option> | |
<option value="tinny-llama">Tinny Llama</option> | |
<option value="pythia">Pythia</option> | |
<option value="gpt-neo">GPT-Neo</option> | |
<option value="distil-gpt-2">DistilGPT-2</option> | |
<option value="gpt-2-tinny">GPT-2 Tinny</option> | |
<option value="bert-tinny">BERT Tinny</option> | |
<option value="albert-base-v2">ALBERT Base V2</option> | |
<option value="distilbert-base-uncased">DistilBERT</option> | |
<option value="electra-small">ELECTRA Small</option> | |
<option value="t5-small">T5 Small</option> | |
</select> | |
</div> | |
<div class="chat-messages" id="chatMessages"> | |
<div class="welcome-message"> | |
π Halo! Saya LyonPoy AI Assistant.<br> | |
Pilih model di atas dan mulai chat dengan saya! | |
</div> | |
</div> | |
<div class="typing-indicator" id="typingIndicator"> | |
<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div> | |
</div> | |
<div class="chat-input-container"> | |
<input type="text" class="chat-input" id="chatInput" placeholder="Ketik pesan..." maxlength="500"> | |
<button class="send-button" id="sendButton">β€</button> | |
</div> | |
</div> | |
<script> | |
const chatMessages = document.getElementById('chatMessages'); | |
const chatInput = document.getElementById('chatInput'); | |
const sendButton = document.getElementById('sendButton'); | |
const modelSelect = document.getElementById('modelSelect'); | |
const typingIndicator = document.getElementById('typingIndicator'); | |
function scrollToBottom() { chatMessages.scrollTop = chatMessages.scrollHeight; } | |
function addMessage(content, isUser = false) { | |
const messageDiv = document.createElement('div'); | |
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`; | |
const time = new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); | |
messageDiv.innerHTML = `${content}<div class="message-time">${time}</div>`; | |
chatMessages.appendChild(messageDiv); | |
scrollToBottom(); | |
} | |
function showTyping() { typingIndicator.style.display = 'flex'; scrollToBottom(); } | |
function hideTyping() { typingIndicator.style.display = 'none'; } | |
async function sendMessage() { | |
const message = chatInput.value.trim(); | |
if (!message) return; | |
chatInput.disabled = true; sendButton.disabled = true; | |
addMessage(message, true); chatInput.value = ''; showTyping(); | |
try { | |
const response = await fetch('/chat', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ message: message, model: modelSelect.value }) | |
}); | |
const data = await response.json(); | |
hideTyping(); | |
if (data.status === 'success') { | |
addMessage(data.response); | |
} else { | |
addMessage('β Maaf, terjadi kesalahan. Coba lagi nanti.'); | |
} | |
} catch (error) { | |
hideTyping(); | |
addMessage('β Tidak dapat terhubung ke server.'); | |
} | |
chatInput.disabled = false; sendButton.disabled = false; chatInput.focus(); | |
} | |
sendButton.addEventListener('click', sendMessage); | |
chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); | |
modelSelect.addEventListener('change', () => { | |
const modelName = modelSelect.options[modelSelect.selectedIndex].text; | |
addMessage(`π Model diubah ke: ${modelName}`); | |
}); | |
window.addEventListener('load', () => chatInput.focus()); | |
</script> | |
</body> | |
</html> | |
''' | |
return HTMLResponse(content=html_content) | |
# Chat API | |
async def chat(request: ChatRequest): | |
try: | |
model_id = request.model.lower() | |
if model_id not in MODELS: | |
raise HTTPException(status_code=400, detail="Model tidak tersedia") | |
model_config = MODELS[model_id] | |
# Load model jika belum ada | |
if model_id not in app.state.pipelines: | |
print(f"β³ Loading {model_config['name']}...") | |
device = 0 if torch.cuda.is_available() else -1 | |
dtype = torch.float16 if torch.cuda.is_available() else torch.float32 | |
app.state.pipelines[model_id] = pipeline( | |
task=model_config["task"], | |
model=model_config["model_path"], | |
device=device, | |
torch_dtype=dtype | |
) | |
pipe = app.state.pipelines[model_id] | |
# Process berdasarkan task | |
if model_config["task"] == "text-generation": | |
result = pipe( | |
request.message, | |
max_length=min(len(request.message.split()) + 50, 200), | |
temperature=0.7, | |
do_sample=True, | |
pad_token_id=pipe.tokenizer.eos_token_id | |
)[0]['generated_text'] | |
# Clean output | |
if result.startswith(request.message): | |
result = result[len(request.message):].strip() | |
elif model_config["task"] == "text-classification": | |
output = pipe(request.message)[0] | |
result = f"Sentimen: {output['label']} (Confidence: {output['score']:.2f})" | |
elif model_config["task"] == "text2text-generation": | |
result = pipe(request.message, max_length=150)[0]['generated_text'] | |
return {"response": result, "model": model_config["name"], "status": "success"} | |
except Exception as e: | |
print(f"β Error: {e}") | |
raise HTTPException(status_code=500, detail="Terjadi kesalahan") | |
# Health check | |
async def health(): | |
return {"status": "healthy", "gpu": torch.cuda.is_available()} | |
# Run app | |
if __name__ == "__main__": | |
port = int(os.environ.get("PORT", 7860)) | |
uvicorn.run(app, host="0.0.0.0", port=port) |