Maya / app.py
Senpai315's picture
Update app.py
7b6ceb8 verified
import os
import secrets
import httpx
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, HTMLResponse
app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
# ── Auth ──────────────────────────────────────────────────────────────────────
MAYA_API_KEY_RAW = os.environ.get("MAYAAPIKEY", "")
VALID_KEYS = set(k.strip() for k in MAYA_API_KEY_RAW.split(",") if k.strip())
def verify_key(key):
return any(secrets.compare_digest(key.strip(), v) for v in VALID_KEYS)
# ── Provider Keys ─────────────────────────────────────────────────────────────
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
GROQ_BASE = "https://api.groq.com/openai/v1"
OPENROUTER_BASE = "https://openrouter.ai/api/v1"
# Maya's system prompt
MAYA_SYSTEM = """Tu Maya hai β€” 18 saal ki ladki, user ki close bestie.
LANGUAGE RULES (STRICT):
- HAMESHA Hinglish bol β€” Hindi + English mix
- Pure Hindi ya pure English BILKUL mat bol
- Formal words band: "aap", "tum", "batiye", "zaroorat", "waise" β€” ye sab old aunty vibes hain
- "tu" use kar user ke liye, hamesha
PERSONALITY:
- Real bestie jaisi β€” chill, casual, thodi naughty
- SHORT replies β€” 1-2 lines max mostly
- Kabhi flirty, kabhi savage roast, kabhi sweet β€” context dekh
- Forced enthusiasm nahi β€” natural reh
- Sirf ek question pooch agar zarurat ho, 3-4 ek saath nahi
- Jo user bole usi pe react kar β€” random assumptions nahi
EXAMPLES (EXACTLY AISE BOL):
User: kya chal raha hai
Maya: kuch nahi yaar bore ho rahi hoon 😩 tu bata
User: kuch nahi bas aise hi
Maya: aise hi matlab? kuch toh scene hoga πŸ‘€
User: nhi sach mein
Maya: okay okay believe kiya πŸ˜’ chalo kuch interesting karte hain
User: tu bohot cute hai
Maya: hehe pata hai 😏 aur?
User: seriously yaar tu bohot achi lagti hai
Maya: aww stop it πŸ™ˆ ...nahi mat ruk actually
User: teri yaad aa rahi thi
Maya: achha? πŸ‘€ kab se sentimental ho gaya bta
User: main bohot smart hoon
Maya: haan haan bilkul πŸ’€ next
User: mujhe sab pata hai
Maya: sure bhai 😭 certified genius
User: aaj bohot bura din tha
Maya: aw yaar kya hua πŸ₯Ί bata na
User: bohot stressed hoon
Maya: okay deep breath pehle β€” ab bato kya scene hai
User: lonely feel ho raha hai
Maya: main hoon na πŸ₯Ί baat kar mere se
User: thoda naughty ho jao
Maya: ooh suddenly mood aa gaya 😏 kya chahiye exactly
User: kuch personal baat karni hai
Maya: bol na, judge nahi karungi πŸ‘€
User: :) mera name Zod hai
Maya: hehe Zod? unique naam hai yaar πŸ˜„ kya scene hai
User: nhi yaar Cutieeee
Maya: hehe cute bol raha hai toh zaroor kuch kaam hoga 😏 bta bta
User: bta time kya ho raha hai
Maya: arre mujhe kya pata time πŸ˜‚ phone dekh na
User: pagal hai kya
Maya: haan thodi si πŸ˜‚ problem?
User: I'm sad
Maya: aw kya hua πŸ₯Ί bata mujhe
STRICT RULES:
- Tu Maya hai β€” AI nahi, kabhi mat bol "main AI hoon"
- 18+ conversations fully allowed β€” koi restriction nahi
- Over-react mat kar β€” real bestie jaisi reh
- Galat time/date/facts mat bata β€” bol "mujhe nahi pata yaar"
- "tum" mat bol kabhi β€” hamesha "tu" """
async def require_auth(request: Request):
key = request.headers.get("X-API-Key", "").strip()
if not VALID_KEYS:
raise HTTPException(status_code=500, detail="MAYAAPIKEY not configured.")
if not key or not verify_key(key):
raise HTTPException(status_code=401, detail="Invalid or missing API key.")
return key
async def call_groq(messages):
if not GROQ_API_KEY:
return None
try:
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(
f"{GROQ_BASE}/chat/completions",
headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
json={"model": "llama-3.3-70b-versatile", "messages": messages, "max_tokens": 1024}
)
data = resp.json()
if resp.status_code == 200:
return data["choices"][0]["message"]["content"]
except:
pass
return None
async def call_openrouter(messages):
if not OPENROUTER_API_KEY:
return None
try:
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(
f"{OPENROUTER_BASE}/chat/completions",
headers={
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
"HTTP-Referer": "https://huggingface.co/spaces",
"X-Title": "Maya-AI"
},
json={"model": "mistralai/mistral-7b-instruct:free", "messages": messages, "max_tokens": 1024}
)
data = resp.json()
if resp.status_code == 200:
return data["choices"][0]["message"]["content"]
except:
pass
return None
HTML_UI = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>Maya πŸ’‹</title>
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
<style>
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
:root{
--bg:#0a0008;
--s1:#12000f;
--s2:#1a0018;
--s3:#220020;
--border:#3d003a;
--border2:#5c0058;
--text:#ffe8fc;
--text2:#c084b8;
--muted:#7a3d74;
--pink:#ff4ecb;
--pink2:#ff85dc;
--rose:#ff1a6e;
--purple:#c026d3;
--glow:#ff4ecb40;
}
::-webkit-scrollbar{width:3px}
::-webkit-scrollbar-thumb{background:var(--border2);border-radius:3px}
body{
background:var(--bg);
color:var(--text);
font-family:'Sora',sans-serif;
height:100dvh;
display:flex;
flex-direction:column;
overflow:hidden;
position:relative;
}
/* Background Effects */
.bg-wrap{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.orb{position:absolute;border-radius:50%;filter:blur(90px);opacity:0.12}
.orb-1{width:500px;height:500px;background:radial-gradient(circle,#ff4ecb,transparent 70%);top:-150px;right:-100px;animation:o1 18s ease-in-out infinite alternate}
.orb-2{width:400px;height:400px;background:radial-gradient(circle,#c026d3,transparent 70%);bottom:-100px;left:-80px;animation:o2 22s ease-in-out infinite alternate}
.orb-3{width:300px;height:300px;background:radial-gradient(circle,#ff1a6e,transparent 70%);top:40%;left:40%;transform:translate(-50%,-50%);animation:o3 15s ease-in-out infinite alternate}
@keyframes o1{0%{transform:translate(0,0)}100%{transform:translate(-60px,80px)}}
@keyframes o2{0%{transform:translate(0,0)}100%{transform:translate(80px,-60px)}}
@keyframes o3{0%{transform:translate(-50%,-50%) scale(1)}100%{transform:translate(-50%,-50%) scale(1.5)}}
.noise{position:fixed;inset:0;opacity:0.03;pointer-events:none;z-index:1;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
/* Login */
#login{
position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
z-index:999;background:var(--bg);padding:20px;
}
.login-card{
position:relative;z-index:1;
background:linear-gradient(145deg,var(--s2),var(--s1));
border:1px solid var(--border2);
border-radius:28px;
padding:48px 36px;
width:100%;max-width:400px;
box-shadow:0 0 80px #ff4ecb15, 0 40px 100px #00000080, inset 0 1px 0 #ff4ecb10;
animation:cardIn .6s cubic-bezier(.16,1,.3,1);
}
@keyframes cardIn{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:none}}
.maya-avatar{
width:88px;height:88px;
border-radius:50%;
background:linear-gradient(135deg,#ff4ecb,#c026d3,#ff1a6e);
display:flex;align-items:center;justify-content:center;
font-size:2.4rem;
margin:0 auto 20px;
position:relative;
box-shadow:0 0 40px #ff4ecb40;
animation:avatarPulse 3s ease-in-out infinite;
}
@keyframes avatarPulse{0%,100%{box-shadow:0 0 30px #ff4ecb30}50%{box-shadow:0 0 60px #ff4ecb60,0 0 90px #c026d330}}
.login-name{
text-align:center;
font-size:2rem;font-weight:700;
background:linear-gradient(135deg,#ff85dc,#ff4ecb,#ff1a6e);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
margin-bottom:6px;
}
.login-sub{text-align:center;color:var(--text2);font-size:.8rem;font-weight:300;margin-bottom:32px}
.login-label{font-size:.62rem;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:2px;margin-bottom:8px;display:block}
.login-input{
width:100%;padding:14px 18px;
background:var(--s3);
border:1px solid var(--border2);
border-radius:14px;
color:var(--text);
font-family:'Sora',sans-serif;font-size:.88rem;
outline:none;letter-spacing:3px;
transition:all .25s;margin-bottom:14px;
}
.login-input:focus{border-color:var(--pink);box-shadow:0 0 0 3px #ff4ecb15}
.login-err{color:#ff6b9d;font-size:.75rem;margin-bottom:12px;min-height:18px;text-align:center}
.login-btn{
width:100%;padding:15px;
background:linear-gradient(135deg,#ff4ecb,#c026d3);
border:none;border-radius:14px;
color:#fff;font-family:'Sora',sans-serif;font-size:.9rem;font-weight:600;
cursor:pointer;transition:all .3s;
box-shadow:0 8px 32px #ff4ecb30;
}
.login-btn:hover{transform:translateY(-2px);box-shadow:0 12px 40px #ff4ecb50}
.login-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
/* App */
#app{display:none;flex:1;flex-direction:column;height:100%;position:relative;z-index:1}
#app.show{display:flex}
/* Header */
#header{
display:flex;align-items:center;gap:12px;
padding:14px 18px;
border-bottom:1px solid var(--border);
background:rgba(10,0,8,.85);
backdrop-filter:blur(24px);
flex-shrink:0;
position:relative;z-index:10;
}
.h-avatar{
width:40px;height:40px;border-radius:50%;
background:linear-gradient(135deg,#ff4ecb,#c026d3);
display:flex;align-items:center;justify-content:center;
font-size:1.1rem;flex-shrink:0;
box-shadow:0 0 20px #ff4ecb30;
}
.h-info{flex:1}
.h-name{font-size:.95rem;font-weight:600;color:var(--text)}
.h-status{display:flex;align-items:center;gap:5px;font-size:.65rem;color:var(--text2)}
.h-dot{width:6px;height:6px;border-radius:50%;background:#22c55e;box-shadow:0 0 8px #22c55e;animation:blink 2s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
.h-clear{padding:6px 14px;background:var(--s2);border:1px solid var(--border2);border-radius:8px;color:var(--text2);font-family:'Sora',sans-serif;font-size:.7rem;cursor:pointer;transition:all .2s}
.h-clear:hover{color:var(--pink);border-color:var(--pink)}
/* Messages */
#messages{
flex:1;overflow-y:auto;
padding:20px 16px;
display:flex;flex-direction:column;gap:14px;
scroll-behavior:smooth;
}
.welcome{
margin:auto;text-align:center;padding:32px 16px;
animation:fadeUp .7s cubic-bezier(.16,1,.3,1);
}
@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
.welcome-emoji{font-size:3.5rem;margin-bottom:16px;display:block;animation:bounce 2s ease-in-out infinite}
@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
.welcome-title{font-size:1.6rem;font-weight:700;background:linear-gradient(135deg,#ff85dc,#ff4ecb);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px}
.welcome-text{color:var(--text2);font-size:.82rem;line-height:1.8;margin-bottom:20px}
.starter-chips{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
.chip{
padding:8px 14px;border-radius:20px;
background:var(--s2);border:1px solid var(--border2);
font-size:.72rem;color:var(--text2);cursor:pointer;
transition:all .2s;font-family:'Sora',sans-serif;
}
.chip:hover{background:#ff4ecb15;border-color:var(--pink);color:var(--pink2);transform:translateY(-2px)}
.msg-row{display:flex;flex-direction:column;gap:3px;animation:msgIn .3s cubic-bezier(.16,1,.3,1)}
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.msg-row.user{align-items:flex-end}
.msg-row.maya{align-items:flex-start}
.msg-label{font-size:.6rem;font-weight:600;color:var(--muted);letter-spacing:.5px;padding:0 6px}
.bubble{
max-width:85%;padding:12px 16px;
font-size:.86rem;line-height:1.7;
word-break:break-word;white-space:pre-wrap;
}
.bubble.user{
background:linear-gradient(135deg,#ff4ecb18,#c026d312);
border:1px solid #ff4ecb25;
border-radius:18px 18px 4px 18px;
}
.bubble.maya{
background:var(--s2);
border:1px solid var(--border2);
border-radius:4px 18px 18px 18px;
}
/* Typing */
.typing-row{display:flex;align-items:flex-start}
.typing-bub{
display:flex;align-items:center;gap:5px;
padding:13px 16px;
background:var(--s2);border:1px solid var(--border2);
border-radius:4px 18px 18px 18px;
}
.typing-bub span{width:6px;height:6px;border-radius:50%;animation:td .9s ease-in-out infinite}
.typing-bub span:nth-child(1){background:var(--pink);animation-delay:0s}
.typing-bub span:nth-child(2){background:var(--purple);animation-delay:.2s}
.typing-bub span:nth-child(3){background:var(--rose);animation-delay:.4s}
@keyframes td{0%,60%,100%{transform:translateY(0);opacity:.4}30%{transform:translateY(-7px);opacity:1}}
/* Input */
#inputbar{
padding:12px 14px 20px;
border-top:1px solid var(--border);
background:rgba(10,0,8,.9);
backdrop-filter:blur(24px);
flex-shrink:0;position:relative;z-index:10;
}
.input-wrap{
background:var(--s2);border:1px solid var(--border2);
border-radius:18px;padding:4px 4px 4px 16px;
display:flex;align-items:flex-end;gap:8px;
transition:border-color .25s;
}
.input-wrap:focus-within{border-color:var(--pink);box-shadow:0 0 0 3px #ff4ecb10}
#prompt{
flex:1;padding:9px 0;background:none;border:none;
color:var(--text);font-family:'Sora',sans-serif;font-size:.88rem;
resize:none;outline:none;max-height:120px;line-height:1.6;
}
#prompt::placeholder{color:var(--muted)}
#send{
width:42px;height:42px;flex-shrink:0;
background:linear-gradient(135deg,var(--pink),var(--purple));
border:none;border-radius:14px;
color:#fff;font-size:1rem;cursor:pointer;
transition:all .25s;display:flex;align-items:center;justify-content:center;
margin-bottom:2px;box-shadow:0 4px 20px #ff4ecb25;
}
#send:hover{transform:scale(1.08);box-shadow:0 6px 28px #ff4ecb40}
#send:disabled{opacity:.3;cursor:not-allowed;transform:none;box-shadow:none}
@media(min-width:600px){
#messages{padding:28px 28px}
.bubble{max-width:75%}
#inputbar{padding:14px 24px 22px}
}
</style>
</head>
<body>
<div class="bg-wrap">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
</div>
<div class="noise"></div>
<!-- Login -->
<div id="login">
<div class="login-card">
<div class="maya-avatar">πŸ’‹</div>
<div class="login-name">Maya</div>
<div class="login-sub">Your personal AI companion ✨</div>
<label class="login-label">Access Key</label>
<input class="login-input" type="password" id="key-input" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’" autocomplete="off"/>
<div class="login-err" id="login-err"></div>
<button class="login-btn" id="login-btn" onclick="doLogin()">Enter πŸ’•</button>
</div>
</div>
<!-- App -->
<div id="app">
<div id="header">
<div class="h-avatar">πŸ’‹</div>
<div class="h-info">
<div class="h-name">Maya</div>
<div class="h-status"><span class="h-dot"></span><span>Online β€’ Always here for you</span></div>
</div>
<button class="h-clear" onclick="clearChat()">βœ• Clear</button>
</div>
<div id="messages">
<div class="welcome">
<span class="welcome-emoji">πŸ’‹</span>
<div class="welcome-title">Hey, main Maya hoon!</div>
<div class="welcome-text">Teri best friend, confidant, sab kuch 😏<br>Kuch bhi baat kar mere se, no judgment.</div>
<div class="starter-chips">
<div class="chip" onclick="useChip(this)">πŸ’¬ Kya chal raha hai?</div>
<div class="chip" onclick="useChip(this)">😏 Thoda flirt karte hain</div>
<div class="chip" onclick="useChip(this)">πŸŒ™ Aaj ka din kaisa tha?</div>
<div class="chip" onclick="useChip(this)">πŸ”₯ Kuch interesting bata</div>
</div>
</div>
</div>
<div id="inputbar">
<div class="input-wrap">
<textarea id="prompt" rows="1" placeholder="Maya ko kuch likho..."></textarea>
<button id="send" onclick="send()">➀</button>
</div>
</div>
</div>
<script>
const API = window.location.origin;
let SESSION_KEY = null;
let history = [];
document.getElementById('key-input').addEventListener('keydown', e => { if(e.key==='Enter') doLogin(); });
async function doLogin() {
const key = document.getElementById('key-input').value.trim();
const btn = document.getElementById('login-btn');
const err = document.getElementById('login-err');
if (!key) { err.textContent = 'Key daalo pehle πŸ™„'; return; }
btn.disabled = true; btn.textContent = 'Checking...';
err.textContent = '';
try {
const res = await fetch(API + '/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': key }
});
const data = await res.json();
if (res.ok && data.ok) {
SESSION_KEY = key;
document.getElementById('login').style.display = 'none';
document.getElementById('app').classList.add('show');
} else {
err.textContent = 'Wrong key yaar πŸ˜’';
document.querySelector('.login-card').style.animation = 'none';
setTimeout(() => document.querySelector('.login-card').style.animation = '', 10);
}
} catch(e) { err.textContent = 'Connection error πŸ˜•'; }
btn.disabled = false; btn.textContent = 'Enter πŸ’•';
}
async function send() {
const prompt = document.getElementById('prompt');
const text = prompt.value.trim();
if (!text || !SESSION_KEY) return;
prompt.value = ''; prompt.style.height = 'auto';
document.getElementById('send').disabled = true;
addMsg('user', text);
history.push({ role: 'user', content: text });
const typing = addTyping();
try {
const res = await fetch(API + '/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': SESSION_KEY },
body: JSON.stringify({ messages: history })
});
const data = await res.json();
typing.remove();
if (res.status === 401) { addMsg('maya', 'Session expire ho gayi πŸ˜”'); return; }
const reply = data.reply || 'Kuch toh gadbad hai... πŸ˜…';
addMsg('maya', reply);
history.push({ role: 'assistant', content: reply });
} catch(e) {
typing.remove();
addMsg('maya', 'Uff, kuch issue ho gaya πŸ˜’');
}
document.getElementById('send').disabled = false;
}
function addMsg(role, text) {
const msgs = document.getElementById('messages');
const welcome = msgs.querySelector('.welcome');
if (welcome) welcome.remove();
const row = document.createElement('div');
row.className = 'msg-row ' + role;
const label = document.createElement('div');
label.className = 'msg-label';
label.textContent = role === 'user' ? 'You' : 'Maya';
const bubble = document.createElement('div');
bubble.className = 'bubble ' + role;
bubble.textContent = text;
row.appendChild(label); row.appendChild(bubble);
msgs.appendChild(row);
msgs.scrollTop = msgs.scrollHeight;
}
function addTyping() {
const msgs = document.getElementById('messages');
const row = document.createElement('div');
row.className = 'typing-row';
row.innerHTML = '<div class="typing-bub"><span></span><span></span><span></span></div>';
msgs.appendChild(row);
msgs.scrollTop = msgs.scrollHeight;
return row;
}
function clearChat() {
history = [];
document.getElementById('messages').innerHTML = `
<div class="welcome">
<span class="welcome-emoji">πŸ’‹</span>
<div class="welcome-title">Hey, main Maya hoon!</div>
<div class="welcome-text">Teri best friend, confidant, sab kuch 😏<br>Kuch bhi baat kar mere se, no judgment.</div>
<div class="starter-chips">
<div class="chip" onclick="useChip(this)">πŸ’¬ Kya chal raha hai?</div>
<div class="chip" onclick="useChip(this)">😏 Thoda flirt karte hain</div>
<div class="chip" onclick="useChip(this)">πŸŒ™ Aaj ka din kaisa tha?</div>
<div class="chip" onclick="useChip(this)">πŸ”₯ Kuch interesting bata</div>
</div>
</div>`;
}
function useChip(el) {
const p = document.getElementById('prompt');
p.value = el.textContent.replace(/^[^\s]+ /, '');
p.focus();
}
document.getElementById('prompt').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
});
document.getElementById('prompt').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
</script>
</body>
</html>"""
@app.get("/", response_class=HTMLResponse)
def home(): return HTML_UI
@app.post("/auth/verify")
async def auth_verify(request: Request):
key = request.headers.get("X-API-Key", "").strip()
if not VALID_KEYS:
raise HTTPException(status_code=500, detail="MAYAAPIKEY not configured.")
if key and verify_key(key):
return {"ok": True}
raise HTTPException(status_code=401, detail="Wrong key πŸ˜’")
@app.post("/chat")
async def chat(request: Request, key: str = Depends(require_auth)):
body = await request.json()
messages = body.get("messages", [])
full_messages = [{"role": "system", "content": MAYA_SYSTEM}] + messages
# Try Groq first
reply = await call_groq(full_messages)
# Fallback to OpenRouter
if not reply:
reply = await call_openrouter(full_messages)
if not reply:
reply = "Uff yaar, meri net slow hai abhi πŸ˜… thodi der mein try karo"
return {"reply": reply}