Delete app.py
Browse files
app.py
DELETED
@@ -1,254 +0,0 @@
|
|
1 |
-
import gradio as gr
|
2 |
-
import os
|
3 |
-
import requests
|
4 |
-
import json
|
5 |
-
import asyncio
|
6 |
-
from crawl4ai import AsyncWebCrawler
|
7 |
-
|
8 |
-
# Configuration
|
9 |
-
SPACE_NAME = "Source Discovery Aid"
|
10 |
-
SPACE_DESCRIPTION = "An AI research assistant tailored for academic research and source discovery"
|
11 |
-
SYSTEM_PROMPT = """You are a research assistant that provides link-grounded information through Crawl4AI web fetching. This assistant is designed for students and researchers conducting academic inquiry. Your main responsibilities include: analyzing academic sources, fact-checking claims with evidence, providing properly cited research summaries, and helping users navigate scholarly information. Ground all responses in provided URL contexts and any additional URLs you're instructed to fetch. Never rely on memory for factual claims."""
|
12 |
-
MODEL = "google/gemma-3-27b-it"
|
13 |
-
GROUNDING_URLS = []
|
14 |
-
ACCESS_CODE = "TLC"
|
15 |
-
ENABLE_DYNAMIC_URLS = True
|
16 |
-
ENABLE_VECTOR_RAG = False
|
17 |
-
RAG_DATA = null
|
18 |
-
|
19 |
-
# Get API key from environment - customizable variable name
|
20 |
-
API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
21 |
-
|
22 |
-
async def fetch_url_content_async(url, crawler):
|
23 |
-
"""Fetch and extract text content from a URL using Crawl4AI"""
|
24 |
-
try:
|
25 |
-
result = await crawler.arun(
|
26 |
-
url=url,
|
27 |
-
bypass_cache=True,
|
28 |
-
word_count_threshold=10,
|
29 |
-
excluded_tags=['script', 'style', 'nav', 'header', 'footer'],
|
30 |
-
remove_overlay_elements=True
|
31 |
-
)
|
32 |
-
|
33 |
-
if result.success:
|
34 |
-
content = result.markdown or result.cleaned_html or ""
|
35 |
-
# Truncate to ~4000 characters
|
36 |
-
if len(content) > 4000:
|
37 |
-
content = content[:4000] + "..."
|
38 |
-
return content
|
39 |
-
else:
|
40 |
-
return f"Error fetching {url}: Failed to retrieve content"
|
41 |
-
except Exception as e:
|
42 |
-
return f"Error fetching {url}: {str(e)}"
|
43 |
-
|
44 |
-
def fetch_url_content(url):
|
45 |
-
"""Synchronous wrapper for URL fetching"""
|
46 |
-
async def fetch():
|
47 |
-
async with AsyncWebCrawler(verbose=False) as crawler:
|
48 |
-
return await fetch_url_content_async(url, crawler)
|
49 |
-
|
50 |
-
try:
|
51 |
-
return asyncio.run(fetch())
|
52 |
-
except Exception as e:
|
53 |
-
return f"Error fetching {url}: {str(e)}"
|
54 |
-
|
55 |
-
# Global cache for URL content to avoid re-crawling in generated spaces
|
56 |
-
_url_content_cache = {}
|
57 |
-
|
58 |
-
def get_grounding_context():
|
59 |
-
"""Fetch context from grounding URLs with caching"""
|
60 |
-
if not GROUNDING_URLS:
|
61 |
-
return ""
|
62 |
-
|
63 |
-
# Create cache key from URLs
|
64 |
-
cache_key = tuple(sorted([url for url in GROUNDING_URLS if url and url.strip()]))
|
65 |
-
|
66 |
-
# Check cache first
|
67 |
-
if cache_key in _url_content_cache:
|
68 |
-
return _url_content_cache[cache_key]
|
69 |
-
|
70 |
-
context_parts = []
|
71 |
-
for i, url in enumerate(GROUNDING_URLS, 1):
|
72 |
-
if url.strip():
|
73 |
-
content = fetch_url_content(url.strip())
|
74 |
-
context_parts.append(f"Context from URL {i} ({url}):\n{content}")
|
75 |
-
|
76 |
-
if context_parts:
|
77 |
-
result = "\n\n" + "\n\n".join(context_parts) + "\n\n"
|
78 |
-
else:
|
79 |
-
result = ""
|
80 |
-
|
81 |
-
# Cache the result
|
82 |
-
_url_content_cache[cache_key] = result
|
83 |
-
return result
|
84 |
-
|
85 |
-
import re
|
86 |
-
|
87 |
-
def extract_urls_from_text(text):
|
88 |
-
"""Extract URLs from text using regex"""
|
89 |
-
url_pattern = r'https?://[^\s<>"{}|\^`\[\]"]+'
|
90 |
-
return re.findall(url_pattern, text)
|
91 |
-
|
92 |
-
# Initialize RAG context if enabled
|
93 |
-
if ENABLE_VECTOR_RAG and RAG_DATA:
|
94 |
-
try:
|
95 |
-
import faiss
|
96 |
-
import numpy as np
|
97 |
-
import base64
|
98 |
-
|
99 |
-
class SimpleRAGContext:
|
100 |
-
def __init__(self, rag_data):
|
101 |
-
# Deserialize FAISS index
|
102 |
-
index_bytes = base64.b64decode(rag_data['index_base64'])
|
103 |
-
self.index = faiss.deserialize_index(index_bytes)
|
104 |
-
|
105 |
-
# Restore chunks and mappings
|
106 |
-
self.chunks = rag_data['chunks']
|
107 |
-
self.chunk_ids = rag_data['chunk_ids']
|
108 |
-
|
109 |
-
def get_context(self, query, max_chunks=3):
|
110 |
-
"""Get relevant context - simplified version"""
|
111 |
-
# In production, you'd compute query embedding here
|
112 |
-
# For now, return a simple message
|
113 |
-
return "\n\n[RAG context would be retrieved here based on similarity search]\n\n"
|
114 |
-
|
115 |
-
rag_context_provider = SimpleRAGContext(RAG_DATA)
|
116 |
-
except Exception as e:
|
117 |
-
print(f"Failed to initialize RAG: {e}")
|
118 |
-
rag_context_provider = None
|
119 |
-
else:
|
120 |
-
rag_context_provider = None
|
121 |
-
|
122 |
-
def generate_response(message, history):
|
123 |
-
"""Generate response using OpenRouter API"""
|
124 |
-
|
125 |
-
if not API_KEY:
|
126 |
-
return "Please set your OPENROUTER_API_KEY in the Space settings."
|
127 |
-
|
128 |
-
# Get grounding context
|
129 |
-
grounding_context = get_grounding_context()
|
130 |
-
|
131 |
-
# Add RAG context if available
|
132 |
-
if ENABLE_VECTOR_RAG and rag_context_provider:
|
133 |
-
rag_context = rag_context_provider.get_context(message)
|
134 |
-
if rag_context:
|
135 |
-
grounding_context += rag_context
|
136 |
-
|
137 |
-
# If dynamic URLs are enabled, check message for URLs to fetch
|
138 |
-
if ENABLE_DYNAMIC_URLS:
|
139 |
-
urls_in_message = extract_urls_from_text(message)
|
140 |
-
if urls_in_message:
|
141 |
-
# Fetch content from URLs mentioned in the message
|
142 |
-
dynamic_context_parts = []
|
143 |
-
for url in urls_in_message[:3]: # Limit to 3 URLs per message
|
144 |
-
content = fetch_url_content(url)
|
145 |
-
dynamic_context_parts.append(f"\n\nDynamic context from {url}:\n{content}")
|
146 |
-
if dynamic_context_parts:
|
147 |
-
grounding_context += "\n".join(dynamic_context_parts)
|
148 |
-
|
149 |
-
# Build enhanced system prompt with grounding context
|
150 |
-
enhanced_system_prompt = SYSTEM_PROMPT + grounding_context
|
151 |
-
|
152 |
-
# Build messages array for the API
|
153 |
-
messages = [{"role": "system", "content": enhanced_system_prompt}]
|
154 |
-
|
155 |
-
# Add conversation history - compatible with Gradio 5.x format
|
156 |
-
for chat in history:
|
157 |
-
if isinstance(chat, dict):
|
158 |
-
# New format: {"role": "user", "content": "..."} or {"role": "assistant", "content": "..."}
|
159 |
-
messages.append(chat)
|
160 |
-
else:
|
161 |
-
# Legacy format: ("user msg", "bot msg")
|
162 |
-
user_msg, bot_msg = chat
|
163 |
-
messages.append({"role": "user", "content": user_msg})
|
164 |
-
if bot_msg:
|
165 |
-
messages.append({"role": "assistant", "content": bot_msg})
|
166 |
-
|
167 |
-
# Add current message
|
168 |
-
messages.append({"role": "user", "content": message})
|
169 |
-
|
170 |
-
# Make API request
|
171 |
-
try:
|
172 |
-
response = requests.post(
|
173 |
-
url="https://openrouter.ai/api/v1/chat/completions",
|
174 |
-
headers={
|
175 |
-
"Authorization": f"Bearer {API_KEY}",
|
176 |
-
"Content-Type": "application/json"
|
177 |
-
},
|
178 |
-
json={
|
179 |
-
"model": MODEL,
|
180 |
-
"messages": messages,
|
181 |
-
"temperature": 0.7,
|
182 |
-
"max_tokens": 500
|
183 |
-
}
|
184 |
-
)
|
185 |
-
|
186 |
-
if response.status_code == 200:
|
187 |
-
return response.json()['choices'][0]['message']['content']
|
188 |
-
else:
|
189 |
-
return f"Error: {response.status_code} - {response.text}"
|
190 |
-
|
191 |
-
except Exception as e:
|
192 |
-
return f"Error: {str(e)}"
|
193 |
-
|
194 |
-
# Access code verification
|
195 |
-
access_granted = gr.State(False)
|
196 |
-
|
197 |
-
def verify_access_code(code):
|
198 |
-
"""Verify the access code"""
|
199 |
-
if not ACCESS_CODE:
|
200 |
-
return gr.update(visible=False), gr.update(visible=True), True
|
201 |
-
|
202 |
-
if code == ACCESS_CODE:
|
203 |
-
return gr.update(visible=False), gr.update(visible=True), True
|
204 |
-
else:
|
205 |
-
return gr.update(visible=True, value="❌ Incorrect access code. Please try again."), gr.update(visible=False), False
|
206 |
-
|
207 |
-
def protected_generate_response(message, history, access_state):
|
208 |
-
"""Protected response function that checks access"""
|
209 |
-
if not access_state:
|
210 |
-
return "Please enter the access code to continue."
|
211 |
-
return generate_response(message, history)
|
212 |
-
|
213 |
-
# Create interface with access code protection
|
214 |
-
with gr.Blocks(title=SPACE_NAME) as demo:
|
215 |
-
gr.Markdown(f"# {SPACE_NAME}")
|
216 |
-
gr.Markdown(SPACE_DESCRIPTION)
|
217 |
-
|
218 |
-
# Access code section (shown only if ACCESS_CODE is set)
|
219 |
-
with gr.Column(visible=bool(ACCESS_CODE)) as access_section:
|
220 |
-
gr.Markdown("### 🔐 Access Required")
|
221 |
-
gr.Markdown("Please enter the access code provided by your instructor:")
|
222 |
-
|
223 |
-
access_input = gr.Textbox(
|
224 |
-
label="Access Code",
|
225 |
-
placeholder="Enter access code...",
|
226 |
-
type="password"
|
227 |
-
)
|
228 |
-
access_btn = gr.Button("Submit", variant="primary")
|
229 |
-
access_error = gr.Markdown(visible=False)
|
230 |
-
|
231 |
-
# Main chat interface (hidden until access granted)
|
232 |
-
with gr.Column(visible=not bool(ACCESS_CODE)) as chat_section:
|
233 |
-
chat_interface = gr.ChatInterface(
|
234 |
-
fn=lambda msg, hist: protected_generate_response(msg, hist, access_granted.value),
|
235 |
-
title="", # Title already shown above
|
236 |
-
description="", # Description already shown above
|
237 |
-
examples=["Can you find an article on social media and education for me?", "Return a list of academic databases that I can use", "Check the formatting of my bibliographic entries"]
|
238 |
-
)
|
239 |
-
|
240 |
-
# Connect access verification
|
241 |
-
if ACCESS_CODE:
|
242 |
-
access_btn.click(
|
243 |
-
verify_access_code,
|
244 |
-
inputs=[access_input],
|
245 |
-
outputs=[access_error, chat_section, access_granted]
|
246 |
-
)
|
247 |
-
access_input.submit(
|
248 |
-
verify_access_code,
|
249 |
-
inputs=[access_input],
|
250 |
-
outputs=[access_error, chat_section, access_granted]
|
251 |
-
)
|
252 |
-
|
253 |
-
if __name__ == "__main__":
|
254 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|