| """ |
| 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 |
|
|
| |
| 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) |
|
|
| |
|
|
| 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)] |
|
|
| |
|
|
| 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) |
|
|
| |
|
|
| 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 |
|
|
| |
|
|
| 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 += ( |
| ' π 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, |
| ) |
|
|
| |
|
|
| 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() |
|
|
| |
|
|
| 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>' |
| ) |
|
|
| |
|
|
| _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("<", "<").replace(">", ">") |
|
|
| 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>' |
|
|
| |
| 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("<","<").replace(">",">")}{"β¦" 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_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 = """ |
| :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; } |
| """ |
|
|
| |
|
|
| 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): |
|
|
| |
| 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()) |
|
|
| |
| agent_load_signal = gr.Textbox( |
| value="", |
| elem_id="agent_load_signal", |
| label="", |
| visible=True, |
| ) |
|
|
| |
| with gr.Column(scale=6): |
|
|
| active_badge_html = gr.HTML(_render_active_badge("No agent active")) |
|
|
| chatbot = gr.Chatbot( |
| height=440, |
| show_label=False, |
| ) |
|
|
| |
| 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) |
|
|
| |
| 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")) |
|
|
| |
|
|
| 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, |
| ) |