Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +217 -254
src/streamlit_app.py
CHANGED
@@ -4,8 +4,7 @@ import json
|
|
4 |
from typing import Dict, List, Optional
|
5 |
import re
|
6 |
from urllib.parse import quote
|
7 |
-
import
|
8 |
-
import aiohttp
|
9 |
|
10 |
# Configure page
|
11 |
st.set_page_config(
|
@@ -41,48 +40,38 @@ class OllamaLLM:
|
|
41 |
def check_connection(self) -> bool:
|
42 |
"""Check if Ollama is running"""
|
43 |
try:
|
44 |
-
response = requests.get(self.models_url, timeout=
|
45 |
return response.status_code == 200
|
46 |
-
except:
|
47 |
return False
|
48 |
|
49 |
def get_available_models(self) -> List[str]:
|
50 |
"""Get list of available models"""
|
51 |
try:
|
52 |
-
response = requests.get(self.models_url, timeout=
|
53 |
if response.status_code == 200:
|
54 |
data = response.json()
|
55 |
-
|
|
|
56 |
return []
|
57 |
-
except:
|
58 |
return []
|
59 |
|
60 |
def generate_summary(self, text: str, model: str = "llama3.2", language: str = "English",
|
61 |
summary_type: str = "concise") -> str:
|
62 |
"""Generate AI summary using local LLM"""
|
63 |
try:
|
64 |
-
#
|
|
|
|
|
|
|
|
|
65 |
if summary_type == "concise":
|
66 |
-
prompt = f"
|
67 |
-
Make it clear and informative:
|
68 |
-
|
69 |
-
{text}
|
70 |
-
|
71 |
-
Summary:"""
|
72 |
elif summary_type == "detailed":
|
73 |
-
prompt = f"
|
74 |
-
Include key points, important facts, and context:
|
75 |
-
|
76 |
-
{text}
|
77 |
-
|
78 |
-
Detailed Summary:"""
|
79 |
else: # explanatory
|
80 |
-
prompt = f"
|
81 |
-
easy-to-understand way as if explaining to someone unfamiliar with the topic:
|
82 |
-
|
83 |
-
{text}
|
84 |
-
|
85 |
-
Explanation:"""
|
86 |
|
87 |
# Request to Ollama
|
88 |
payload = {
|
@@ -91,7 +80,7 @@ class OllamaLLM:
|
|
91 |
"stream": False,
|
92 |
"options": {
|
93 |
"temperature": 0.7,
|
94 |
-
"num_predict":
|
95 |
}
|
96 |
}
|
97 |
|
@@ -99,22 +88,20 @@ class OllamaLLM:
|
|
99 |
|
100 |
if response.status_code == 200:
|
101 |
data = response.json()
|
102 |
-
|
|
|
103 |
else:
|
104 |
-
return f"Error: {response.status_code}"
|
105 |
|
|
|
|
|
106 |
except Exception as e:
|
107 |
-
return f"
|
108 |
|
109 |
def translate_text(self, text: str, target_language: str, model: str = "llama3.2") -> str:
|
110 |
"""Translate text using local LLM"""
|
111 |
try:
|
112 |
-
prompt = f"
|
113 |
-
Provide only the translation, no additional text:
|
114 |
-
|
115 |
-
{text}
|
116 |
-
|
117 |
-
Translation:"""
|
118 |
|
119 |
payload = {
|
120 |
"model": model,
|
@@ -122,7 +109,7 @@ class OllamaLLM:
|
|
122 |
"stream": False,
|
123 |
"options": {
|
124 |
"temperature": 0.3,
|
125 |
-
"num_predict":
|
126 |
}
|
127 |
}
|
128 |
|
@@ -130,11 +117,12 @@ class OllamaLLM:
|
|
130 |
|
131 |
if response.status_code == 200:
|
132 |
data = response.json()
|
133 |
-
|
|
|
134 |
else:
|
135 |
-
return text
|
136 |
|
137 |
-
except Exception
|
138 |
return text
|
139 |
|
140 |
class WikipediaAPI:
|
@@ -160,6 +148,7 @@ class WikipediaAPI:
|
|
160 |
|
161 |
data = response.json()
|
162 |
return data.get("query", {}).get("search", [])
|
|
|
163 |
except Exception as e:
|
164 |
st.error(f"Search error: {str(e)}")
|
165 |
return []
|
@@ -174,11 +163,11 @@ class WikipediaAPI:
|
|
174 |
response.raise_for_status()
|
175 |
|
176 |
return response.json()
|
|
|
177 |
except Exception as e:
|
178 |
-
st.error(f"Summary error: {str(e)}")
|
179 |
return None
|
180 |
|
181 |
-
def get_page_content(self, title: str, lang: str = "en", char_limit: int =
|
182 |
"""Get page content sections"""
|
183 |
try:
|
184 |
params = {
|
@@ -204,23 +193,34 @@ class WikipediaAPI:
|
|
204 |
return page_data["extract"]
|
205 |
|
206 |
return None
|
207 |
-
|
208 |
-
|
209 |
return None
|
210 |
|
211 |
def clean_html(text: str) -> str:
|
212 |
"""Remove HTML tags from text"""
|
|
|
|
|
213 |
clean = re.compile('<.*?>')
|
214 |
return re.sub(clean, '', text)
|
215 |
|
216 |
def simple_summarize(text: str, max_sentences: int = 3) -> str:
|
217 |
"""Fallback simple text summarization"""
|
|
|
|
|
|
|
218 |
sentences = text.split('. ')
|
|
|
|
|
|
|
219 |
summary_sentences = sentences[:max_sentences]
|
220 |
-
|
|
|
|
|
|
|
221 |
|
222 |
def main():
|
223 |
-
# Custom CSS
|
224 |
st.markdown("""
|
225 |
<style>
|
226 |
.main-header {
|
@@ -230,13 +230,13 @@ def main():
|
|
230 |
}
|
231 |
.search-container {
|
232 |
background-color: #f8f9fa;
|
233 |
-
padding:
|
234 |
border-radius: 10px;
|
235 |
margin-bottom: 1rem;
|
236 |
}
|
237 |
.result-card {
|
238 |
background-color: white;
|
239 |
-
padding:
|
240 |
border-radius: 8px;
|
241 |
border: 1px solid #dee2e6;
|
242 |
margin-bottom: 1rem;
|
@@ -245,13 +245,14 @@ def main():
|
|
245 |
.article-title {
|
246 |
color: #007bff;
|
247 |
font-weight: bold;
|
|
|
248 |
margin-bottom: 0.5rem;
|
249 |
}
|
250 |
-
.
|
251 |
-
padding: 0.
|
252 |
-
border-radius:
|
253 |
margin-bottom: 1rem;
|
254 |
-
font-
|
255 |
}
|
256 |
.status-connected {
|
257 |
background-color: #d4edda;
|
@@ -270,51 +271,51 @@ def main():
|
|
270 |
border-left: 4px solid #007bff;
|
271 |
margin: 1rem 0;
|
272 |
}
|
273 |
-
@media (max-width: 768px) {
|
274 |
-
.stSelectbox, .stTextInput {
|
275 |
-
font-size: 16px;
|
276 |
-
}
|
277 |
-
}
|
278 |
</style>
|
279 |
""", unsafe_allow_html=True)
|
280 |
|
281 |
# Header
|
282 |
-
st.markdown("<h1 class='main-header'>π€ WikiBot - AI-Powered
|
283 |
-
st.markdown("<p style='text-align: center; color: #666;'>
|
|
|
|
|
|
|
|
|
284 |
|
285 |
# Initialize APIs
|
286 |
wiki_api = WikipediaAPI()
|
287 |
llm = OllamaLLM()
|
288 |
|
289 |
# Check LLM connection
|
290 |
-
|
291 |
-
|
|
|
292 |
|
293 |
-
#
|
294 |
if llm_connected:
|
295 |
st.markdown(f"""
|
296 |
-
<div class='
|
297 |
-
β
|
298 |
</div>
|
299 |
""", unsafe_allow_html=True)
|
300 |
else:
|
301 |
st.markdown("""
|
302 |
-
<div class='
|
303 |
-
β
|
304 |
</div>
|
305 |
""", unsafe_allow_html=True)
|
306 |
-
st.info("To enable AI features: Install Ollama from https://ollama.ai and run `ollama serve`")
|
307 |
|
308 |
-
#
|
309 |
st.markdown("<div class='search-container'>", unsafe_allow_html=True)
|
310 |
|
|
|
311 |
col1, col2 = st.columns([3, 1])
|
312 |
|
313 |
with col1:
|
314 |
query = st.text_input(
|
315 |
-
"π Search
|
316 |
-
placeholder="e.g., '
|
317 |
-
help="Enter your search query
|
318 |
)
|
319 |
|
320 |
with col2:
|
@@ -324,218 +325,180 @@ def main():
|
|
324 |
index=0
|
325 |
)
|
326 |
|
327 |
-
#
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
with col2:
|
335 |
-
if llm_connected:
|
336 |
-
summary_mode = st.selectbox(
|
337 |
-
"AI Summary Type",
|
338 |
-
["concise", "detailed", "explanatory"],
|
339 |
-
index=0
|
340 |
-
)
|
341 |
-
else:
|
342 |
-
summary_mode = st.selectbox(
|
343 |
-
"Summary Type",
|
344 |
-
["short", "medium", "long"],
|
345 |
-
index=1
|
346 |
-
)
|
347 |
-
|
348 |
-
with col3:
|
349 |
-
if llm_connected and available_models:
|
350 |
-
selected_model = st.selectbox(
|
351 |
-
"LLM Model",
|
352 |
-
options=available_models,
|
353 |
-
index=0
|
354 |
-
)
|
355 |
-
else:
|
356 |
-
st.info("No models available")
|
357 |
-
selected_model = None
|
358 |
-
|
359 |
-
# Translation options
|
360 |
if llm_connected:
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
371 |
|
372 |
st.markdown("</div>", unsafe_allow_html=True)
|
373 |
|
374 |
# Search button
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
397 |
|
398 |
-
if
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
content,
|
403 |
-
selected_model,
|
404 |
-
selected_lang,
|
405 |
-
summary_mode
|
406 |
-
)
|
407 |
|
408 |
-
|
409 |
-
|
410 |
-
st.
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
with st.spinner(f"Translating to {target_lang}..."):
|
416 |
-
translated = llm.translate_text(ai_summary, target_lang, selected_model)
|
417 |
-
if translated != ai_summary:
|
418 |
-
st.markdown(f"**π Translation to {target_lang}:**")
|
419 |
-
st.write(translated)
|
420 |
-
|
421 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
422 |
-
else:
|
423 |
-
# Fallback to simple summary
|
424 |
-
st.warning("AI summary failed, using fallback")
|
425 |
-
fallback_summary = simple_summarize(content, 3)
|
426 |
-
st.write(fallback_summary)
|
427 |
-
|
428 |
-
elif summary_data:
|
429 |
-
# Standard Wikipedia summary
|
430 |
-
summary_text = summary_data.get("extract", "")
|
431 |
-
if not llm_connected:
|
432 |
-
if summary_mode == "short":
|
433 |
-
summary_text = simple_summarize(summary_text, 2)
|
434 |
-
elif summary_mode == "medium":
|
435 |
-
summary_text = simple_summarize(summary_text, 4)
|
436 |
|
437 |
-
st.
|
438 |
-
|
439 |
else:
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
height=300,
|
460 |
-
key=f"content_{idx}"
|
461 |
-
)
|
462 |
-
else:
|
463 |
-
st.warning("Detailed content not available")
|
464 |
-
|
465 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
466 |
-
st.markdown("---")
|
467 |
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
|
|
474 |
|
475 |
-
|
|
|
|
|
|
|
476 |
st.markdown("---")
|
477 |
col1, col2, col3, col4 = st.columns(4)
|
478 |
|
479 |
with col1:
|
480 |
st.metric("π Languages", len(LANGUAGES))
|
481 |
-
|
482 |
with col2:
|
483 |
-
st.metric("π€ LLM
|
484 |
-
|
485 |
with col3:
|
486 |
st.metric("π Models", len(available_models))
|
487 |
-
|
488 |
with col4:
|
489 |
-
st.metric("
|
490 |
|
491 |
-
# Setup
|
492 |
-
with st.expander("π οΈ Setup
|
493 |
st.markdown("""
|
494 |
-
###
|
495 |
|
496 |
-
1.
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
# Windows - Download from https://ollama.ai
|
502 |
-
```
|
503 |
|
504 |
-
|
505 |
-
|
506 |
-
ollama pull llama3.2
|
507 |
-
# or
|
508 |
-
ollama pull mistral
|
509 |
-
ollama pull codellama
|
510 |
-
```
|
511 |
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
|
517 |
-
|
|
|
|
|
|
|
518 |
|
519 |
-
|
520 |
-
- **llama3.2** - Great for general summarization
|
521 |
-
- **mistral** - Fast and efficient
|
522 |
-
- **codellama** - Good for technical content
|
523 |
-
""")
|
524 |
-
|
525 |
-
# Usage examples
|
526 |
-
with st.expander("π‘ Usage Examples"):
|
527 |
-
st.markdown("""
|
528 |
-
**Try these example queries:**
|
529 |
-
- "Explain Kargil War in Telugu" β AI generates Telugu explanation
|
530 |
-
- "Machine Learning" β Detailed AI summary with translation
|
531 |
-
- "Climate Change" β AI explanatory summary
|
532 |
-
- "Quantum Computing" β Technical AI analysis
|
533 |
|
534 |
-
|
535 |
-
-
|
536 |
-
-
|
537 |
-
-
|
538 |
-
- π Enhanced content understanding
|
539 |
""")
|
540 |
|
541 |
if __name__ == "__main__":
|
|
|
4 |
from typing import Dict, List, Optional
|
5 |
import re
|
6 |
from urllib.parse import quote
|
7 |
+
import time
|
|
|
8 |
|
9 |
# Configure page
|
10 |
st.set_page_config(
|
|
|
40 |
def check_connection(self) -> bool:
|
41 |
"""Check if Ollama is running"""
|
42 |
try:
|
43 |
+
response = requests.get(self.models_url, timeout=3)
|
44 |
return response.status_code == 200
|
45 |
+
except Exception:
|
46 |
return False
|
47 |
|
48 |
def get_available_models(self) -> List[str]:
|
49 |
"""Get list of available models"""
|
50 |
try:
|
51 |
+
response = requests.get(self.models_url, timeout=5)
|
52 |
if response.status_code == 200:
|
53 |
data = response.json()
|
54 |
+
models = data.get("models", [])
|
55 |
+
return [model["name"] for model in models]
|
56 |
return []
|
57 |
+
except Exception:
|
58 |
return []
|
59 |
|
60 |
def generate_summary(self, text: str, model: str = "llama3.2", language: str = "English",
|
61 |
summary_type: str = "concise") -> str:
|
62 |
"""Generate AI summary using local LLM"""
|
63 |
try:
|
64 |
+
# Truncate text if too long
|
65 |
+
if len(text) > 2000:
|
66 |
+
text = text[:2000] + "..."
|
67 |
+
|
68 |
+
# Craft prompt based on summary type
|
69 |
if summary_type == "concise":
|
70 |
+
prompt = f"Summarize this Wikipedia content in {language} in 2-3 clear sentences:\n\n{text}\n\nSummary:"
|
|
|
|
|
|
|
|
|
|
|
71 |
elif summary_type == "detailed":
|
72 |
+
prompt = f"Provide a detailed summary of this Wikipedia content in {language}. Include key points and important facts:\n\n{text}\n\nDetailed Summary:"
|
|
|
|
|
|
|
|
|
|
|
73 |
else: # explanatory
|
74 |
+
prompt = f"Explain this Wikipedia content in {language} in simple terms that anyone can understand:\n\n{text}\n\nExplanation:"
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
# Request to Ollama
|
77 |
payload = {
|
|
|
80 |
"stream": False,
|
81 |
"options": {
|
82 |
"temperature": 0.7,
|
83 |
+
"num_predict": 300 if summary_type == "detailed" else 150
|
84 |
}
|
85 |
}
|
86 |
|
|
|
88 |
|
89 |
if response.status_code == 200:
|
90 |
data = response.json()
|
91 |
+
summary = data.get("response", "").strip()
|
92 |
+
return summary if summary else "No summary generated"
|
93 |
else:
|
94 |
+
return f"Error: Status {response.status_code}"
|
95 |
|
96 |
+
except requests.exceptions.Timeout:
|
97 |
+
return "Error: Request timeout - try a smaller text"
|
98 |
except Exception as e:
|
99 |
+
return f"Error: {str(e)}"
|
100 |
|
101 |
def translate_text(self, text: str, target_language: str, model: str = "llama3.2") -> str:
|
102 |
"""Translate text using local LLM"""
|
103 |
try:
|
104 |
+
prompt = f"Translate this text to {target_language}. Only provide the translation:\n\n{text}\n\nTranslation:"
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
payload = {
|
107 |
"model": model,
|
|
|
109 |
"stream": False,
|
110 |
"options": {
|
111 |
"temperature": 0.3,
|
112 |
+
"num_predict": 200
|
113 |
}
|
114 |
}
|
115 |
|
|
|
117 |
|
118 |
if response.status_code == 200:
|
119 |
data = response.json()
|
120 |
+
translation = data.get("response", "").strip()
|
121 |
+
return translation if translation else text
|
122 |
else:
|
123 |
+
return text
|
124 |
|
125 |
+
except Exception:
|
126 |
return text
|
127 |
|
128 |
class WikipediaAPI:
|
|
|
148 |
|
149 |
data = response.json()
|
150 |
return data.get("query", {}).get("search", [])
|
151 |
+
|
152 |
except Exception as e:
|
153 |
st.error(f"Search error: {str(e)}")
|
154 |
return []
|
|
|
163 |
response.raise_for_status()
|
164 |
|
165 |
return response.json()
|
166 |
+
|
167 |
except Exception as e:
|
|
|
168 |
return None
|
169 |
|
170 |
+
def get_page_content(self, title: str, lang: str = "en", char_limit: int = 2000) -> Optional[str]:
|
171 |
"""Get page content sections"""
|
172 |
try:
|
173 |
params = {
|
|
|
193 |
return page_data["extract"]
|
194 |
|
195 |
return None
|
196 |
+
|
197 |
+
except Exception:
|
198 |
return None
|
199 |
|
200 |
def clean_html(text: str) -> str:
|
201 |
"""Remove HTML tags from text"""
|
202 |
+
if not text:
|
203 |
+
return ""
|
204 |
clean = re.compile('<.*?>')
|
205 |
return re.sub(clean, '', text)
|
206 |
|
207 |
def simple_summarize(text: str, max_sentences: int = 3) -> str:
|
208 |
"""Fallback simple text summarization"""
|
209 |
+
if not text:
|
210 |
+
return "No content available"
|
211 |
+
|
212 |
sentences = text.split('. ')
|
213 |
+
if len(sentences) <= max_sentences:
|
214 |
+
return text
|
215 |
+
|
216 |
summary_sentences = sentences[:max_sentences]
|
217 |
+
result = '. '.join(summary_sentences)
|
218 |
+
if not result.endswith('.'):
|
219 |
+
result += '.'
|
220 |
+
return result
|
221 |
|
222 |
def main():
|
223 |
+
# Custom CSS
|
224 |
st.markdown("""
|
225 |
<style>
|
226 |
.main-header {
|
|
|
230 |
}
|
231 |
.search-container {
|
232 |
background-color: #f8f9fa;
|
233 |
+
padding: 1.5rem;
|
234 |
border-radius: 10px;
|
235 |
margin-bottom: 1rem;
|
236 |
}
|
237 |
.result-card {
|
238 |
background-color: white;
|
239 |
+
padding: 1.5rem;
|
240 |
border-radius: 8px;
|
241 |
border: 1px solid #dee2e6;
|
242 |
margin-bottom: 1rem;
|
|
|
245 |
.article-title {
|
246 |
color: #007bff;
|
247 |
font-weight: bold;
|
248 |
+
font-size: 1.2rem;
|
249 |
margin-bottom: 0.5rem;
|
250 |
}
|
251 |
+
.status-box {
|
252 |
+
padding: 0.8rem;
|
253 |
+
border-radius: 8px;
|
254 |
margin-bottom: 1rem;
|
255 |
+
font-weight: bold;
|
256 |
}
|
257 |
.status-connected {
|
258 |
background-color: #d4edda;
|
|
|
271 |
border-left: 4px solid #007bff;
|
272 |
margin: 1rem 0;
|
273 |
}
|
|
|
|
|
|
|
|
|
|
|
274 |
</style>
|
275 |
""", unsafe_allow_html=True)
|
276 |
|
277 |
# Header
|
278 |
+
st.markdown("<h1 class='main-header'>π€ WikiBot - AI-Powered Assistant</h1>", unsafe_allow_html=True)
|
279 |
+
st.markdown("<p style='text-align: center; color: #666;'>Wikipedia + Local LLM Intelligence</p>", unsafe_allow_html=True)
|
280 |
+
|
281 |
+
# Initialize session state
|
282 |
+
if 'search_results' not in st.session_state:
|
283 |
+
st.session_state.search_results = []
|
284 |
|
285 |
# Initialize APIs
|
286 |
wiki_api = WikipediaAPI()
|
287 |
llm = OllamaLLM()
|
288 |
|
289 |
# Check LLM connection
|
290 |
+
with st.spinner("Checking Ollama connection..."):
|
291 |
+
llm_connected = llm.check_connection()
|
292 |
+
available_models = llm.get_available_models() if llm_connected else []
|
293 |
|
294 |
+
# Status display
|
295 |
if llm_connected:
|
296 |
st.markdown(f"""
|
297 |
+
<div class='status-box status-connected'>
|
298 |
+
β
Ollama Connected - {len(available_models)} models available
|
299 |
</div>
|
300 |
""", unsafe_allow_html=True)
|
301 |
else:
|
302 |
st.markdown("""
|
303 |
+
<div class='status-box status-disconnected'>
|
304 |
+
β Ollama Offline - Basic mode only
|
305 |
</div>
|
306 |
""", unsafe_allow_html=True)
|
|
|
307 |
|
308 |
+
# Main search interface
|
309 |
st.markdown("<div class='search-container'>", unsafe_allow_html=True)
|
310 |
|
311 |
+
# Search inputs
|
312 |
col1, col2 = st.columns([3, 1])
|
313 |
|
314 |
with col1:
|
315 |
query = st.text_input(
|
316 |
+
"π Search Query",
|
317 |
+
placeholder="e.g., 'Artificial Intelligence', 'Kargil War'",
|
318 |
+
help="Enter your Wikipedia search query"
|
319 |
)
|
320 |
|
321 |
with col2:
|
|
|
325 |
index=0
|
326 |
)
|
327 |
|
328 |
+
# Options
|
329 |
+
col1, col2, col3 = st.columns(3)
|
330 |
+
|
331 |
+
with col1:
|
332 |
+
num_results = st.slider("Results", 1, 8, 3)
|
333 |
+
|
334 |
+
with col2:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
335 |
if llm_connected:
|
336 |
+
summary_type = st.selectbox(
|
337 |
+
"AI Summary",
|
338 |
+
["concise", "detailed", "explanatory"]
|
339 |
+
)
|
340 |
+
else:
|
341 |
+
summary_type = st.selectbox(
|
342 |
+
"Summary",
|
343 |
+
["short", "medium", "long"]
|
344 |
+
)
|
345 |
+
|
346 |
+
with col3:
|
347 |
+
if llm_connected and available_models:
|
348 |
+
selected_model = st.selectbox("Model", available_models)
|
349 |
+
else:
|
350 |
+
selected_model = None
|
351 |
+
st.info("No models")
|
352 |
+
|
353 |
+
# Translation option
|
354 |
+
if llm_connected:
|
355 |
+
enable_translation = st.checkbox("π Enable Translation")
|
356 |
+
if enable_translation:
|
357 |
+
target_lang = st.selectbox(
|
358 |
+
"Translate to",
|
359 |
+
[lang for lang in LANGUAGES.keys() if lang != selected_lang]
|
360 |
+
)
|
361 |
|
362 |
st.markdown("</div>", unsafe_allow_html=True)
|
363 |
|
364 |
# Search button
|
365 |
+
search_clicked = st.button("π Search", type="primary", use_container_width=True)
|
366 |
+
|
367 |
+
if search_clicked and query:
|
368 |
+
lang_code = LANGUAGES[selected_lang]
|
369 |
+
|
370 |
+
with st.spinner("Searching Wikipedia..."):
|
371 |
+
search_results = wiki_api.search_articles(query, lang_code, num_results)
|
372 |
+
st.session_state.search_results = search_results
|
373 |
+
|
374 |
+
# Display results
|
375 |
+
if st.session_state.search_results:
|
376 |
+
st.success(f"Found {len(st.session_state.search_results)} results")
|
377 |
+
|
378 |
+
for idx, result in enumerate(st.session_state.search_results):
|
379 |
+
with st.container():
|
380 |
+
st.markdown("<div class='result-card'>", unsafe_allow_html=True)
|
381 |
|
382 |
+
# Title
|
383 |
+
title = result.get("title", "")
|
384 |
+
st.markdown(f"<div class='article-title'>{idx+1}. {title}</div>", unsafe_allow_html=True)
|
385 |
+
|
386 |
+
# Get content
|
387 |
+
lang_code = LANGUAGES[selected_lang]
|
388 |
+
summary_data = wiki_api.get_page_summary(title, lang_code)
|
389 |
+
|
390 |
+
# Show thumbnail
|
391 |
+
if summary_data and "thumbnail" in summary_data:
|
392 |
+
col1, col2 = st.columns([1, 4])
|
393 |
+
with col1:
|
394 |
+
st.image(summary_data["thumbnail"]["source"], width=100)
|
395 |
+
content_col = col2
|
396 |
+
else:
|
397 |
+
content_col = st
|
398 |
+
|
399 |
+
with content_col:
|
400 |
+
# AI Summary
|
401 |
+
if llm_connected and selected_model:
|
402 |
+
# Get detailed content for AI
|
403 |
+
detailed_content = wiki_api.get_page_content(title, lang_code)
|
404 |
+
|
405 |
+
if detailed_content:
|
406 |
+
with st.spinner("Generating AI summary..."):
|
407 |
+
ai_summary = llm.generate_summary(
|
408 |
+
detailed_content,
|
409 |
+
selected_model,
|
410 |
+
selected_lang,
|
411 |
+
summary_type
|
412 |
+
)
|
413 |
|
414 |
+
if ai_summary and not ai_summary.startswith("Error"):
|
415 |
+
st.markdown("<div class='ai-summary'>", unsafe_allow_html=True)
|
416 |
+
st.markdown("**π€ AI Summary:**")
|
417 |
+
st.write(ai_summary)
|
|
|
|
|
|
|
|
|
|
|
418 |
|
419 |
+
# Translation
|
420 |
+
if 'enable_translation' in locals() and enable_translation:
|
421 |
+
with st.spinner("Translating..."):
|
422 |
+
translated = llm.translate_text(ai_summary, target_lang, selected_model)
|
423 |
+
if translated != ai_summary:
|
424 |
+
st.markdown(f"**π {target_lang}:**")
|
425 |
+
st.write(translated)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
|
427 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
|
|
428 |
else:
|
429 |
+
st.warning("AI summary failed")
|
430 |
+
if summary_data:
|
431 |
+
basic_summary = summary_data.get("extract", "")
|
432 |
+
st.write(simple_summarize(basic_summary, 3))
|
433 |
+
else:
|
434 |
+
st.warning("Could not fetch detailed content")
|
435 |
+
|
436 |
+
else:
|
437 |
+
# Basic summary
|
438 |
+
if summary_data:
|
439 |
+
basic_summary = summary_data.get("extract", "")
|
440 |
+
if summary_type == "short":
|
441 |
+
basic_summary = simple_summarize(basic_summary, 2)
|
442 |
+
elif summary_type == "medium":
|
443 |
+
basic_summary = simple_summarize(basic_summary, 4)
|
444 |
+
st.write(basic_summary)
|
445 |
+
else:
|
446 |
+
snippet = clean_html(result.get("snippet", ""))
|
447 |
+
st.write(snippet)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
448 |
|
449 |
+
# Wikipedia link
|
450 |
+
if summary_data and "content_urls" in summary_data:
|
451 |
+
wiki_url = summary_data["content_urls"]["desktop"]["page"]
|
452 |
+
st.markdown(f"[π Read on Wikipedia]({wiki_url})")
|
453 |
+
|
454 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
455 |
+
st.markdown("---")
|
456 |
|
457 |
+
elif search_clicked and not query:
|
458 |
+
st.warning("Please enter a search query")
|
459 |
+
|
460 |
+
# Footer stats
|
461 |
st.markdown("---")
|
462 |
col1, col2, col3, col4 = st.columns(4)
|
463 |
|
464 |
with col1:
|
465 |
st.metric("π Languages", len(LANGUAGES))
|
|
|
466 |
with col2:
|
467 |
+
st.metric("π€ LLM", "ON" if llm_connected else "OFF")
|
|
|
468 |
with col3:
|
469 |
st.metric("π Models", len(available_models))
|
|
|
470 |
with col4:
|
471 |
+
st.metric("π Results", len(st.session_state.search_results))
|
472 |
|
473 |
+
# Setup guide
|
474 |
+
with st.expander("π οΈ Ollama Setup Guide"):
|
475 |
st.markdown("""
|
476 |
+
### Quick Setup:
|
477 |
|
478 |
+
**1. Install Ollama:**
|
479 |
+
```bash
|
480 |
+
# macOS/Linux
|
481 |
+
curl -fsSL https://ollama.ai/install.sh | sh
|
|
|
|
|
|
|
482 |
|
483 |
+
# Windows: Download from https://ollama.ai
|
484 |
+
```
|
|
|
|
|
|
|
|
|
|
|
485 |
|
486 |
+
**2. Pull a model:**
|
487 |
+
```bash
|
488 |
+
ollama pull llama3.2
|
489 |
+
```
|
490 |
|
491 |
+
**3. Start server:**
|
492 |
+
```bash
|
493 |
+
ollama serve
|
494 |
+
```
|
495 |
|
496 |
+
**4. Refresh this page!**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
|
498 |
+
### Recommended Models:
|
499 |
+
- `llama3.2` - Best overall performance
|
500 |
+
- `mistral` - Fast and efficient
|
501 |
+
- `qwen2` - Good for multilingual content
|
|
|
502 |
""")
|
503 |
|
504 |
if __name__ == "__main__":
|