PromptHub / app.py
aidn's picture
Update app.py
04eb956 verified
"""
PromptHub ⚑
Forge any AI agent from .md, .skill, or any text file.
Drop files β†’ fill system prompt β†’ chat β†’ share to the Collection.
Storage: HF Bucket β€” auto-mounted at /data inside the Space.
External access: hf sync hf://buckets/aidn/AnyAgent-storage ./local
"""
import os
import re
import json
import datetime
import gradio as gr
from huggingface_hub import InferenceClient
# ── Config ────────────────────────────────────────────────────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN", "")
MODEL_ID = os.environ.get("MODEL_ID", "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8")
BUCKET_DIR = "/data"
AGENTS_FILE = os.path.join(BUCKET_DIR, "agents.jsonl")
os.makedirs(BUCKET_DIR, exist_ok=True)
# ── Storage ───────────────────────────────────────────────────────────────────
def _fetch_all() -> list:
if not os.path.exists(AGENTS_FILE):
return []
entries = []
try:
with open(AGENTS_FILE, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
try:
entries.append(json.loads(line))
except Exception:
pass
except Exception:
pass
return entries
def _push_agent(agent: dict) -> str:
try:
with open(AGENTS_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps(agent, ensure_ascii=False) + "\n")
return ""
except Exception as e:
return str(e)
def _collection_agents() -> list:
return [a for a in _fetch_all() if a.get("in_collection", False)]
# ── File Processing ───────────────────────────────────────────────────────────
def files_to_prompt(files, current: str) -> str:
if not files:
return current
parts = [current.strip()] if current.strip() else []
for f in files:
try:
path = f.name if hasattr(f, "name") else str(f)
with open(path, "r", encoding="utf-8", errors="ignore") as fh:
content = fh.read().strip()
fname = os.path.basename(path)
parts.append(f"<!-- {fname} -->\n{content}")
except Exception as e:
parts.append(f"<!-- Error reading file: {e} -->")
return "\n\n---\n\n".join(parts)
# ── Chat ──────────────────────────────────────────────────────────────────────
def _bot_stream(history: list, system_prompt: str, max_tokens: int):
if not history:
return
if not HF_TOKEN:
history[-1]["content"] = "⚠️ No HF_TOKEN found. Add it in Settings β†’ Secrets."
yield history
return
sys_prompt = system_prompt.strip() or "You are a helpful assistant."
msgs = [{"role": "system", "content": sys_prompt}]
for m in history[:-1]:
msgs.append({"role": m["role"], "content": m["content"] or ""})
msgs.append({"role": "user", "content": history[-2]["content"]})
client = InferenceClient(provider="novita", api_key=HF_TOKEN)
try:
for chunk in client.chat.completions.create(
model=MODEL_ID,
messages=msgs,
max_tokens=max_tokens,
stream=True,
):
delta = chunk.choices[0].delta.content or ""
history[-1]["content"] += delta
yield history
except Exception as e:
history[-1]["content"] += f"\n\n⚠️ Error: {e}"
yield history
# ── Activate Agent ────────────────────────────────────────────────────────────
def activate_agent(name: str, prompt: str, in_coll: bool):
name = name.strip() or "Unnamed Agent"
prompt = prompt.strip()
if not prompt:
return (
"",
_status_html("⚠️ Please add a system prompt before activating.", "warn"),
_render_collection(),
_render_quick_list(),
"No agent active",
)
status_msg = f'βœ… <strong>"{name}"</strong> activated!'
if in_coll:
agent = {
"name": name,
"system_prompt": prompt,
"in_collection": True,
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
}
err = _push_agent(agent)
if err:
status_msg += f'<br><span style="color:#f87171;">⚠️ Bucket write failed: {err}</span>'
else:
status_msg += (
' &nbsp;🌐 Saved to collection '
'<span style="color:#666;font-size:.75rem;">Β· /data/agents.jsonl</span>'
)
return (
prompt,
_status_html(status_msg, "ok"),
_render_collection(),
_render_quick_list(),
name,
)
# ── Load from collection (JS β†’ Python via hidden state) ──────────────────────
def _load_signal(signal: str):
"""Parse JSON signal from agentForgeLoad() JS call."""
if not signal or not signal.strip():
return gr.update(), gr.update(), gr.update(), gr.update()
try:
data = json.loads(signal)
name = data.get("name", "")
prompt = data.get("prompt", "")
return name, prompt, prompt, name
except Exception:
return gr.update(), gr.update(), gr.update(), gr.update()
# ── Status HTML ───────────────────────────────────────────────────────────────
def _status_html(msg: str, kind: str = "ok") -> str:
colors = {
"ok": ("rgba(74,222,128,.10)", "#4ade80", "#a7f3d0"),
"warn": ("rgba(251,191,36,.10)", "#fbbf24", "#fde68a"),
"err": ("rgba(248,113,113,.10)", "#f87171", "#fca5a5"),
}
bg, border, text = colors.get(kind, colors["ok"])
return (
f'<div style="background:{bg};border:1px solid {border}44;border-left:3px solid {border};'
f'border-radius:8px;padding:10px 14px;font-size:.83rem;color:{text};line-height:1.6;">'
f'{msg}</div>'
)
# ── Rendering ─────────────────────────────────────────────────────────────────
_PALETTE = ["#f97316", "#a78bfa", "#38bdf8", "#4ade80", "#fb7185", "#fbbf24"]
def _agent_color(i: int) -> str:
return _PALETTE[i % len(_PALETTE)]
def _prompt_preview(prompt: str, max_len: int = 90) -> str:
"""One-line preview: strip all newlines, truncate."""
clean = re.sub(r'\s+', ' ', prompt).strip()
return (clean[:max_len] + "…") if len(clean) > max_len else clean
def _js_escape(s: str) -> str:
"""Escape a string for safe embedding inside JS single-quoted string."""
return (s
.replace("\\", "\\\\")
.replace("'", "\\'")
.replace("\n", "\\n")
.replace("\r", "")
.replace('"', '\\"'))
def _render_collection() -> str:
agents = _collection_agents()
count = len(agents)
total_count = len(_fetch_all())
bucket_badge = (
f'<span style="font-size:.68rem;color:#666;border:1px solid #2a2a3a;'
f'border-radius:99px;padding:2px 10px;font-family:\'JetBrains Mono\',monospace;'
f'white-space:nowrap;" title="hf sync hf://buckets/aidn/AnyAgent-storage ./local">'
f'πŸ—„οΈ /data Β· {total_count}</span>'
)
refresh_btn = (
'<button onclick="document.getElementById(\'hidden_refresh_btn\').click()"'
' style="background:transparent;border:1px solid #2a2a3a;color:#666;'
'font-size:.68rem;border-radius:99px;padding:2px 8px;cursor:pointer;transition:all .15s;"'
' onmouseover="this.style.color=\'#f97316\';this.style.borderColor=\'#f97316\';"'
' onmouseout="this.style.color=\'#666\';this.style.borderColor=\'#2a2a3a\';">πŸ”„</button>'
)
if not agents:
return (
'<div style="text-align:center;padding:50px 20px;">'
'<div style="font-size:2.5rem;margin-bottom:12px;">βš—οΈ</div>'
'<div style="font-weight:700;font-size:.9rem;color:#888;margin-bottom:6px;">The forge is cold.</div>'
'<div style="font-size:.8rem;color:#666;max-width:260px;margin:0 auto;line-height:1.6;">'
'Activate an agent and check <strong style="color:#f97316;">Add to Collection</strong>'
' to be the first.</div></div>'
)
cards = ""
for i, agent in enumerate(reversed(agents)):
name = agent.get("name", "Unnamed Agent")
prompt = agent.get("system_prompt", "")
date = agent.get("timestamp", "")[:10]
color = _agent_color(i)
chars = len(prompt)
preview = _prompt_preview(prompt, 80)
n_js = _js_escape(name)
p_js = _js_escape(prompt)
safe_preview = preview.replace("<", "&lt;").replace(">", "&gt;")
cards += (
f'<div style="background:#0c0c1a;border:1px solid #1e1e30;border-left:3px solid {color};'
f'border-radius:10px;padding:14px 16px;margin-bottom:8px;cursor:pointer;transition:background .15s;"'
f' onmouseover="this.style.background=\'#12122a\';"'
f' onmouseout="this.style.background=\'#0c0c1a\';"'
f' onclick="agentForgeLoad(\'{n_js}\', \'{p_js}\')">'
f'<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">'
f'<span style="font-weight:700;color:#ddddf0;font-size:.86rem;flex:1;'
f'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{name}</span>'
f'<span style="font-size:.65rem;color:#555;white-space:nowrap;">{date}</span>'
f'</div>'
# Expandable preview using <details>
f'<details style="margin-bottom:8px;">'
f'<summary style="font-size:.72rem;color:#777;cursor:pointer;'
f'list-style:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'
f'font-family:\'JetBrains Mono\',monospace;">{safe_preview}</summary>'
f'<div style="margin-top:8px;font-size:.72rem;color:#999;font-family:\'JetBrains Mono\',monospace;'
f'white-space:pre-wrap;line-height:1.5;max-height:180px;overflow-y:auto;'
f'background:#080812;padding:8px;border-radius:6px;">'
f'{prompt[:800].replace("<","&lt;").replace(">","&gt;")}{"…" if len(prompt)>800 else ""}'
f'</div></details>'
f'<div style="display:flex;align-items:center;gap:8px;">'
f'<span style="font-size:.65rem;color:{color};border:1px solid {color}44;'
f'background:{color}11;border-radius:99px;padding:1px 8px;font-weight:600;white-space:nowrap;">'
f'Load into Forge β†’</span>'
f'<span style="margin-left:auto;font-size:.65rem;color:#555;">{chars:,} chars</span>'
f'</div>'
f'</div>'
)
return (
f'<div>'
f'<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;flex-wrap:wrap;">'
f'<span style="font-weight:700;color:#f97316;font-size:.72rem;'
f'text-transform:uppercase;letter-spacing:.6px;">⚑ {count} Agent{"s" if count!=1 else ""} Forged</span>'
f'<div style="margin-left:auto;display:flex;gap:6px;align-items:center;">'
f'{bucket_badge}{refresh_btn}'
f'</div></div>'
f'{cards}'
f'</div>'
)
def _render_quick_list() -> str:
agents = _collection_agents()
if not agents:
return '<div style="color:#555;font-size:.78rem;padding:6px 2px;">No agents yet β€” forge the first one! ⚑</div>'
items = ""
for i, agent in enumerate(reversed(agents[:10])):
name = agent.get("name", "Unnamed")
prompt = agent.get("system_prompt", "")
color = _agent_color(i)
n_js = _js_escape(name)
p_js = _js_escape(prompt)
items += (
f'<div style="display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:6px;'
f'cursor:pointer;transition:background .15s;"'
f' onmouseover="this.style.background=\'#14142a\';"'
f' onmouseout="this.style.background=\'transparent\';"'
f' onclick="agentForgeLoad(\'{n_js}\', \'{p_js}\')">'
f'<div style="width:5px;height:5px;border-radius:50%;background:{color};flex-shrink:0;"></div>'
f'<span style="font-size:.8rem;color:#ccc;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{name}</span>'
f'<span style="font-size:.65rem;color:#555;white-space:nowrap;">Load β†’</span>'
f'</div>'
)
if len(agents) > 10:
items += f'<div style="font-size:.67rem;color:#555;padding:4px 6px;">+ {len(agents)-10} more</div>'
return items
def _render_active_badge(name: str) -> str:
if not name or name == "No agent active":
return '<div class="agent-badge inactive"><div class="dot-inactive"></div>No agent active</div>'
safe = name[:40] + ("…" if len(name) > 40 else "")
return f'<div class="agent-badge active"><div class="dot-active"></div>Active: <strong>{safe}</strong></div>'
# ── JS ────────────────────────────────────────────────────────────────────────
# NOTE: head= is passed to demo.launch() in Gradio 6
JS_HEAD = r"""
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
function agentForgeLoad(name, prompt) {
// Decode escaped newlines back to real newlines
var decoded = prompt.split("\\n").join("\n");
var signal = JSON.stringify({ name: name, prompt: decoded });
// Suche das Signal‑Textfeld (ID: agent_load_signal)
var el = document.querySelector("#agent_load_signal textarea");
if (!el) {
// Fallback: direktes <input> (sollte nicht nΓΆtig sein, aber sicherheitshalber)
el = document.querySelector("#agent_load_signal input");
}
if (!el) {
console.warn("PromptHub: signal element #agent_load_signal not found");
return;
}
// Wert setzen und Events auslΓΆsen, damit Gradio die Γ„nderung erkennt
el.value = signal;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
</script>
"""
# ── CSS ───────────────────────────────────────────────────────────────────────
# Korrigierte Chatbot‑Darstellung (kein vertikales Buchstabieren mehr)
CSS = """
:root {
--bg: #08080f;
--card: #0d0d1a;
--card2: #11111e;
--border: #1a1a2e;
--border2: #222235;
--orange: #f97316;
--orange-dim: rgba(249,115,22,.15);
--orange-glow:rgba(249,115,22,.25);
--text: #e0e0f0;
--muted: #44445a;
--muted2: #888;
--green: #4ade80;
--font-head: 'Chakra Petch', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
body, .gradio-container {
background: var(--bg) !important;
font-family: var(--font-head) !important;
color: var(--text) !important;
}
.block { background: var(--card) !important; border-color: var(--border) !important; }
/* ── Header ── */
.forge-header {
background: linear-gradient(135deg, #0d0d1a 0%, #130a20 50%, #0a1020 100%);
border: 1px solid var(--border2);
border-radius: 14px;
padding: 24px 28px;
margin-bottom: 16px;
position: relative;
overflow: hidden;
}
.forge-header::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 15% 60%, rgba(249,115,22,.10) 0%, transparent 55%),
radial-gradient(ellipse at 85% 40%, rgba(167,139,250,.07) 0%, transparent 55%);
pointer-events: none;
}
.forge-header h1 {
font-family: var(--font-head) !important;
font-size: 1.9rem !important;
font-weight: 800 !important;
color: var(--text) !important;
margin: 0 0 4px 0 !important;
position: relative;
}
.forge-header p {
font-size: .8rem !important;
color: var(--muted2) !important;
margin: 0 !important;
position: relative;
font-family: var(--font-mono) !important;
}
/* ── Agent badge ── */
.agent-badge {
display: inline-flex;
align-items: center;
gap: 7px;
border-radius: 99px;
padding: 5px 14px;
font-size: .78rem;
font-weight: 700;
font-family: var(--font-head);
border: 1px solid;
margin-bottom: 8px;
}
.agent-badge.active { background: rgba(249,115,22,.08); border-color: rgba(249,115,22,.3); color: var(--orange); }
.agent-badge.inactive { background: rgba(85,85,112,.06); border-color: var(--border2); color: var(--muted2); }
.dot-active { width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 7px var(--green);animation:pulse-dot 2s ease-in-out infinite; }
.dot-inactive { width:7px;height:7px;border-radius:50%;background:var(--muted); }
@keyframes pulse-dot { 0%,100%{opacity:1} 50%{opacity:.3} }
/* ── Labels ── */
label > span {
font-weight: 700 !important;
font-size: .67rem !important;
text-transform: uppercase !important;
letter-spacing: .6px !important;
color: var(--orange) !important;
font-family: var(--font-head) !important;
}
/* ── Inputs ── */
textarea, input[type="text"] {
background: var(--card) !important;
color: var(--text) !important;
border: 1px solid var(--border2) !important;
border-radius: 8px !important;
font-size: .87rem !important;
font-family: var(--font-mono) !important;
writing-mode: horizontal-tb !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--orange) !important;
box-shadow: 0 0 0 2px var(--orange-dim) !important;
outline: none !important;
}
textarea::placeholder, input[type="text"]::placeholder {
color: var(--muted) !important;
font-family: var(--font-mono) !important;
}
/* ── Chatbot – FIX: horizontale, lesbare Nachrichten ── */
.chatbot .message,
.chatbot .bubble,
.chatbot [data-testid="bot"],
.chatbot [data-testid="user"] {
writing-mode: horizontal-tb !important;
white-space: pre-wrap !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
max-width: 85% !important;
width: fit-content !important;
}
.chatbot .message p,
.chatbot .bubble p,
.chatbot [data-testid="bot"] p,
.chatbot [data-testid="user"] p {
white-space: pre-wrap !important;
word-break: break-word !important;
display: block !important;
}
.chatbot * {
writing-mode: horizontal-tb !important;
}
/* ── File drop ── */
.file-upload-container, .upload-box {
background: var(--card) !important;
border: 2px dashed var(--border2) !important;
border-radius: 10px !important;
transition: all .2s !important;
}
.file-upload-container:hover, .upload-box:hover {
border-color: var(--orange) !important;
background: var(--orange-dim) !important;
}
/* ── Checkbox ── */
input[type="checkbox"] { accent-color: var(--orange) !important; }
/* ── Buttons ── */
button.primary {
background: linear-gradient(135deg, var(--orange), #c2410c) !important;
border: none !important;
border-radius: 8px !important;
font-weight: 700 !important;
font-family: var(--font-head) !important;
color: #fff !important;
box-shadow: 0 4px 16px var(--orange-glow) !important;
transition: all .15s !important;
}
button.primary:hover { box-shadow: 0 6px 22px rgba(249,115,22,.45) !important; }
button.secondary {
background: transparent !important;
border: 1px solid var(--border2) !important;
color: var(--muted2) !important;
border-radius: 8px !important;
font-family: var(--font-head) !important;
font-weight: 600 !important;
transition: all .15s !important;
}
button.secondary:hover { border-color: var(--orange) !important; color: var(--orange) !important; }
/* ── Accordion ── */
.accordion > .label-wrap {
background: var(--card2) !important;
border-color: var(--border) !important;
color: var(--text) !important;
font-family: var(--font-head) !important;
font-size: .8rem !important;
font-weight: 700 !important;
}
.accordion > .label-wrap:hover { background: var(--card) !important; }
/* ── Tabs ── */
.tab-nav button {
font-family: var(--font-head) !important;
font-weight: 700 !important;
font-size: .75rem !important;
text-transform: uppercase !important;
letter-spacing: .4px !important;
color: var(--muted2) !important;
background: transparent !important;
border-bottom: 2px solid transparent !important;
transition: all .15s !important;
}
.tab-nav button.selected { color: var(--orange) !important; border-bottom-color: var(--orange) !important; }
/* ── Hidden helpers ── */
#hidden_refresh_btn { display: none !important; }
#agent_load_signal {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
height: 0 !important;
overflow: hidden !important;
width: 1px !important;
}
footer { display: none !important; }
"""
# ── Build UI ──────────────────────────────────────────────────────────────────
with gr.Blocks(title="PromptHub ⚑") as demo:
system_prompt_state = gr.State("")
active_name_state = gr.State("No agent active")
gr.HTML("""
<div class="forge-header">
<h1>Prompt<em style="font-style:normal;color:#f97316;">Hub</em> ⚑</h1>
<p>Drop .md Β· .skill Β· .txt Β· any text file β†’ fill system prompt β†’ chat β†’ share to the Collection</p>
</div>
""")
with gr.Row(equal_height=False):
# ── LEFT ──────────────────────────────────────────────────────────────
with gr.Column(scale=4, min_width=280):
gr.HTML('<div style="font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.7px;color:#f97316;margin-bottom:8px;">πŸ“ Drop Files</div>')
file_upload = gr.File(
label="",
file_count="multiple",
file_types=[".md", ".txt", ".skill", ".yaml", ".yml",
".json", ".py", ".ts", ".js", ".toml"],
height=110,
)
prompt_box = gr.Textbox(
label="System Prompt",
placeholder="Paste or type your system prompt…\nOr drop files above ↑\n\nTip: multiple files are auto-concatenated.",
lines=9,
max_lines=22,
)
agent_name_box = gr.Textbox(
label="Agent Name",
placeholder="e.g. Code Reviewer Β· Sales Coach Β· SQL Expert",
max_lines=1,
)
in_coll_check = gr.Checkbox(
label="🌐 Add to public Agent Collection",
value=False,
info="Saves the raw system prompt string to the shared HF Bucket.",
)
activate_btn = gr.Button("⚑ Activate Agent", variant="primary", size="lg")
status_out = gr.HTML("")
with gr.Accordion("βš™οΈ Model Settings", open=False):
max_tokens_slider = gr.Slider(
minimum=256, maximum=8192, value=1024, step=128,
label="Max output tokens",
)
gr.Markdown(f"*Model: `{MODEL_ID}`*")
gr.HTML("<div style='height:4px;'></div>")
with gr.Accordion("πŸ“š Quick Load from Collection", open=True):
quick_list_html = gr.HTML(_render_quick_list())
# Signal‑Textbox (unsichtbar, aber aktiv)
agent_load_signal = gr.Textbox(
value="",
elem_id="agent_load_signal",
label="",
visible=True,
)
# ── RIGHT ─────────────────────────────────────────────────────────────
with gr.Column(scale=6):
active_badge_html = gr.HTML(_render_active_badge("No agent active"))
chatbot = gr.Chatbot(
height=440,
show_label=False,
)
# Chat input row
with gr.Row():
msg_box = gr.Textbox(
placeholder="Message your agent…",
show_label=False,
scale=7,
max_lines=4,
)
send_btn = gr.Button("Send", variant="primary", scale=2, min_width=80)
clear_btn = gr.Button("Clear", variant="secondary", scale=1, min_width=70)
# ── Tabs ──────────────────────────────────────────────────────────────────
gr.HTML("<div style='height:8px;'></div>")
with gr.Tabs():
with gr.Tab("🌐 Agent Collection"):
collection_out = gr.HTML(_render_collection())
hidden_refresh_btn = gr.Button("refresh", elem_id="hidden_refresh_btn", visible=False)
with gr.Tab("ℹ️ About"):
gr.Markdown("""
## PromptHub ⚑
**Forge any AI agent from plain text files.**
Drop `.md`, `.skill`, `.txt`, `.yaml`, or any text file β€” PromptHub reads them,
concatenates their content, and uses it as the system prompt for a live chat session.
### How it works
1. **Drop files** or paste text directly into the *System Prompt* box
2. Give your agent a **name**
3. Optionally **add it to the Collection** so others can load and use it
4. Hit **⚑ Activate Agent** β€” then chat!
### Storage
All agents are stored in `/data/agents.jsonl` via HF Bucket (auto-mounted).
```bash
# Download the bucket
hf sync hf://buckets/aidn/AnyAgent-storage ./local
```
### Secrets
| Secret | Purpose |
|--------|---------|
| `HF_TOKEN` | Required for inference |
| `MODEL_ID` | Optional model override |
""")
if not HF_TOKEN:
gr.HTML(_status_html(
"No <code>HF_TOKEN</code> β€” add it in <em>Settings β†’ Secrets</em>.", "warn"))
# ── Events ────────────────────────────────────────────────────────────────
file_upload.change(
fn=files_to_prompt,
inputs=[file_upload, prompt_box],
outputs=[prompt_box],
)
activate_btn.click(
fn=activate_agent,
inputs=[agent_name_box, prompt_box, in_coll_check],
outputs=[system_prompt_state, status_out,
collection_out, quick_list_html, active_name_state],
).then(
fn=_render_active_badge,
inputs=[active_name_state],
outputs=[active_badge_html],
)
def _submit(msg, hist):
if not msg.strip():
return gr.update(), hist
return "", hist + [
{"role": "user", "content": msg},
{"role": "assistant", "content": ""},
]
msg_box.submit(
fn=_submit,
inputs=[msg_box, chatbot],
outputs=[msg_box, chatbot],
).then(
fn=_bot_stream,
inputs=[chatbot, system_prompt_state, max_tokens_slider],
outputs=[chatbot],
)
send_btn.click(
fn=_submit,
inputs=[msg_box, chatbot],
outputs=[msg_box, chatbot],
).then(
fn=_bot_stream,
inputs=[chatbot, system_prompt_state, max_tokens_slider],
outputs=[chatbot],
)
clear_btn.click(fn=lambda: [], outputs=[chatbot])
agent_load_signal.change(
fn=_load_signal,
inputs=[agent_load_signal],
outputs=[agent_name_box, prompt_box, system_prompt_state, active_name_state],
).then(
fn=_render_active_badge,
inputs=[active_name_state],
outputs=[active_badge_html],
)
hidden_refresh_btn.click(
fn=lambda: (_render_collection(), _render_quick_list()),
outputs=[collection_out, quick_list_html],
)
if __name__ == "__main__":
demo.launch(
css=CSS,
head=JS_HEAD,
)