Ghost-ai / app.py
YANAbillionarie's picture
Add app.py
66a9af1 verified
Raw
History Blame Contribute Delete
43.4 kB
"""
╔═══════════════════════════════════════════════════════════════╗
║ GHOST — Private Intelligence Layer ║
║ Build Small Hackathon 2026 · HuggingFace · Backyard AI ║
║ Model : microsoft/Phi-3-mini-4k-instruct (3.8 B) ║
╚═══════════════════════════════════════════════════════════════╝
"""
import gradio as gr
from huggingface_hub import InferenceClient
import datetime, os, time
# ─────────────────────────────────────────────────────────────
# MODEL
# ─────────────────────────────────────────────────────────────
MODEL_ID = "microsoft/Phi-3-mini-4k-instruct"
CLIENT = InferenceClient(MODEL_ID, token=os.environ.get("HF_TOKEN"))
def ts():
return datetime.datetime.now().strftime("%H:%M:%S")
def logline(icon, msg):
return f"[{ts()}] {icon} {msg}"
def call_llm(system, user, temperature=0.35, max_tokens=700):
resp = CLIENT.chat_completion(
messages=[{"role":"system","content":system},
{"role":"user", "content":user}],
max_tokens=max_tokens, temperature=temperature,
)
return resp.choices[0].message.content.strip()
def stream_text(text, chunk=6):
out = ""
for i in range(0, len(text), chunk):
out += text[i:i+chunk]
yield out
# ─────────────────────────────────────────────────────────────
# HEADER HTML — cinematic ghost + starfield + emotion system
# ─────────────────────────────────────────────────────────────
HEADER = r"""
<div id="gh-root">
<canvas id="gh-bg"></canvas>
<div id="gh-wrap">
<!-- ═══ GHOST STAGE ═══ -->
<div id="gh-stage" onclick="petGhost()" title="Pet me!">
<div class="ring r1"><span class="rdot"></span></div>
<div class="ring r2"></div>
<div class="ring r3"><span class="rdot rd2"></span></div>
<div id="gh-glow"></div>
<!-- accessories layer (above body, below everything else) -->
<div id="gh-acc">
<div class="acc-heart h1">💜</div>
<div class="acc-heart h2">💙</div>
<div class="acc-heart h3">🩷</div>
<div class="acc-star s1">✦</div>
<div class="acc-star s2">✧</div>
<div class="acc-star s3">✦</div>
<div class="acc-sweat sw1">💧</div>
</div>
<svg id="gh-svg" viewBox="0 0 120 148" fill="none"
xmlns="http://www.w3.org/2000/svg">
<!-- shadow -->
<ellipse cx="60" cy="138" rx="26" ry="7" fill="#a78bfa18"/>
<!-- body -->
<path id="gh-body"
d="M10 72 C10 36 32 10 60 10 C88 10 110 36 110 72
L110 116 C110 116 97 106 86 116 C75 126 73 108 60 108
C47 108 45 126 34 116 C23 106 10 116 10 116 Z"
fill="url(#bodyG)" stroke="var(--gc)44" stroke-width="1.2"/>
<!-- inner body shimmer -->
<path
d="M24 52 C24 36 40 20 60 18 C80 20 96 36 96 52
L93 82 C78 76 42 76 27 82 Z"
fill="url(#shineG)" opacity="0.28"/>
<!-- LEFT EYEBROW -->
<path id="gh-lbrow"
d="M26 50 Q36 45 46 50"
stroke="var(--gc)88" stroke-width="2.5"
stroke-linecap="round" fill="none"/>
<!-- RIGHT EYEBROW -->
<path id="gh-rbrow"
d="M64 50 Q74 45 84 50"
stroke="var(--gc)88" stroke-width="2.5"
stroke-linecap="round" fill="none"/>
<!-- Eyes group (idle blink) -->
<g class="gh-blink">
<!-- LEFT EYE base -->
<ellipse id="gh-leye" cx="36" cy="66" rx="9" ry="11" fill="#06061a"/>
<!-- RIGHT EYE base -->
<ellipse id="gh-reye" cx="74" cy="66" rx="9" ry="11" fill="#06061a"/>
<!-- iris glow (wanders) -->
<ellipse id="gh-liris" class="gh-iris" cx="36" cy="66" rx="5.5" ry="7"
fill="var(--gc)" opacity="0.55"/>
<ellipse id="gh-riris" class="gh-iris" cx="74" cy="66" rx="5.5" ry="7"
fill="var(--gc)" opacity="0.55"/>
<!-- eye shine (large) -->
<circle cx="40" cy="60" r="3.2" fill="rgba(255,255,255,.96)"/>
<circle cx="78" cy="60" r="3.2" fill="rgba(255,255,255,.96)"/>
<!-- eye shine (small) -->
<circle cx="33" cy="65" r="1.5" fill="rgba(255,255,255,.6)"/>
<circle cx="71" cy="65" r="1.5" fill="rgba(255,255,255,.6)"/>
</g>
<!-- HEART eyes overlay (hidden by default) -->
<g id="gh-hearts-eyes" opacity="0">
<path d="M28 60 C28 56 32 53 36 57 C40 53 44 56 44 60
C44 65 36 72 36 72 C36 72 28 65 28 60 Z"
fill="#f472b6"/>
<path d="M66 60 C66 56 70 53 74 57 C78 53 82 56 82 60
C82 65 74 72 74 72 C74 72 66 65 66 60 Z"
fill="#f472b6"/>
</g>
<!-- STAR eyes overlay (hidden by default) -->
<g id="gh-stars-eyes" opacity="0">
<text x="27" y="74" font-size="20" fill="#fbbf24">★</text>
<text x="65" y="74" font-size="20" fill="#fbbf24">★</text>
</g>
<!-- SPIRAL eyes overlay (thinking — hidden by default) -->
<g id="gh-spiral-eyes" opacity="0">
<text x="26" y="74" font-size="22" fill="var(--gc)">◎</text>
<text x="64" y="74" font-size="22" fill="var(--gc)">◎</text>
</g>
<!-- MOUTH -->
<path id="gh-mouth"
d="M44 84 Q60 93 76 84"
stroke="var(--gc)CC" stroke-width="2.8"
stroke-linecap="round" fill="none"/>
<!-- TONGUE (big smile only) -->
<ellipse id="gh-tongue" cx="60" cy="92" rx="8" ry="5"
fill="#f472b6" opacity="0"/>
<!-- LEFT BLUSH -->
<ellipse id="gh-lblush" cx="18" cy="80" rx="11" ry="6"
fill="rgba(244,114,182,.25)"
style="animation:blushPulse 2s ease-in-out infinite"/>
<!-- RIGHT BLUSH -->
<ellipse id="gh-rblush" cx="92" cy="80" rx="11" ry="6"
fill="rgba(244,114,182,.25)"
style="animation:blushPulse 2s ease-in-out infinite .4s"/>
<!-- SWEAT DROP (thinking — hidden) -->
<g id="gh-sweatdrop" opacity="0">
<path d="M96 30 C96 30 100 22 104 30 C104 35 100 38 96 30 Z"
fill="#93c5fd"/>
</g>
<defs>
<linearGradient id="bodyG" x1="10" y1="10" x2="110" y2="130"
gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f5f3ff"/>
<stop offset="100%" stop-color="#ddd6fe"/>
</linearGradient>
<radialGradient id="shineG" cx="50%" cy="18%" r="65%">
<stop offset="0%" stop-color="white" stop-opacity=".9"/>
<stop offset="100%" stop-color="white" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>
<!-- Particle canvas -->
<canvas id="gh-particles"></canvas>
<!-- Speech bubble -->
<div id="gh-bubble"></div>
</div>
<!-- ═══ COPY ═══ -->
<div id="gh-copy">
<div id="gh-badge">
<span class="bdot"></span>
<span id="gh-status">GHOST · 3.8B · PRIVATE</span>
</div>
<h1 id="gh-title">
<span class="gl" style="--i:0">G</span>
<span class="gl" style="--i:1">H</span>
<span class="gl" style="--i:2">O</span>
<span class="gl" style="--i:3">S</span>
<span class="gl" style="--i:4">T</span>
</h1>
<p id="gh-sub">
Your private intelligence layer.<br>
No cloud. No logs. Just&nbsp;you.
</p>
<div id="gh-chips">
<span class="chip chip-p" onclick="setTab('chief')">📋 Chief of Staff</span>
<span class="chip chip-b" onclick="setTab('letter')">💌 Unsent Letter</span>
<span class="chip chip-g" onclick="setTab('reality')">🔍 Reality Check</span>
</div>
<div id="gh-model">✦ Phi-3-mini · 3.8B parameters · Tiny Titan eligible</div>
</div>
</div>
</div>
<style>
/* Accessories */
.acc-heart,.acc-star,.acc-sweat{
position:absolute;font-size:16px;opacity:0;
pointer-events:none;transition:opacity .4s;
}
.h1{top:5%; left:50%; animation:floatAcc 2.2s ease-in-out infinite .0s;}
.h2{top:15%;left:10%; animation:floatAcc 2.6s ease-in-out infinite .3s;}
.h3{top:10%;right:10%; animation:floatAcc 2s ease-in-out infinite .6s;}
.s1{top:5%; left:40%; animation:floatAcc 2.4s ease-in-out infinite .1s;}
.s2{top:0%; left:60%; animation:floatAcc 2.8s ease-in-out infinite .5s;}
.s3{top:8%; right:20%; animation:floatAcc 2.2s ease-in-out infinite .8s;}
.sw1{top:2%;right:5%; animation:floatAcc 1.8s ease-in-out infinite 0s;}
@keyframes floatAcc{
0%,100%{transform:translateY(0) scale(1);}
50%{transform:translateY(-12px) scale(1.1);}
}
@keyframes blushPulse{
0%,100%{opacity:.25;transform:scale(1);}
50%{opacity:.45;transform:scale(1.15);}
}
/* ── Idle blink ── */
.gh-blink{
transform-box: fill-box;
transform-origin: center;
animation: ghBlink 4.6s ease-in-out infinite;
}
@keyframes ghBlink{
0%,94%,100%{ transform:scaleY(1); }
96%{ transform:scaleY(.08); }
98%{ transform:scaleY(1); }
}
/* ── Eyes wander like they're curious ── */
.gh-iris{ animation: irisWander 6s ease-in-out infinite; }
@keyframes irisWander{
0%,100%{ transform:translate(0,0); }
20%{ transform:translate(1.6px,-1px); }
40%{ transform:translate(-1.6px,.6px); }
60%{ transform:translate(1px,1.2px); }
80%{ transform:translate(-1px,-.6px); }
}
/* ── Speech bubble ── */
#gh-bubble{
position:absolute;
top:-4px; left:58%;
min-width:118px; max-width:200px;
background:rgba(10,10,26,.94);
border:1px solid color-mix(in srgb,var(--gc) 45%,transparent);
border-radius:14px;
padding:8px 13px;
font-family:'Space Mono',monospace;
font-size:11px; color:var(--gc);
text-align:center; line-height:1.4;
opacity:0; transform:translateY(8px) scale(.9);
transition:opacity .3s ease, transform .3s ease, color .7s, border-color .7s;
pointer-events:none; z-index:8;
backdrop-filter:blur(8px);
}
#gh-bubble::after{
content:''; position:absolute;
bottom:-6px; left:22px;
width:12px; height:12px;
background:rgba(10,10,26,.94);
border-right:1px solid color-mix(in srgb,var(--gc) 45%,transparent);
border-bottom:1px solid color-mix(in srgb,var(--gc) 45%,transparent);
transform:rotate(45deg);
transition:border-color .7s;
}
#gh-bubble.show{ opacity:1; transform:translateY(0) scale(1); }
/* ── Pet bounce on click ── */
#gh-stage{ cursor:pointer; }
.gh-pet-bounce{ animation: petBounce .52s ease !important; }
@keyframes petBounce{
0% { transform:translateY(0) rotate(-1.8deg) scale(1,1); }
30% { transform:translateY(-24px) rotate(5deg) scale(1.1,.92); }
55% { transform:translateY(3px) rotate(-4deg) scale(.95,1.06); }
100%{ transform:translateY(0) rotate(-1.8deg) scale(1,1); }
}
</style>
<script>
/* ═══ GHOST EMOTION ENGINE ═══ */
const EMOTIONS = {
idle:{
eyeRx:9,eyeRy:11,irisRx:5.5,irisRy:7,
lbrow:'M26 50 Q36 45 46 50',rbrow:'M64 50 Q74 45 84 50',
mouth:'M44 84 Q60 93 76 84',tongue:0,
heartsEyes:0,starsEyes:0,spiralEyes:0,sweat:0,
acc:'none',gc:'#a78bfa',
status:'GHOST · 3.8B · PRIVATE',
},
chief:{
eyeRx:10,eyeRy:7,irisRx:6.5,irisRy:4.5,
lbrow:'M26 48 Q36 43 46 47',rbrow:'M64 47 Q74 43 84 48',
mouth:'M46 86 Q60 87 74 86',tongue:0,
heartsEyes:0,starsEyes:0,spiralEyes:0,sweat:0,
acc:'none',gc:'#60a5fa',
status:'CHIEF OF STAFF · ANALYZING',
},
letter:{
eyeRx:9,eyeRy:11,irisRx:5.5,irisRy:7,
lbrow:'M26 47 Q36 42 46 47',rbrow:'M64 47 Q74 42 84 47',
mouth:'M41 82 Q60 96 79 82',tongue:0.8,
heartsEyes:1,starsEyes:0,spiralEyes:0,sweat:0,
acc:'hearts',gc:'#f472b6',
status:'UNSENT LETTER · FEELING',
},
reality:{
eyeRx:11,eyeRy:13,irisRx:7,irisRy:9,
lbrow:'M26 50 Q36 47 46 52',rbrow:'M64 52 Q74 47 84 50',
mouth:'M44 90 Q60 83 76 90',tongue:0,
heartsEyes:0,starsEyes:0,spiralEyes:0,sweat:0,
acc:'none',gc:'#f59e0b',
status:'REALITY CHECK · SCANNING',
},
think:{
eyeRx:9,eyeRy:6,irisRx:5.5,irisRy:3.5,
lbrow:'M26 50 Q36 46 46 50',rbrow:'M64 50 Q74 46 84 50',
mouth:'M48 87 Q60 86 72 87',tongue:0,
heartsEyes:0,starsEyes:0,spiralEyes:1,sweat:1,
acc:'none',gc:'#818cf8',
status:'GHOST IS THINKING...',
},
done:{
eyeRx:9,eyeRy:11,irisRx:5.5,irisRy:7,
lbrow:'M26 46 Q36 41 46 46',rbrow:'M64 46 Q74 41 84 46',
mouth:'M38 82 Q60 98 82 82',tongue:0.9,
heartsEyes:0,starsEyes:1,spiralEyes:0,sweat:0,
acc:'stars',gc:'#34d399',
status:'DONE · REVIEW ABOVE ✦',
},
};
function lerp(a,b,t){ return a+(b-a)*t; }
let currentState = 'idle';
function applyEmotion(key,fast){
if(!EMOTIONS[key]) key='idle';
currentState = key;
const e = EMOTIONS[key];
const r = document.documentElement;
/* ghost color cascade */
r.style.setProperty('--gc', e.gc);
/* SVG elements */
const set=(id,attr,val)=>{
const el=document.getElementById(id);
if(el) el.setAttribute(attr,val);
};
const setO=(id,val)=>{
const el=document.getElementById(id);
if(el){ el.style.transition='opacity .4s'; el.style.opacity=val; }
};
set('gh-leye','rx',e.eyeRx); set('gh-leye','ry',e.eyeRy);
set('gh-reye','rx',e.eyeRx); set('gh-reye','ry',e.eyeRy);
set('gh-liris','rx',e.irisRx); set('gh-liris','ry',e.irisRy);
set('gh-riris','rx',e.irisRx); set('gh-riris','ry',e.irisRy);
set('gh-lbrow','d',e.lbrow);
set('gh-rbrow','d',e.rbrow);
set('gh-mouth','d',e.mouth);
setO('gh-tongue', e.tongue);
setO('gh-hearts-eyes', e.heartsEyes);
setO('gh-stars-eyes', e.starsEyes);
setO('gh-spiral-eyes', e.spiralEyes);
setO('gh-sweatdrop', e.sweat);
/* accessories */
const hearts = document.querySelectorAll('.acc-heart');
const stars = document.querySelectorAll('.acc-star');
const sweat = document.querySelectorAll('.acc-sweat');
hearts.forEach(h=>h.style.opacity = e.acc==='hearts'?'1':'0');
stars.forEach(s=>s.style.opacity = e.acc==='stars' ?'1':'0');
sweat.forEach(s=>s.style.opacity = e.sweat ?'1':'0');
/* badge dot + status */
document.querySelectorAll('.bdot').forEach(d=>{
d.style.background=e.gc;
d.style.boxShadow=`0 0 8px ${e.gc}`;
});
const st=document.getElementById('gh-status');
if(st) st.textContent=e.status;
}
function setTab(mode){ applyEmotion(mode); }
/* ═══ PET PERSONALITY ═══ */
const IDLE_LINES = [
"👋 hi, I'm Ghost",
"tap me anytime",
"I keep your secrets 🤫",
"ready when you are ✨",
"your words stay yours 💜",
"*floats happily*",
"pick a mode above 👆",
"I don't judge, I just help",
"私はあなたの秘密を守ります",
"no cloud, just us 🌙",
];
function showBubble(text, ms){
const b=document.getElementById('gh-bubble');
if(!b) return;
b.textContent=text;
b.classList.add('show');
clearTimeout(b.__t);
b.__t=setTimeout(()=>b.classList.remove('show'), ms||2200);
}
function petGhost(){
const svg=document.getElementById('gh-svg');
if(svg){
svg.classList.remove('gh-pet-bounce');
void svg.offsetWidth; /* restart animation */
svg.classList.add('gh-pet-bounce');
setTimeout(()=>svg.classList.remove('gh-pet-bounce'),520);
}
showBubble(IDLE_LINES[Math.floor(Math.random()*IDLE_LINES.length)], 1900);
if(window.ghostBurst) ghostBurst();
if(currentState==='idle'){
applyEmotion('done');
setTimeout(()=>{ if(currentState==='done') applyEmotion('idle'); },1700);
}
}
/* gentle idle chatter so Ghost feels alive */
setInterval(()=>{
if(currentState==='idle'){
showBubble(IDLE_LINES[Math.floor(Math.random()*IDLE_LINES.length)],2400);
}
},10000);
setTimeout(()=>showBubble("👋 hi, I'm Ghost — tap me!",2800),1200);
/* Watch Gradio buttons */
function watchBtns(){
document.querySelectorAll('button').forEach(btn=>{
if(btn.__ghWatched) return;
btn.__ghWatched=true;
const t=btn.textContent.trim().toLowerCase();
if(t.includes('analyze')){
btn.addEventListener('click',()=>{
applyEmotion('think');
setTimeout(()=>applyEmotion('chief'),900);
setTimeout(()=>{ applyEmotion('done'); ghostBurst(); }, 4000);
});
} else if(t.includes('write')){
btn.addEventListener('click',()=>{
applyEmotion('think');
setTimeout(()=>applyEmotion('letter'),900);
setTimeout(()=>{ applyEmotion('done'); ghostBurst(); }, 4000);
});
} else if(t.includes('reality')){
btn.addEventListener('click',()=>{
applyEmotion('think');
setTimeout(()=>applyEmotion('reality'),900);
setTimeout(()=>{ applyEmotion('done'); ghostBurst(); }, 4000);
});
}
});
}
setInterval(watchBtns,1500);
/* ═══ STARFIELD ═══ */
(function(){
const cv=document.getElementById('gh-bg');
if(!cv) return;
const ctx=cv.getContext('2d');
function resize(){cv.width=cv.offsetWidth;cv.height=cv.offsetHeight;}
resize();
window.addEventListener('resize',resize);
const stars=Array.from({length:240},()=>({
x:Math.random(),y:Math.random(),
r:Math.random()*1.6+.2,
base:Math.random()*.9+.1,
spd:Math.random()*.008+.001,
t:Math.random()*Math.PI*2,
c:Math.random()<.12?'#c4b5fd':Math.random()<.08?'#93c5fd':'#fff',
}));
const nebs=[
{x:.12,y:.22,r:180,c:'rgba(109,40,217,.13)'},
{x:.88,y:.7, r:160,c:'rgba(30,58,138,.1)'},
{x:.5, y:.5, r:110,c:'rgba(157,23,77,.07)'},
{x:.7, y:.2, r:90, c:'rgba(6,78,59,.06)'},
];
const shoots=[];
function shoot(){
shoots.push({x:Math.random()*.8,y:Math.random()*.6,
len:60+Math.random()*100,spd:7+Math.random()*6,
life:1,a:Math.PI*.25});
if(Math.random()<.3) shoots.push({x:Math.random()*.8,y:Math.random()*.5,
len:40+Math.random()*80,spd:5+Math.random()*5,life:1,a:Math.PI*.2});
}
setInterval(shoot,2600);
function draw(){
const W=cv.width,H=cv.height;
ctx.clearRect(0,0,W,H);
nebs.forEach(n=>{
const g=ctx.createRadialGradient(n.x*W,n.y*H,0,n.x*W,n.y*H,n.r);
g.addColorStop(0,n.c);g.addColorStop(1,'transparent');
ctx.fillStyle=g;ctx.beginPath();
ctx.arc(n.x*W,n.y*H,n.r,0,Math.PI*2);ctx.fill();
});
stars.forEach(s=>{
s.t+=s.spd;
ctx.globalAlpha=((Math.sin(s.t)+1)/2)*s.base;
ctx.fillStyle=s.c;ctx.beginPath();
ctx.arc(s.x*W,s.y*H,s.r,0,Math.PI*2);ctx.fill();
});
ctx.globalAlpha=1;
for(let i=shoots.length-1;i>=0;i--){
const s=shoots[i];
const g=ctx.createLinearGradient(s.x*W,s.y*H,
s.x*W-Math.cos(s.a)*s.len,s.y*H-Math.sin(s.a)*s.len);
g.addColorStop(0,`rgba(196,181,253,${s.life})`);
g.addColorStop(.5,`rgba(167,139,250,${s.life*.4})`);
g.addColorStop(1,'transparent');
ctx.beginPath();
ctx.moveTo(s.x*W,s.y*H);
ctx.lineTo(s.x*W-Math.cos(s.a)*s.len,s.y*H-Math.sin(s.a)*s.len);
ctx.strokeStyle=g;ctx.lineWidth=1.8;ctx.stroke();
s.x+=Math.cos(s.a)*s.spd/W*2.2;
s.y+=Math.sin(s.a)*s.spd/H*2.2;
s.life-=.018;
if(s.life<=0) shoots.splice(i,1);
}
requestAnimationFrame(draw);
}
draw();
})();
/* ═══ PARTICLE BURST ═══ */
(function(){
const cv=document.getElementById('gh-particles');
if(!cv) return;
const ctx=cv.getContext('2d');
cv.width=340;cv.height=340;
const parts=[];
window.ghostBurst=function(){
const gc=getComputedStyle(document.documentElement)
.getPropertyValue('--gc').trim()||'#34d399';
const colors=[gc,'#fff','#f472b6','#fbbf24','#60a5fa'];
for(let i=0;i<40;i++){
const a=Math.random()*Math.PI*2;
const sp=3+Math.random()*7;
parts.push({
x:170,y:170,
vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,
r:1.5+Math.random()*4,
life:1,
color:colors[Math.floor(Math.random()*colors.length)],
shape:Math.random()<.3?'star':'circle',
});
}
};
function draw(){
ctx.clearRect(0,0,340,340);
for(let i=parts.length-1;i>=0;i--){
const p=parts[i];
ctx.globalAlpha=p.life;
ctx.fillStyle=p.color;
ctx.beginPath();
ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
ctx.fill();
p.x+=p.vx;p.y+=p.vy;
p.vy+=.15;p.vx*=.98;
p.life-=.022;
if(p.life<=0) parts.splice(i,1);
}
ctx.globalAlpha=1;
requestAnimationFrame(draw);
}
draw();
})();
</script>
"""
# ─────────────────────────────────────────────────────────────
# CSS
# ─────────────────────────────────────────────────────────────
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@300;400;600;700;800&family=Space+Mono:wght@400;700&display=swap');
:root{
--gc :#a78bfa;
--bg :#000008;
--glass :rgba(255,255,255,.025);
--border :rgba(139,92,246,.18);
--text :#f1f5f9;
--muted :#374151;
--mono :'Space Mono',monospace;
--sans :'Syne',sans-serif;
--r :16px;
--ease :cubic-bezier(.16,1,.3,1);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
::-webkit-scrollbar{width:3px;}
::-webkit-scrollbar-track{background:transparent;}
::-webkit-scrollbar-thumb{background:rgba(139,92,246,.4);border-radius:99px;}
body,.gradio-container{
background:var(--bg) !important;
font-family:var(--sans) !important;
color:var(--text) !important;
}
.gradio-container{max-width:1060px !important;margin:0 auto !important;}
.gradio-container>div,.gr-block{background:transparent !important;}
/* ════ HEADER ════ */
#gh-root{
position:relative;overflow:hidden;
background:linear-gradient(168deg,#0e0e26 0%,#070714 55%,var(--bg) 100%);
border-bottom:1px solid rgba(139,92,246,.14);
min-height:210px;
}
#gh-bg{
position:absolute;inset:0;
width:100%;height:100%;
pointer-events:none;
}
#gh-wrap{
position:relative;z-index:3;
display:flex;align-items:center;gap:44px;
padding:46px 54px 38px;flex-wrap:wrap;
}
/* Stage */
#gh-stage{
position:relative;flex-shrink:0;
width:170px;height:178px;
display:flex;align-items:center;justify-content:center;
}
#gh-svg{
width:110px;height:132px;
position:relative;z-index:5;
filter:
drop-shadow(0 0 36px color-mix(in srgb,var(--gc) 60%,transparent))
drop-shadow(0 0 12px color-mix(in srgb,var(--gc) 30%,transparent));
animation:ghFloat 3.4s ease-in-out infinite;
transition:filter .7s ease;
}
#gh-glow{
position:absolute;top:50%;left:50%;
transform:translate(-50%,-50%);
width:128px;height:128px;border-radius:50%;
background:radial-gradient(circle,
color-mix(in srgb,var(--gc) 22%,transparent),transparent 72%);
animation:haloPulse 2.8s ease-in-out infinite;
transition:background .7s ease;z-index:2;
}
/* Rings */
.ring{
position:absolute;top:50%;left:50%;
border-radius:50%;
border:1px solid rgba(167,139,250,.1);
}
.r1{width:132px;height:132px;animation:spinCW 12s linear infinite;}
.r2{width:166px;height:166px;border-style:dashed;
border-color:rgba(167,139,250,.06);animation:spinCCW 18s linear infinite;}
.r3{width:206px;height:206px;border-color:rgba(96,165,250,.04);
animation:spinCW 25s linear infinite;}
.rdot{
position:absolute;width:7px;height:7px;border-radius:50%;
top:-3px;left:50%;transform:translateX(-50%);
background:var(--gc);box-shadow:0 0 10px var(--gc);
transition:background .7s,box-shadow .7s;
}
.rd2{top:auto;bottom:-3px;background:#60a5fa;box-shadow:0 0 10px #60a5fa;}
#gh-acc{position:absolute;inset:0;z-index:6;pointer-events:none;}
#gh-particles{
position:absolute;top:50%;left:50%;
transform:translate(-50%,-50%);
width:340px;height:340px;
pointer-events:none;z-index:7;
}
/* Copy */
#gh-copy{flex:1;min-width:240px;}
#gh-badge{
display:inline-flex;align-items:center;gap:8px;
font-family:var(--mono);font-size:10px;
color:var(--gc);letter-spacing:.1em;
background:color-mix(in srgb,var(--gc) 10%,transparent);
border:1px solid color-mix(in srgb,var(--gc) 32%,transparent);
border-radius:100px;padding:5px 15px;margin-bottom:16px;
transition:all .7s ease;
}
.bdot{
width:7px;height:7px;border-radius:50%;
background:var(--gc);box-shadow:0 0 8px var(--gc);
animation:blink 1.3s ease infinite;
transition:background .7s,box-shadow .7s;
}
#gh-title{
font-size:70px;font-weight:800;
letter-spacing:-.05em;line-height:1;
margin-bottom:12px;display:flex;
}
.gl{
display:inline-block;
background:linear-gradient(135deg,#fff 0%,#c4b5fd 40%,#818cf8 66%,#60a5fa 100%);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
animation:letterWave 5s ease-in-out infinite;
animation-delay:calc(var(--i) * .14s);
}
#gh-sub{font-size:14px;color:#4b5563;line-height:1.7;margin-bottom:20px;}
#gh-chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;}
.chip{
font-size:11px;font-family:var(--mono);letter-spacing:.04em;
padding:6px 15px;border-radius:100px;border:1px solid;
cursor:pointer;transition:all .22s var(--ease);user-select:none;
}
.chip:hover{transform:translateY(-3px);}
.chip-p{color:#a78bfa;background:rgba(167,139,250,.08);border-color:rgba(167,139,250,.26);}
.chip-p:hover{background:rgba(167,139,250,.18);box-shadow:0 4px 16px rgba(167,139,250,.2);}
.chip-b{color:#60a5fa;background:rgba(96,165,250,.08);border-color:rgba(96,165,250,.26);}
.chip-b:hover{background:rgba(96,165,250,.18);box-shadow:0 4px 16px rgba(96,165,250,.2);}
.chip-g{color:#34d399;background:rgba(52,211,153,.08);border-color:rgba(52,211,153,.26);}
.chip-g:hover{background:rgba(52,211,153,.18);box-shadow:0 4px 16px rgba(52,211,153,.2);}
#gh-model{font-family:var(--mono);font-size:10px;color:#1f2937;letter-spacing:.08em;}
/* ════ SECTION LABEL ════ */
.slabel{
font-family:var(--mono);font-size:9px;
color:#1f2937;letter-spacing:.18em;text-transform:uppercase;
padding:22px 26px 8px;
}
/* ════ INPUTS ════ */
textarea,input[type=text]{
background:var(--glass) !important;
border:1px solid var(--border) !important;
border-radius:var(--r) !important;
color:var(--text) !important;
font-family:var(--sans) !important;
font-size:14px !important;line-height:1.78 !important;
padding:18px 20px !important;
transition:border-color .25s,box-shadow .25s !important;
resize:none !important;
backdrop-filter:blur(14px) !important;
-webkit-backdrop-filter:blur(14px) !important;
}
textarea:focus,input[type=text]:focus{
border-color:color-mix(in srgb,var(--gc) 75%,transparent) !important;
box-shadow:0 0 0 3px color-mix(in srgb,var(--gc) 10%,transparent),
0 0 30px color-mix(in srgb,var(--gc) 7%,transparent) !important;
outline:none !important;
}
/* ════ LABELS ════ */
label>span:first-child{
color:#374151 !important;font-size:10px !important;
font-family:var(--mono) !important;
letter-spacing:.14em !important;text-transform:uppercase !important;
}
/* ════ BUTTON ════ */
button.lg{
position:relative !important;overflow:hidden !important;
background:linear-gradient(135deg,
color-mix(in srgb,var(--gc) 90%,#000) 0%,
color-mix(in srgb,var(--gc) 60%,#4f46e5) 100%) !important;
border:none !important;border-radius:14px !important;
color:#fff !important;font-family:var(--sans) !important;
font-size:15px !important;font-weight:700 !important;
padding:18px 26px !important;cursor:pointer !important;
letter-spacing:.02em !important;width:100% !important;
box-shadow:0 4px 30px color-mix(in srgb,var(--gc) 45%,transparent),
0 0 0 1px rgba(255,255,255,.07) inset !important;
transition:all .28s var(--ease) !important;
}
button.lg::before{
content:'';position:absolute;
top:0;left:-120%;width:60%;height:100%;
background:linear-gradient(90deg,transparent,rgba(255,255,255,.14),transparent);
animation:shimmer 3.2s ease infinite;
}
button.lg:hover{
transform:translateY(-3px) !important;
box-shadow:0 12px 48px color-mix(in srgb,var(--gc) 65%,transparent),
0 0 0 1px rgba(255,255,255,.12) inset !important;
}
button.lg:active{transform:translateY(-1px) !important;}
/* ════ OUTPUT BOX ════ */
.out-box textarea{
background:color-mix(in srgb,var(--gc) 3%,transparent) !important;
border-color:color-mix(in srgb,var(--gc) 25%,transparent) !important;
line-height:1.9 !important;
transition:border-color .7s,background .7s !important;
}
/* ════ LOG BOX ════ */
.log-box textarea{
background:rgba(0,0,0,.72) !important;
border-color:rgba(52,211,153,.16) !important;
color:#34d399 !important;
font-family:var(--mono) !important;
font-size:11px !important;letter-spacing:.04em !important;
line-height:2 !important;
text-shadow:0 0 12px rgba(52,211,153,.35) !important;
}
/* ════ RADIO ════ */
.tone-wrap fieldset{border:none !important;padding:0 !important;}
.tone-wrap label{
background:var(--glass) !important;
border:1px solid var(--border) !important;
border-radius:10px !important;color:#6b7280 !important;
font-size:11px !important;font-family:var(--mono) !important;
padding:8px 16px !important;cursor:pointer !important;
transition:all .22s var(--ease) !important;
margin:0 4px 4px 0 !important;
display:inline-flex !important;align-items:center !important;
backdrop-filter:blur(8px) !important;
}
.tone-wrap label:hover{
border-color:color-mix(in srgb,var(--gc) 50%,transparent) !important;
color:var(--gc) !important;transform:translateY(-2px) !important;
}
.tone-wrap label:has(input:checked){
background:color-mix(in srgb,var(--gc) 14%,transparent) !important;
border-color:color-mix(in srgb,var(--gc) 55%,transparent) !important;
color:var(--gc) !important;
box-shadow:0 0 18px color-mix(in srgb,var(--gc) 18%,transparent) !important;
}
/* ════ TABS ════ */
.tabs>.tab-nav{
background:rgba(0,0,0,.38) !important;
border-bottom:1px solid rgba(139,92,246,.1) !important;
padding:0 26px !important;
backdrop-filter:blur(10px) !important;
}
.tab-nav button{
color:#374151 !important;font-family:var(--mono) !important;
font-size:11px !important;letter-spacing:.1em !important;
text-transform:uppercase !important;padding:14px 22px !important;
background:transparent !important;border:none !important;
border-bottom:2px solid transparent !important;
transition:all .22s var(--ease) !important;cursor:pointer !important;
}
.tab-nav button:hover{color:#6b7280 !important;}
.tab-nav button.selected{
color:var(--gc) !important;
border-bottom-color:var(--gc) !important;
text-shadow:0 0 22px color-mix(in srgb,var(--gc) 70%,transparent) !important;
transition:color .7s,border-bottom-color .7s,text-shadow .7s !important;
}
/* ════ FOOTER ════ */
#gh-footer{
display:flex;align-items:center;justify-content:center;
gap:10px;padding:20px 40px;flex-wrap:wrap;
background:rgba(0,0,0,.3);
border-top:1px solid rgba(139,92,246,.07);
backdrop-filter:blur(10px);
}
/* ════ ANIMATIONS ════ */
@keyframes ghFloat{
0%,100%{transform:translateY(0) rotate(-1.8deg) scale(1);}
33% {transform:translateY(-14px) rotate(2deg) scale(1.02);}
66% {transform:translateY(-6px) rotate(-1deg) scale(1.008);}
}
@keyframes haloPulse{
0%,100%{opacity:.38;transform:translate(-50%,-50%) scale(1);}
50% {opacity:.92;transform:translate(-50%,-50%) scale(1.26);}
}
@keyframes spinCW{
from{transform:translate(-50%,-50%) rotate(0deg);}
to{transform:translate(-50%,-50%) rotate(360deg);}
}
@keyframes spinCCW{
from{transform:translate(-50%,-50%) rotate(0deg);}
to{transform:translate(-50%,-50%) rotate(-360deg);}
}
@keyframes blink{0%,100%{opacity:1;}50%{opacity:.12;}}
@keyframes letterWave{
0%,100%{transform:translateY(0);}
50%{transform:translateY(-4px);}
}
@keyframes shimmer{
0%{left:-120%;}60%{left:160%;}100%{left:160%;}
}
@keyframes blushPulse{
0%,100%{opacity:.25;transform:scale(1);}
50%{opacity:.5;transform:scale(1.18);}
}
"""
# ─────────────────────────────────────────────────────────────
# PROMPTS
# ─────────────────────────────────────────────────────────────
SYS_CHIEF = "You are GHOST, a razor-sharp private executive assistant. Extract only what matters. Be direct and specific."
SYS_LETTER = "You are GHOST. Help people say what they have always meant to say. Write in their voice — completely human, never robotic."
SYS_REAL = "You are GHOST, a sharp-eyed private advisor. Find what others miss. Be direct and specific."
TONE_MAP = {
"Warm & Loving": "warm, loving, deeply heartfelt",
"Professional": "professional, respectful, clear",
"Honest & Direct": "honest, direct, quietly courageous",
"Apologetic": "sincere, remorseful, genuinely healing",
}
def p_chief(text):
return f"""Analyze this. Respond EXACTLY in this format:
SUMMARY:
[2 sentences max. What it's really about. No fluff.]
DEADLINES:
[Every date/time/urgency. Quote them. If none: None found.]
COMMITMENTS:
[What's been promised by whom. If none: None found.]
NEXT ACTIONS:
1. [Most urgent]
2. [Second]
3. [Third]
Document:
---
{text.strip()}
---"""
def p_letter(rough, tone):
return f"""Turn this rough idea into a beautiful letter.
Tone: {tone}
Rules: Sound completely human. No clichés. Every sentence earns its place.
Write the letter directly — no preamble.
What they want to say:
---
{rough.strip()}
---
Write the letter:"""
def p_real(text):
return f"""Reality-check this. Be ruthlessly direct.
CONTRADICTIONS:
[Conflicting claims. Quote them. If none: None found.]
MISSING CONTEXT:
[Absent/assumed info. If none: None found.]
RISKY COMMITMENTS:
[Problematic promises. If none: None found.]
RED FLAGS:
[Vague language, manipulation, inconsistency. If none: None found.]
VERDICT:
[One sentence: trust it, revise it, or reject it — and why.]
Text:
---
{text.strip()}
---"""
# ─────────────────────────────────────────────────────────────
# STREAMING FUNCTIONS
# ─────────────────────────────────────────────────────────────
def chief_of_staff(text):
if not text.strip():
yield "Paste a document above to begin.", logline("⚠","No input"); return
yield "👻 Ghost is reading your document...", logline("👁","Reading...")
time.sleep(.35)
yield "🧠 Extracting what actually matters...", logline("🧠","Analyzing...")
try:
result = call_llm(SYS_CHIEF, p_chief(text), temperature=0.22, max_tokens=660)
out=""
for chunk in (result[i:i+7] for i in range(0,len(result),7)):
out+=chunk; yield out, logline("📝","Writing..."); time.sleep(.008)
yield result, "\n".join([
logline("👁","Document read"),
logline("🧠","Analysis complete"),
logline("📋","Deadlines & commitments extracted"),
logline("✅","Done — review and edit above"),
])
except Exception as e:
yield f"⚠ Error: {e}\n\nTip: wait 30s and retry.", logline("❌",str(e))
def unsent_letter(rough, tone):
if not rough.strip():
yield "Describe what you want to say above.", logline("⚠","No input"); return
tone_desc = TONE_MAP.get(tone,"warm and genuine")
yield "👻 Ghost is listening...", logline("👻","Listening...")
time.sleep(.35)
yield "💜 Understanding what you really mean...", logline("💜","Processing...")
try:
result = call_llm(SYS_LETTER, p_letter(rough,tone_desc), temperature=0.82, max_tokens=740)
out=""
for chunk in (result[i:i+6] for i in range(0,len(result),6)):
out+=chunk; yield out, logline("✍","Writing in your voice..."); time.sleep(.008)
yield result, "\n".join([
logline("👻","Ghost listened"),
logline("💜","Real meaning understood"),
logline("✍",f"Written — tone: {tone}"),
logline("✅","Done — read it, edit it, send it"),
])
except Exception as e:
yield f"⚠ Error: {e}", logline("❌",str(e))
def reality_check(text):
if not text.strip():
yield "Paste a message or document above.", logline("⚠","No input"); return
yield "👁 Ghost is reading very carefully...", logline("👁","Reading...")
time.sleep(.35)
yield "🔍 Scanning for contradictions...", logline("🔍","Scanning...")
try:
result = call_llm(SYS_REAL, p_real(text), temperature=0.22, max_tokens=660)
out=""
for chunk in (result[i:i+7] for i in range(0,len(result),7)):
out+=chunk; yield out, logline("⚡","Checking..."); time.sleep(.008)
yield result, "\n".join([
logline("👁","Read carefully"),
logline("🔍","Contradictions checked"),
logline("⚡","Risky commitments flagged"),
logline("🛡","Reality check complete"),
])
except Exception as e:
yield f"⚠ Error: {e}", logline("❌",str(e))
# ─────────────────────────────────────────────────────────────
# UI
# ─────────────────────────────────────────────────────────────
with gr.Blocks(
css=CSS, title="GHOST — Private Intelligence Layer",
theme=gr.themes.Base(
primary_hue="violet", neutral_hue="slate",
font=gr.themes.GoogleFont("Syne"),
font_mono=gr.themes.GoogleFont("Space Mono"),
),
) as demo:
gr.HTML(HEADER)
with gr.Tabs():
# ── CHIEF OF STAFF ──
with gr.Tab("📋 Chief of Staff"):
gr.HTML('<div class="slabel">Paste any email, contract or meeting notes — Ghost extracts what actually matters</div>')
with gr.Row(equal_height=False):
with gr.Column(scale=1):
c1i = gr.Textbox(label="EMAIL / DOCUMENT",
placeholder="Paste any email, contract, or document here...\n\nGhost will extract:\n→ What it's really about\n→ Every deadline\n→ All commitments made\n→ Your 3 most important next actions",
lines=16)
c1b = gr.Button("👻 Analyze It", size="lg")
with gr.Column(scale=1):
c1o = gr.Textbox(label="GHOST'S ANALYSIS (editable)",
placeholder="SUMMARY · DEADLINES · COMMITMENTS · NEXT ACTIONS\nwill stream here...",
lines=16, interactive=True, elem_classes=["out-box"])
c1l = gr.Textbox(label="ACTION LOG", lines=4, interactive=False, elem_classes=["log-box"])
c1b.click(fn=chief_of_staff, inputs=c1i, outputs=[c1o,c1l])
# ── UNSENT LETTER ──
with gr.Tab("💌 Unsent Letter"):
gr.HTML('<div class="slabel">Say what you\'ve always meant to say — Ghost writes it beautifully in your voice</div>')
with gr.Row(equal_height=False):
with gr.Column(scale=1):
c2i = gr.Textbox(label="WHAT YOU WANT TO SAY",
placeholder='Just describe it roughly — Ghost does the rest.\n\nExample:\n"My dad sacrificed everything for my education.\nI never told him what that means to me.\nI want him to feel proud — and know it wasn\'t wasted."',
lines=10)
c2t = gr.Radio(choices=["Warm & Loving","Professional","Honest & Direct","Apologetic"],
value="Warm & Loving", label="TONE", elem_classes=["tone-wrap"])
c2b = gr.Button("👻 Write It", size="lg")
with gr.Column(scale=1):
c2o = gr.Textbox(label="YOUR LETTER (editable)",
placeholder="Ghost will write your letter here.\n\nRead it.\nEdit anything that doesn't feel right.\nThen send it.",
lines=16, interactive=True, elem_classes=["out-box"])
c2l = gr.Textbox(label="ACTION LOG", lines=4, interactive=False, elem_classes=["log-box"])
c2b.click(fn=unsent_letter, inputs=[c2i,c2t], outputs=[c2o,c2l])
# ── REALITY CHECK ──
with gr.Tab("🔍 Reality Check"):
gr.HTML('<div class="slabel">Paste any message or document — Ghost spots what you might miss</div>')
with gr.Row(equal_height=False):
with gr.Column(scale=1):
c3i = gr.Textbox(label="MESSAGE / DOCUMENT TO CHECK",
placeholder="Paste any message, contract, email, or document...\n\nGhost will find:\n→ Contradictions\n→ Missing context\n→ Risky commitments\n→ Red flags\n→ Final verdict",
lines=16)
c3b = gr.Button("👻 Reality Check", size="lg")
with gr.Column(scale=1):
c3o = gr.Textbox(label="GHOST'S VERDICT (editable)",
placeholder="Contradictions · Missing context\nRisky commitments · Red flags · Verdict\n\nwill stream here...",
lines=16, interactive=True, elem_classes=["out-box"])
c3l = gr.Textbox(label="ACTION LOG", lines=4, interactive=False, elem_classes=["log-box"])
c3b.click(fn=reality_check, inputs=c3i, outputs=[c3o,c3l])
gr.HTML("""
<div id="gh-footer">
<span style="font-size:22px">👻</span>
<span style="font-family:'Space Mono',monospace;font-size:10px;
color:#1f2937;letter-spacing:.07em;text-align:center">
Ghost never stores your conversations &nbsp;·&nbsp; No logs &nbsp;·&nbsp;
No cloud &nbsp;·&nbsp; Your words stay yours &nbsp;·&nbsp;
Phi-3-mini 3.8B &nbsp;·&nbsp; Build Small Hackathon 2026
</span>
</div>""")
demo.queue().launch()