FinLLaVA / fa_page.py
jiminHuang's picture
Redesign: custom The Fin AI page + gr.api /chat backend (ZeroGPU)
dadccad verified
Raw History Blame Contribute Delete
6.64 kB
"""Custom The Fin AI page for Gradio-backed demo Spaces.
Gradio only serves the page and the `/chat` API (gr.api); the whole UI is our own HTML/CSS/JS (page/), which talks to
the API with @gradio/client. Works with Gradio 5 (head/css on Blocks) and Gradio 6 (head/css on launch).
page = fa_page.Page(cfg) # cfg: see build_body()
with gr.Blocks(**page.blocks_kwargs()) as demo:
page.render()
gr.api(chat, api_name="chat")
demo.queue().launch(**page.launch_kwargs())
"""
import html
import json
import os
import gradio as gr
HERE = os.path.dirname(os.path.abspath(__file__))
PAGE = os.path.join(HERE, "page")
ASSETS = os.path.join(HERE, "assets")
gr.set_static_paths(paths=[ASSETS])
GRADIO6 = int(gr.__version__.split(".")[0]) >= 6
CLIENT = "2.7.1" if GRADIO6 else "1.19.1" # @gradio/client matching the Gradio major version
e = lambda s: html.escape(str(s), quote=True)
def asset(path):
return f"/gradio_api/file={os.path.join(ASSETS, path)}"
def _read(name):
with open(os.path.join(PAGE, name), encoding="utf-8") as f:
return f.read()
ICON_SEND = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>'
ICON_STOP = '<svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>'
ICON_IMG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="3"/><circle cx="9" cy="10" r="2"/><path d="m21 16-5-5-9 9"/></svg>'
class Page:
def __init__(self, cfg):
self.cfg = cfg
def head(self):
c = self.cfg
chat = {"client": CLIENT, "endpoint": "/chat", "image": c.get("image", False), "avatar": asset(c["avatar"]),
"greeting": c["greeting"], "params": c["params"], "readyText": c.get("ready_text", "Ready"),
"examples": [{**x, "image": asset(x["image"]) if x.get("image") else None} for x in c["examples"]]}
fonts = ('<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=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400'
'&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">')
return (fonts + f'<link rel="icon" href="{asset("finai-logo.png")}"><style>{_read("style.css")}\n{_read("chat.css")}</style>'
f'<script>window.FA_CHAT={json.dumps(chat)};</script><script type="module">{_read("chat.js")}</script>')
def body(self):
c = self.cfg
nav = "".join(f'<a href="{e(u)}" target="_blank" rel="noreferrer">{e(l)}</a>' for l, u in c["nav"])
stats = "".join(f'<div><b data-count="{e(v)}"{f" data-dec={d}" if d else ""}>0</b><span>{e(k)}</span></div>' for v, k, d in c["stats"])
pills = "".join(f'<a class="pill" href="{e(u)}" target="_blank" rel="noreferrer">{e(l)}</a>' for l, u in c["links"])
orgs = "".join(f'<a class="org" href="{e(u)}" target="_blank" rel="noreferrer" style="--d:{i * 40}ms" title="{e(n)}">'
f'<img src="{asset("orgs/" + f)}" alt="" class="{e(k)}" loading="lazy"><span>{e(n)}</span></a>' for i, (n, f, u, k) in enumerate(c["orgs"]))
attach = (f'<label class="icon-btn attach" title="Add an image">{ICON_IMG}<input id="fa-image" type="file" accept="image/*"></label>'
'<div id="fa-preview" hidden><img alt=""><button type="button" aria-label="Remove image">×</button></div>') if c.get("image") else ""
return f'''<div id="fa-app">
<div class="progress" aria-hidden="true"></div>
<header class="header scrolled"><div class="wrap nav">
<a class="brand" href="https://thefin.ai" target="_blank" rel="noreferrer"><img src="{asset("finai-logo.png")}" alt="">The Fin AI</a>
<nav class="links">{nav}<a href="https://thefin.ai" target="_blank" rel="noreferrer">thefin.ai ↗</a></nav>
</div></header>
<main class="wrap demo">
<section class="intro">
<span class="kicker">{e(c["kicker"])}</span>
<h1>{c["title_html"]}</h1>
<p class="lead">{c["lead_html"]}</p>
<div class="fb-stats">{stats}</div>
<div class="links-row">{pills}</div>
<h3 class="side-h">Try an example</h3>
<div id="fa-examples"></div>
<details class="params"><summary>Generation settings</summary><div id="fa-params"></div></details>
</section>
<section class="panel" aria-label="Chat">
<div class="panel-top"><span id="fa-status" data-state="connecting"><i></i><span>Connecting…</span></span><button type="button" class="ghost" id="fa-clear">New chat</button></div>
<div id="fa-log" aria-live="polite"></div>
<div class="composer">
{attach}
<textarea id="fa-input" rows="1" placeholder="{e(c["placeholder"])}"></textarea>
<button type="button" class="icon-btn" id="fa-send" aria-label="Send">{ICON_SEND}</button>
<button type="button" class="icon-btn" id="fa-stop" aria-label="Stop" hidden>{ICON_STOP}</button>
<div id="fa-note" role="status"></div>
</div>
<p class="hint">{c["hint_html"]}</p>
</section>
</main>
<section class="wrap credits-sec reveal">
<span class="kicker">People &amp; partners</span>
<h2>Built together</h2>
<p class="sub">{c["orgs_sub_html"]}</p>
<div class="orgs">{orgs}</div>
<p class="disclaimer">{c["disclaimer"]}</p>
</section>
<footer class="footer wrap">
<span class="brand"><img src="{asset("finai-logo.png")}" alt="">The Fin AI</span>
<nav><a href="https://github.com/The-FinAI" target="_blank" rel="noreferrer">GitHub</a><a href="https://huggingface.co/TheFinAI" target="_blank" rel="noreferrer">Hugging Face</a><a href="https://huggingface.co/collections/TheFinAI/the-fin-ai-leaderboards-6ac7154319e5aa2b52037515" target="_blank" rel="noreferrer">Leaderboards</a><a href="https://discord.gg/Wpt347rusk" target="_blank" rel="noreferrer">Discord</a></nav>
<span class="copy">© 2026 The Fin AI · AI Readiness in Finance</span>
</footer>
</div>'''
def render(self):
return gr.HTML(self.body())
def blocks_kwargs(self):
kw = dict(title=self.cfg["page_title"], fill_width=True)
if not GRADIO6:
kw["head"] = self.head()
return kw
def launch_kwargs(self):
kw = {} if not GRADIO6 else dict(footer_links=[])
if GRADIO6:
kw["head"] = self.head()
kw["ssr_mode"] = False
return kw