OpenMark / openmark /ui /app.py
codingwithadi's picture
Show landing page when credentials not configured
530fa5d verified
Raw
History Blame Contribute Delete
8.17 kB
"""
OpenMark Gradio UI β€” 3 tabs:
1. Chat β€” talk to the LangGraph agent
2. Search β€” instant semantic search with filters
3. Stats β€” knowledge base overview
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
import gradio as gr
from openmark import config
# Load once at startup β€” fail gracefully if credentials are not configured
print("Loading OpenMark...")
_embedder = None
_agent = None
_setup_error = None
try:
from openmark.embeddings.factory import get_embedder
from openmark.agent.graph import build_agent, ask
from openmark.stores import chroma as chroma_store
from openmark.stores import neo4j_store
_embedder = get_embedder()
_agent = build_agent()
print("OpenMark ready.")
except Exception as e:
_setup_error = str(e)
print(f"OpenMark setup incomplete: {e}")
# ── Chat tab ──────────────────────────────────────────────────
_NOT_READY = (
"## Setup required\n\n"
"This Space is a **demo shell** β€” it requires your own credentials to run.\n\n"
"See the [GitHub repo](https://github.com/OthmanAdi/OpenMark) for full setup instructions."
)
def chat_fn(message: str, history: list, thread_id: str):
if _agent is None:
history.append((message, _NOT_READY))
return history, ""
if not message.strip():
return history, ""
response = ask(_agent, message, thread_id=thread_id or "default")
history.append((message, response))
return history, ""
# ── Search tab ────────────────────────────────────────────────
def search_fn(query: str, category: str, min_score: float, n_results: int):
if _embedder is None:
return _NOT_READY
if not query.strip():
return "Enter a search query."
cat = category if category != "All" else None
ms = min_score if min_score > 0 else None
results = chroma_store.search(
query, _embedder, n=int(n_results),
category=cat, min_score=ms,
)
if not results:
return "No results found."
lines = []
for r in results:
lines.append(
f"**{r['rank']}. {r['title'] or r['url']}**\n"
f"πŸ”— {r['url']}\n"
f"πŸ“ {r['category']} | πŸ“Œ {', '.join(t for t in r['tags'] if t)} | "
f"⭐ {r['score']} | 🎯 {r['similarity']:.3f} similarity\n"
)
return "\n---\n".join(lines)
# ── Stats tab ─────────────────────────────────────────────────
def stats_fn():
if _embedder is None:
return _NOT_READY
chroma = chroma_store.get_stats()
neo4j = neo4j_store.get_stats()
# Category breakdown from Neo4j
cat_rows = neo4j_store.query("""
MATCH (b:Bookmark)-[:IN_CATEGORY]->(c:Category)
RETURN c.name AS category, count(b) AS count
ORDER BY count DESC
""")
cat_lines = "\n".join(f" {r['category']:<35} {r['count']:>5}" for r in cat_rows)
# Top tags
tag_rows = neo4j_store.query("""
MATCH (b:Bookmark)-[:TAGGED]->(t:Tag)
RETURN t.name AS tag, count(b) AS count
ORDER BY count DESC LIMIT 20
""")
tag_lines = ", ".join(f"{r['tag']} ({r['count']})" for r in tag_rows)
return (
f"## OpenMark Knowledge Base\n\n"
f"**ChromaDB vectors:** {chroma.get('total', 0)}\n"
f"**Neo4j bookmarks:** {neo4j.get('bookmarks', 0)}\n"
f"**Neo4j tags:** {neo4j.get('tags', 0)}\n"
f"**Neo4j categories:** {neo4j.get('categories', 0)}\n\n"
f"### By Category\n```\n{cat_lines}\n```\n\n"
f"### Top Tags\n{tag_lines}"
)
# ── Build UI ──────────────────────────────────────────────────
def build_ui():
with gr.Blocks(title="OpenMark") as app:
gr.Markdown("# OpenMark β€” Your Personal Knowledge Graph")
if _setup_error:
# No credentials β€” show landing page only
gr.Markdown(
"**8,000+ bookmarks, LinkedIn saves, and YouTube videos** indexed with "
"[pplx-embed](https://huggingface.co/collections/perplexity-ai/pplx-embed), "
"searchable with ChromaDB and Neo4j, queryable via a LangGraph agent.\n\n"
"Built by [Ahmad Othman Ammar Adi](https://github.com/OthmanAdi) Β· "
"[GitHub](https://github.com/OthmanAdi/OpenMark) Β· "
"[Dataset](https://huggingface.co/datasets/codingwithadi/openmark-bookmarks)\n\n"
"---\n\n"
"## Run it yourself\n\n"
"This Space requires your own data and credentials. "
"Clone the repo and follow the setup guide:\n\n"
"```bash\n"
"git clone https://github.com/OthmanAdi/OpenMark.git\n"
"cd OpenMark\n"
"pip install -r requirements.txt\n"
"cp .env.example .env # add your keys\n"
"python scripts/ingest.py\n"
"python openmark/ui/app.py\n"
"```"
)
else:
categories = ["All"] + config.CATEGORIES
with gr.Tabs():
# Tab 1: Chat
with gr.Tab("Chat"):
thread = gr.Textbox(value="default", label="Session ID", scale=1)
chatbot = gr.Chatbot(height=500)
msg_box = gr.Textbox(
placeholder="Ask anything about your saved bookmarks...",
label="Message", lines=2,
)
send_btn = gr.Button("Send", variant="primary")
send_btn.click(
chat_fn,
inputs=[msg_box, chatbot, thread],
outputs=[chatbot, msg_box],
)
msg_box.submit(
chat_fn,
inputs=[msg_box, chatbot, thread],
outputs=[chatbot, msg_box],
)
# Tab 2: Search
with gr.Tab("Search"):
with gr.Row():
q_input = gr.Textbox(placeholder="Search your knowledge base...", label="Query", scale=3)
cat_input = gr.Dropdown(categories, value="All", label="Category")
with gr.Row():
score_input = gr.Slider(0, 10, value=0, step=1, label="Min Quality Score")
n_input = gr.Slider(5, 50, value=10, step=5, label="Results")
search_btn = gr.Button("Search", variant="primary")
search_output = gr.Markdown()
search_btn.click(
search_fn,
inputs=[q_input, cat_input, score_input, n_input],
outputs=search_output,
)
q_input.submit(
search_fn,
inputs=[q_input, cat_input, score_input, n_input],
outputs=search_output,
)
# Tab 3: Stats
with gr.Tab("Stats"):
refresh_btn = gr.Button("Refresh Stats")
stats_output = gr.Markdown()
refresh_btn.click(stats_fn, outputs=stats_output)
app.load(stats_fn, outputs=stats_output)
return app
if __name__ == "__main__":
ui = build_ui()
ui.launch(server_name="0.0.0.0", server_port=7860, share=False)