AI-Live / app.py
brmansoneka's picture
Update app.py
099134b verified
Raw
History Blame Contribute Delete
18.1 kB
# 📢 1. ZeroGPU keepalive setup
import spaces
@spaces.GPU
def _hf_zerogpu_keepalive():
return None
import os
import sys
import time
import subprocess
import html
# 2. Auto-install required packages
for pkg in ["gradio", "psutil"]:
try:
__import__(pkg)
except ImportError:
subprocess.run([sys.executable, "-m", "pip", "install", pkg, "-q"], check=True)
import gradio as gr
import psutil
# Global directory state
current_working_dir = os.path.expanduser("~")
initial_text = "<div id='welcome_msg' style='color:#8b949e; font-size:11px;'>[TERNAL Terminal Is Ready...]</div>"
# ==========================================
# 3. Terminal Execution Engine
# ==========================================
def execute_terminal(cmd, current_history):
global current_working_dir
if not cmd or not cmd.strip():
return current_history, ""
cmd_clean = cmd.strip()
if cmd_clean == "clear":
return initial_text, ""
# Remove initial text on first command
if current_history and initial_text in current_history:
current_history = current_history.replace(initial_text, "")
if cmd_clean.startswith("cd ") or cmd_clean == "cd":
target = cmd_clean[3:].strip() if len(cmd_clean) > 2 else os.path.expanduser("~")
if target == "~":
target = os.path.expanduser("~")
try:
new_path = os.path.abspath(os.path.join(current_working_dir, target))
if os.path.isdir(new_path):
current_working_dir = new_path
output = f"<span style='color: #7ee787;'>[Dir: {current_working_dir}]</span>"
else:
output = f"<span style='color: #ff7b72;'>cd: {target}: No such directory</span>"
except Exception as e:
output = f"<span style='color: #ff7b72;'>cd error: {str(e)}</span>"
else:
try:
res = subprocess.run(
cmd_clean,
shell=True,
cwd=current_working_dir,
capture_output=True,
text=True,
timeout=180
)
out_html = ""
if res.stdout:
out_html += f"<span style='color: #a5d6ff;'>{html.escape(res.stdout)}</span>"
if res.stderr:
out_html += f"<span style='color: #ff7b72;'>{html.escape(res.stderr)}</span>"
if not res.stdout and not res.stderr:
out_html = "<span style='color: #8b949e;'>[Process finished with exit code 0]</span>"
output = f"<pre style='margin: 0; font-family: inherit; font-size: 11.5px; white-space: pre-wrap; word-break: break-all;'>{out_html}</pre>"
except subprocess.TimeoutExpired:
output = "<span style='color: #ff7b72;'>[Timeout: 180s limit exceeded!]</span>"
except Exception as e:
output = f"<span style='color: #ff7b72;'>[Error: {html.escape(str(e))}]</span>"
user_part = "<span style='color: #7ee787; font-weight: bold;'>user@hf</span>"
path_part = f"<span style='color: #79c0ff;'>:{os.path.basename(current_working_dir) or '/'}</span>"
cmd_part = f"<span style='color: #f2cc60; font-weight: bold;'>$ {html.escape(cmd_clean)}</span>"
prompt_line = f"<div style='margin-top: 6px; padding-top: 3px; font-size: 11.5px;'>{user_part}{path_part}{cmd_part}</div>"
output_block = f"<div style='padding-left: 2px; margin-bottom: 4px;'>{output}</div>"
updated_history = (current_history if current_history else "") + prompt_line + output_block
scroll_js = "<script>var el = document.getElementById('terminal_output'); if(el) el.scrollTop = el.scrollHeight;</script>"
return updated_history + scroll_js, ""
# ==========================================
# 4. Live RAM & CPU Header
# ==========================================
def get_live_header():
used_gb = 0.0
try:
if os.path.exists("/sys/fs/cgroup/memory.current"):
with open("/sys/fs/cgroup/memory.current") as f:
used_gb = round(int(f.read().strip()) / (1024**3), 1)
else:
used_gb = round(psutil.virtual_memory().used / (1024**3), 1)
except:
used_gb = round(psutil.virtual_memory().used / (1024**3), 1)
if used_gb < 0.1 or used_gb > 96.0:
used_gb = round(psutil.virtual_memory().used / (1024**3), 1)
cpu_load = int(psutil.cpu_percent())
ram_svg = """<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#79c0ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>"""
cpu_svg = """<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7ee787" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line></svg>"""
return f"""
<div style='display: flex; justify-content: space-between; align-items: center; width: 100%;'>
<div style='display: flex; align-items: center; gap: 5px; white-space: nowrap;'>
{ram_svg} <span style='color: #79c0ff; font-weight: bold;'>RAM:</span> {used_gb}/96GB
</div>
<div style='display: flex; align-items: center; gap: 5px; white-space: nowrap;'>
{cpu_svg} <span style='color: #7ee787; font-weight: bold;'>CPU:</span> 16C ({cpu_load}%)
</div>
</div>
"""
# ==========================================
# 5. Bulletproof CSS (Hardware Engine Auto-Lock)
# ==========================================
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
footer { display: none !important; }
html, body {
height: 100dvh !important;
width: 100vw !important;
background-color: #05070a !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
position: fixed !important;
}
.gradio-container {
padding: 0 !important;
margin: 0 !important;
max-width: 100% !important;
width: 100vw !important;
height: 100dvh !important;
border: none !important;
background-color: #05070a !important;
}
#main_wrapper {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100dvh !important;
background-color: #05070a !important;
margin: 0 !important;
padding: 0 !important;
}
/* Top Header */
#live_header {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 32px !important;
background-color: #161b22 !important;
border-bottom: 1px solid #30363d !important;
padding: 0 15px !important;
display: flex !important;
align-items: center !important;
box-sizing: border-box !important;
z-index: 9999 !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 11px !important;
}
#live_header > div { width: 100% !important; }
/* TERNAL Sub-header */
#ternal_branding {
position: fixed !important;
top: 32px !important;
left: 0 !important;
width: 100vw !important;
height: 26px !important;
background-color: #0d1117 !important;
border-bottom: none !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
box-sizing: border-box !important;
z-index: 9998 !important;
}
.ternal-text {
font-family: 'JetBrains Mono', monospace;
font-size: 11.5px;
font-weight: bold;
letter-spacing: 1.5px;
background: linear-gradient(90deg, #ff007f, #79c0ff, #7ee787, #f2cc60, #ff007f);
background-size: 200% auto;
color: transparent !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
animation: shine 3s linear infinite;
margin-right: 6px;
}
@keyframes shine { to { background-position: 200% center; } }
/* Output screen */
#terminal_output {
position: fixed !important;
top: 58px !important;
bottom: 75px !important;
left: 0 !important;
width: 100vw !important;
overflow-y: auto !important;
padding: 10px !important;
font-family: 'JetBrains Mono', monospace !important;
box-sizing: border-box !important;
background-color: #05070a !important;
}
/* Bottom Bar */
#bottom_row {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 75px !important;
background-color: #0b0e14 !important;
border-top: 1px solid #30363d !important;
display: flex !important;
flex-direction: row !important;
align-items: flex-start !important;
padding: 8px 8px 25px 8px !important;
box-sizing: border-box !important;
z-index: 9999 !important;
gap: 8px !important;
}
/* Clear button */
#btn_clear {
background-color: #da3633 !important;
color: #ffffff !important;
min-width: 75px !important;
max-width: 75px !important;
height: 40px !important;
font-size: 12px !important;
border-radius: 6px !important;
border: none !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
cursor: pointer !important;
}
#btn_clear::before {
content: "";
display: inline-block;
width: 14px;
height: 14px;
margin-right: 5px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBvbHlsaW5lIHBvaW50cz0iMyA2IDUgNiAyMSA2Ij48L3BvbHlsaW5lPjxwYXRoIGQ9Ik0xOSA2djE0YTIgMiAwIDAgMS0yIDJIN2EyIDIgMCAwIDEtMi0yVjZtMyAwVjRhMiAyIDAgMCAxIDItMmg0YTIgMiAwIDAgMSAyIDJ2MiI+PC9wYXRoPjwvc3ZnPg==");
background-size: contain;
background-repeat: no-repeat;
}
#cmd_input {
flex-grow: 1 !important;
height: 40px !important;
}
#cmd_input input, #cmd_input textarea {
background-color: #161b22 !important;
color: #58a6ff !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 14px !important;
border: 1px solid #30363d !important;
border-radius: 6px !important;
height: 40px !important;
padding: 0 10px !important;
width: 100% !important;
box-sizing: border-box !important;
}
/* Send button default active state */
#btn_send {
background-color: #238636 !important;
color: #ffffff !important;
min-width: 75px !important;
max-width: 75px !important;
height: 40px !important;
font-size: 12px !important;
border-radius: 6px !important;
border: none !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
cursor: pointer !important;
transition: all 0.15s ease !important;
}
#btn_send::before {
content: "";
display: inline-block;
width: 14px;
height: 14px;
margin-right: 5px;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGxpbmUgeDE9IjIyIiB5MT0iMiIgeDI9IjExIiB5Mj0iMTMiPjwvbGluZT48cG9seWdvbiBwb2ludHM9IjIyIDIgMTUgMjIgMTEgMTMgMiA5IDIyIDIiPjwvcG9seWdvbj48L3N2Zz4=");
background-size: contain;
background-repeat: no-repeat;
}
/* 🔒 1. CSS Engine Auto-Lock: ইনপুট খালি থাকলে বাটন ১০০% লক ও ধূসর */
#bottom_row:has(#cmd_input input:placeholder-shown) #btn_send,
#bottom_row:has(#cmd_input textarea:placeholder-shown) #btn_send {
background-color: #21262d !important;
color: #6e7681 !important;
opacity: 0.35 !important;
pointer-events: none !important;
cursor: not-allowed !important;
}
#bottom_row:has(#cmd_input input:placeholder-shown) #btn_send::before,
#bottom_row:has(#cmd_input textarea:placeholder-shown) #btn_send::before {
opacity: 0.25 !important;
}
/* 🔒 2. Execution Lock: কমান্ড আউটপুট আসার আগ পর্যন্ত লক */
#btn_send.running-state,
.pending #btn_send {
background-color: #21262d !important;
color: #6e7681 !important;
opacity: 0.35 !important;
pointer-events: none !important;
cursor: not-allowed !important;
}
/* Hidden Action Button */
#btn_hidden_clear {
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
opacity: 0 !important;
width: 1px !important;
height: 1px !important;
pointer-events: none !important;
}
"""
# HTML for in-app Dark Modal Dialog
modal_html = """
<div id="clear_modal" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.85); backdrop-filter: blur(8px); z-index: 100000; justify-content: center; align-items: center; margin: 0; padding: 0;">
<div style="background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 22px; width: 85%; max-width: 310px; text-align: center; box-shadow: 0 20px 40px rgba(0,0,0,0.9); font-family: 'JetBrains Mono', monospace;">
<div style="font-size: 15px; font-weight: bold; color: #f85149; margin-bottom: 8px;">Clear Terminal?</div>
<div style="font-size: 11.5px; color: #8b949e; margin-bottom: 20px; line-height: 1.5;">This will clear all current screen logs. Are you sure?</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<button onclick="document.getElementById('clear_modal').style.display='none';" style="background: #21262d; color: #c9d1d9; border: 1px solid #30363d; padding: 8px 18px; border-radius: 6px; font-size: 12px; font-weight: bold; cursor: pointer;">Cancel</button>
<button onclick="document.getElementById('clear_modal').style.display='none'; const el = document.getElementById('btn_hidden_clear') || document.querySelector('#btn_hidden_clear button'); if(el) el.click();" style="background: #da3633; color: #ffffff; border: none; padding: 8px 18px; border-radius: 6px; font-size: 12px; font-weight: bold; cursor: pointer;">Confirm</button>
</div>
</div>
</div>
"""
# TERNAL Sub-header Branding
ternal_html = """
<div id="ternal_branding">
<span class="ternal-text">TERNAL</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#79c0ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
</div>
"""
# Client-side dynamic execution tracker
client_js = """
() => {
function initTracker() {
const sendBtn = document.querySelector('#btn_send');
const term = document.querySelector('#terminal_output');
const inp = document.querySelector('#cmd_input input') || document.querySelector('#cmd_input textarea');
if (!sendBtn || !term || !inp) return;
function lockButton() {
sendBtn.classList.add('running-state');
}
inp.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && inp.value.trim().length > 0) {
lockButton();
}
});
sendBtn.addEventListener('click', lockButton);
// Unlock when output arrives on screen
const observer = new MutationObserver(() => {
sendBtn.classList.remove('running-state');
});
observer.observe(term, { childList: true, subtree: true, characterData: true });
}
setTimeout(initTracker, 300);
setTimeout(initTracker, 1000);
}
"""
# ==========================================
# 6. Gradio UI
# ==========================================
with gr.Blocks(title="Ternal", css=custom_css, js=client_js) as demo:
with gr.Column(elem_id="main_wrapper"):
# In-App Custom Modal
modal_component = gr.HTML(value=modal_html)
# Top Live Header (RAM & CPU)
header_bar = gr.HTML(value=get_live_header, every=1.5, elem_id="live_header")
# TERNAL Branding
ternal_bar = gr.HTML(value=ternal_html)
# Terminal Output Screen
terminal_box = gr.HTML(value=initial_text, elem_id="terminal_output")
# Bottom Controls
with gr.Row(elem_id="bottom_row"):
btn_clear = gr.Button("Clear", elem_id="btn_clear")
cmd_input = gr.Textbox(
label="",
placeholder="Enter command (e.g. ls, free -h)...",
elem_id="cmd_input",
container=False,
max_lines=1
)
btn_send = gr.Button("Send", elem_id="btn_send")
# Hidden button for modal trigger
btn_hidden_clear = gr.Button("HiddenClear", elem_id="btn_hidden_clear")
# 1. Clear button opens modal
btn_clear.click(fn=None, js="() => { document.getElementById('clear_modal').style.display = 'flex'; }")
# 2. Modal's confirm button triggers clear action
btn_hidden_clear.click(lambda: (initial_text, ""), outputs=[terminal_box, cmd_input])
# 3. Command execution
cmd_input.submit(execute_terminal, inputs=[cmd_input, terminal_box], outputs=[terminal_box, cmd_input])
btn_send.click(execute_terminal, inputs=[cmd_input, terminal_box], outputs=[terminal_box, cmd_input])
if __name__ == "__main__":
demo.launch()