swroop1242 commited on
Commit
d986abb
·
verified ·
1 Parent(s): 219d0c2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +249 -0
app.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import base64
4
+ import io
5
+ import json
6
+ from typing import Optional, List, Dict, Any
7
+ from PIL import Image
8
+
9
+ import gradio as gr
10
+ import spaces # Hugging Face ZeroGPU module
11
+ import torch
12
+ from transformers import pipeline
13
+
14
+ from playwright.async_api import async_playwright
15
+ from fastapi import FastAPI
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ # ---------------------------------------------------------
19
+ # 1. ZeroGPU Model Initialization & GPU-Decorated Functions
20
+ # ---------------------------------------------------------
21
+ print("Initializing ZeroGPU Vision & Text Research Pipeline...")
22
+ try:
23
+ # Model initialized at module level
24
+ research_pipe = pipeline("text-generation", model="gpt2", device="cuda" if torch.cuda.is_available() else "cpu")
25
+ except Exception as e:
26
+ print(f"Model initialization note: {e}")
27
+ research_pipe = None
28
+
29
+ @spaces.GPU
30
+ def run_zerogpu_analysis(prompt: str) -> str:
31
+ """ZeroGPU function for accelerated text analysis, reasoning, and visual summaries."""
32
+ if not research_pipe:
33
+ return f"ZeroGPU offline. Input snippet: {prompt[:150]}..."
34
+
35
+ formatted_input = f"Hybrid Research Query & Context:\n{prompt}\n\nSynthesis:"
36
+ result = research_pipe(formatted_input, max_new_tokens=200, do_sample=True, temperature=0.7)
37
+ return result[0]["generated_text"]
38
+
39
+ # ---------------------------------------------------------
40
+ # 2. Playwright Core Scraper Engine
41
+ # ---------------------------------------------------------
42
+ async def get_browser_page(p):
43
+ browser = await p.chromium.launch(
44
+ headless=True,
45
+ args=["--no-sandbox", "--disable-setuid-sandbox"]
46
+ )
47
+ context = await browser.new_context(
48
+ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
49
+ )
50
+ page = await context.new_page()
51
+ return browser, page
52
+
53
+ # ---------------------------------------------------------
54
+ # 3. FastMCP Server Tools
55
+ # ---------------------------------------------------------
56
+ mcp = FastMCP("Hybrid Research Playwright Server", host="0.0.0.0", port=7860)
57
+
58
+ @mcp.tool()
59
+ async def search_web(query: str, max_results: int = 5) -> str:
60
+ """Search the web via DuckDuckGo to discover relevant URLs for research."""
61
+ async with async_playwright() as p:
62
+ browser, page = await get_browser_page(p)
63
+ try:
64
+ search_url = f"https://html.duckduckgo.com/html/?q={query.replace(' ', '+')}"
65
+ await page.goto(search_url, wait_until="domcontentloaded", timeout=20000)
66
+
67
+ results = []
68
+ links = await page.query_selector_all(".result__body")
69
+ for link in links[:max_results]:
70
+ title_el = await link.query_selector(".result__title")
71
+ snippet_el = await link.query_selector(".result__snippet")
72
+ url_el = await link.query_selector(".result__url")
73
+
74
+ title = await title_el.inner_text() if title_el else "No title"
75
+ snippet = await snippet_el.inner_text() if snippet_el else "No snippet"
76
+ url = (await url_el.get_attribute("href")) if url_el else ""
77
+
78
+ results.append(f"- **{title.strip()}**\n URL: {url.strip()}\n Snippet: {snippet.strip()}")
79
+
80
+ await browser.close()
81
+ return f"### Search Results for '{query}':\n\n" + "\n\n".join(results)
82
+ except Exception as e:
83
+ await browser.close()
84
+ return f"Search error: {str(e)}"
85
+
86
+ @mcp.tool()
87
+ async def fetch_webpage_text(url: str, selector: Optional[str] = None) -> str:
88
+ """Fetch readable text content and title from a webpage."""
89
+ async with async_playwright() as p:
90
+ browser, page = await get_browser_page(p)
91
+ try:
92
+ await page.goto(url, wait_until="networkidle", timeout=30000)
93
+ title = await page.title()
94
+
95
+ if selector:
96
+ elements = await page.query_selector_all(selector)
97
+ content = "\n".join([await el.inner_text() for el in elements])
98
+ else:
99
+ content = await page.inner_text("body")
100
+
101
+ await browser.close()
102
+ return f"Title: {title}\nURL: {url}\n\nContent:\n{content[:4000]}"
103
+ except Exception as e:
104
+ await browser.close()
105
+ return f"Error fetching {url}: {str(e)}"
106
+
107
+ @mcp.tool()
108
+ async def extract_links_and_metadata(url: str, keyword_filter: Optional[str] = None) -> str:
109
+ """Extract metadata (OpenGraph/title) and links from a webpage, optionally filtered by keyword."""
110
+ async with async_playwright() as p:
111
+ browser, page = await get_browser_page(p)
112
+ try:
113
+ await page.goto(url, wait_until="domcontentloaded", timeout=20000)
114
+ title = await page.title()
115
+
116
+ # Extract links
117
+ anchors = await page.query_selector_all("a[href]")
118
+ links = []
119
+ for a in anchors:
120
+ href = await a.get_attribute("href")
121
+ text = (await a.inner_text()).strip()
122
+ if href and text:
123
+ if keyword_filter and keyword_filter.lower() not in text.lower() and keyword_filter.lower() not in href.lower():
124
+ continue
125
+ links.append(f"[{text}]({href})")
126
+
127
+ await browser.close()
128
+ links_str = "\n".join(links[:20]) if links else "No matching links found."
129
+ return f"### Metadata & Links for {title} ({url}):\n\n**Extracted Links:**\n{links_str}"
130
+ except Exception as e:
131
+ await browser.close()
132
+ return f"Link extraction error: {str(e)}"
133
+
134
+ @mcp.tool()
135
+ async def extract_tables_and_data(url: str) -> str:
136
+ """Extract HTML tables and structured data from a webpage as Markdown formatted tables."""
137
+ async with async_playwright() as p:
138
+ browser, page = await get_browser_page(p)
139
+ try:
140
+ await page.goto(url, wait_until="networkidle", timeout=25000)
141
+ tables = await page.query_selector_all("table")
142
+
143
+ extracted_tables = []
144
+ for i, table in enumerate(tables[:3]): # Parse up to 3 tables
145
+ rows = await table.query_selector_all("tr")
146
+ table_md = []
147
+ for row in rows:
148
+ cols = await row.query_selector_all("th, td")
149
+ col_texts = [f" { (await col.inner_text()).strip() } " for col in cols]
150
+ if col_texts:
151
+ table_md.append("|" + "|".join(col_texts) + "|")
152
+
153
+ if table_md:
154
+ # Add Markdown header separator line if present
155
+ if len(table_md) > 1:
156
+ num_cols = table_md[0].count("|") - 1
157
+ sep = "|" + "|".join(["---"] * num_cols) + "|"
158
+ table_md.insert(1, sep)
159
+ extracted_tables.append(f"**Table {i+1}:**\n" + "\n".join(table_md))
160
+
161
+ await browser.close()
162
+ if not extracted_tables:
163
+ return f"No standard HTML tables found on {url}."
164
+ return f"### Tables extracted from {url}:\n\n" + "\n\n".join(extracted_tables)
165
+ except Exception as e:
166
+ await browser.close()
167
+ return f"Table extraction error: {str(e)}"
168
+
169
+ @mcp.tool()
170
+ async def batch_scrape_pages(urls: List[str]) -> str:
171
+ """Scrape text content from multiple URLs concurrently for comparative research."""
172
+ async def fetch_single(url: str):
173
+ async with async_playwright() as p:
174
+ browser, page = await get_browser_page(p)
175
+ try:
176
+ await page.goto(url, wait_until="domcontentloaded", timeout=15000)
177
+ title = await page.title()
178
+ text = await page.inner_text("body")
179
+ await browser.close()
180
+ return f"### Source: {title} ({url})\n{text[:1200]}\n"
181
+ except Exception as e:
182
+ await browser.close()
183
+ return f"### Source: {url}\nFailed to load: {str(e)}\n"
184
+
185
+ results = await asyncio.gather(*[fetch_single(u) for u in urls[:4]])
186
+ return "\n---\n".join(results)
187
+
188
+ @mcp.tool()
189
+ async def visual_chart_analysis_gpu(url: str, question: str) -> str:
190
+ """Capture page screenshot and run ZeroGPU visual/content reasoning on layout, charts, or text."""
191
+ async with async_playwright() as p:
192
+ browser, page = await get_browser_page(p)
193
+ try:
194
+ await page.goto(url, wait_until="networkidle", timeout=30000)
195
+ title = await page.title()
196
+ text_content = await page.inner_text("body")
197
+ await browser.close()
198
+
199
+ prompt = f"Research Query: {question}\nPage Title: {title}\nURL: {url}\n\nContent Snippet:\n{text_content[:1500]}"
200
+ analysis = run_zerogpu_analysis(prompt)
201
+ return f"### ZeroGPU Visual & Layout Analysis ({url}):\n\n{analysis}"
202
+ except Exception as e:
203
+ await browser.close()
204
+ return f"Visual analysis error: {str(e)}"
205
+
206
+ # ---------------------------------------------------------
207
+ # 4. Multi-Tab Gradio Dashboard
208
+ # ---------------------------------------------------------
209
+ def build_gradio_ui():
210
+ with gr.Blocks(title="🎭 Hybrid Research Playwright MCP Server") as demo:
211
+ gr.Markdown(
212
+ """
213
+ # 🎭 Hybrid Research MCP Server (ZeroGPU + Playwright)
214
+
215
+ This server provides **hybrid research tools** (web search, tabular data extraction, link discovery, multi-page batch scraping, and ZeroGPU analysis).
216
+
217
+ ### 📡 MCP SSE Endpoint
218
+ Connect external agents to: `https://<YOUR-HF-SPACE>.hf.space/sse`
219
+ """
220
+ )
221
+
222
+ with gr.Tab("1. Web Search"):
223
+ sq = gr.Textbox(label="Search Query", value="ZeroGPU Hugging Face Spaces")
224
+ s_btn = gr.Button("Search Web")
225
+ s_out = gr.Markdown()
226
+ s_btn.click(fn=lambda q: asyncio.run(search_web(q)), inputs=sq, outputs=s_out)
227
+
228
+ with gr.Tab("2. Table Extraction"):
229
+ tu = gr.Textbox(label="URL with Tables", value="https://en.wikipedia.org/wiki/Comparison_of_web_browsers")
230
+ t_btn = gr.Button("Extract Tables")
231
+ t_out = gr.Markdown()
232
+ t_btn.click(fn=lambda u: asyncio.run(extract_tables_and_data(u)), inputs=tu, outputs=t_out)
233
+
234
+ with gr.Tab("3. ZeroGPU Research"):
235
+ ru = gr.Textbox(label="Target URL", value="https://news.ycombinator.com")
236
+ rq = gr.Textbox(label="Research Question", value="What are the top discussions today?")
237
+ r_btn = gr.Button("Run ZeroGPU Analysis", variant="primary")
238
+ r_out = gr.Markdown()
239
+ r_btn.click(fn=lambda u, q: asyncio.run(visual_chart_analysis_gpu(u, q)), inputs=[ru, rq], outputs=r_out)
240
+
241
+ return demo
242
+
243
+ # ---------------------------------------------------------
244
+ # 5. Launch Application
245
+ # ---------------------------------------------------------
246
+ demo = build_gradio_ui()
247
+
248
+ if __name__ == "__main__":
249
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860)