qwerty45-uiop commited on
Commit
91fc2e4
Β·
verified Β·
1 Parent(s): 498d485

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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 asyncio
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=5)
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=10)
53
  if response.status_code == 200:
54
  data = response.json()
55
- return [model["name"] for model in data.get("models", [])]
 
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
- # Craft prompt based on language and summary type
 
 
 
 
65
  if summary_type == "concise":
66
- prompt = f"""Summarize the following Wikipedia content in {language} in 2-3 sentences.
67
- Make it clear and informative:
68
-
69
- {text}
70
-
71
- Summary:"""
72
  elif summary_type == "detailed":
73
- prompt = f"""Provide a comprehensive summary of the following Wikipedia content in {language}.
74
- Include key points, important facts, and context:
75
-
76
- {text}
77
-
78
- Detailed Summary:"""
79
  else: # explanatory
80
- prompt = f"""Explain the following Wikipedia content in {language} in a simple,
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": 500 if summary_type == "detailed" else 200
95
  }
96
  }
97
 
@@ -99,22 +88,20 @@ class OllamaLLM:
99
 
100
  if response.status_code == 200:
101
  data = response.json()
102
- return data.get("response", "").strip()
 
103
  else:
104
- return f"Error: {response.status_code}"
105
 
 
 
106
  except Exception as e:
107
- return f"LLM Error: {str(e)}"
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"""Translate the following text to {target_language}.
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": 300
126
  }
127
  }
128
 
@@ -130,11 +117,12 @@ class OllamaLLM:
130
 
131
  if response.status_code == 200:
132
  data = response.json()
133
- return data.get("response", "").strip()
 
134
  else:
135
- return text # Return original if translation fails
136
 
137
- except Exception as e:
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 = 3000) -> Optional[str]:
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
- except Exception as e:
208
- st.error(f"Content error: {str(e)}")
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
- return '. '.join(summary_sentences) + ('.' if not summary_sentences[-1].endswith('.') else '')
 
 
 
221
 
222
  def main():
223
- # Custom CSS for mobile-first design
224
  st.markdown("""
225
  <style>
226
  .main-header {
@@ -230,13 +230,13 @@ def main():
230
  }
231
  .search-container {
232
  background-color: #f8f9fa;
233
- padding: 1rem;
234
  border-radius: 10px;
235
  margin-bottom: 1rem;
236
  }
237
  .result-card {
238
  background-color: white;
239
- padding: 1rem;
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
- .llm-status {
251
- padding: 0.5rem;
252
- border-radius: 5px;
253
  margin-bottom: 1rem;
254
- font-size: 0.9rem;
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 Multilingual Assistant</h1>", unsafe_allow_html=True)
283
- st.markdown("<p style='text-align: center; color: #666;'>Search Wikipedia with Local LLM Intelligence</p>", unsafe_allow_html=True)
 
 
 
 
284
 
285
  # Initialize APIs
286
  wiki_api = WikipediaAPI()
287
  llm = OllamaLLM()
288
 
289
  # Check LLM connection
290
- llm_connected = llm.check_connection()
291
- available_models = llm.get_available_models() if llm_connected else []
 
292
 
293
- # LLM Status
294
  if llm_connected:
295
  st.markdown(f"""
296
- <div class='llm-status status-connected'>
297
- βœ… <strong>Local LLM Connected</strong> - Ollama running with {len(available_models)} models
298
  </div>
299
  """, unsafe_allow_html=True)
300
  else:
301
  st.markdown("""
302
- <div class='llm-status status-disconnected'>
303
- ❌ <strong>Local LLM Disconnected</strong> - Install and run Ollama for AI features
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
- # Search interface
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 Wikipedia",
316
- placeholder="e.g., 'Explain Kargil War in Telugu'",
317
- help="Enter your search query in any language"
318
  )
319
 
320
  with col2:
@@ -324,218 +325,180 @@ def main():
324
  index=0
325
  )
326
 
327
- # Advanced options
328
- with st.expander("βš™οΈ Advanced Options"):
329
- col1, col2, col3 = st.columns(3)
330
-
331
- with col1:
332
- num_results = st.slider("Number of results", 1, 10, 3)
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
- col1, col2 = st.columns(2)
362
- with col1:
363
- enable_translation = st.checkbox("🌐 Enable Translation", value=False)
364
- with col2:
365
- if enable_translation:
366
- target_lang = st.selectbox(
367
- "Translate to",
368
- options=list(LANGUAGES.keys()),
369
- index=1
370
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
  st.markdown("</div>", unsafe_allow_html=True)
373
 
374
  # Search button
375
- if st.button("πŸ”Ž Search with AI", type="primary", use_container_width=True):
376
- if query:
377
- lang_code = LANGUAGES[selected_lang]
378
-
379
- with st.spinner(f"Searching Wikipedia and processing with AI..."):
380
- # Search for articles
381
- search_results = wiki_api.search_articles(query, lang_code, num_results)
 
 
 
 
 
 
 
 
 
382
 
383
- if search_results:
384
- st.success(f"Found {len(search_results)} results - Processing with {'AI' if llm_connected else 'basic'} summarization")
385
-
386
- for idx, result in enumerate(search_results):
387
- with st.container():
388
- st.markdown("<div class='result-card'>", unsafe_allow_html=True)
389
-
390
- # Article title
391
- title = result.get("title", "")
392
- st.markdown(f"<div class='article-title'>{idx+1}. {title}</div>", unsafe_allow_html=True)
393
-
394
- # Get detailed content for AI processing
395
- content = wiki_api.get_page_content(title, lang_code)
396
- summary_data = wiki_api.get_page_summary(title, lang_code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
- if content and llm_connected and selected_model:
399
- # AI-powered summary
400
- with st.spinner("Generating AI summary..."):
401
- ai_summary = llm.generate_summary(
402
- content,
403
- selected_model,
404
- selected_lang,
405
- summary_mode
406
- )
407
 
408
- if ai_summary and not ai_summary.startswith("Error") and not ai_summary.startswith("LLM Error"):
409
- st.markdown("<div class='ai-summary'>", unsafe_allow_html=True)
410
- st.markdown("**πŸ€– AI Summary:**")
411
- st.write(ai_summary)
412
-
413
- # Translation if enabled
414
- if 'enable_translation' in locals() and enable_translation and target_lang != selected_lang:
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.write(summary_text)
438
-
439
  else:
440
- # Fallback to search snippet
441
- snippet = clean_html(result.get("snippet", ""))
442
- st.write(snippet)
443
-
444
- # Display thumbnail if available
445
- if summary_data and "thumbnail" in summary_data:
446
- st.image(summary_data["thumbnail"]["source"], width=150)
447
-
448
- # Wikipedia link
449
- if summary_data and "content_urls" in summary_data:
450
- wiki_url = summary_data["content_urls"]["desktop"]["page"]
451
- st.markdown(f"[πŸ“– Read full article on Wikipedia]({wiki_url})")
452
-
453
- # Detailed content button
454
- if st.button(f"πŸ“ Show detailed content", key=f"detail_{idx}"):
455
- if content:
456
- st.text_area(
457
- "Full Content",
458
- content,
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
- else:
469
- st.warning(f"No results found for '{query}' in {selected_lang}")
470
- st.info("Try different keywords or switch to another language")
471
-
472
- else:
473
- st.warning("Please enter a search query")
 
474
 
475
- # Status dashboard
 
 
 
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 Status", "Connected" if llm_connected else "Offline")
484
-
485
  with col3:
486
  st.metric("πŸ“š Models", len(available_models))
487
-
488
  with col4:
489
- st.metric("πŸ” Search Mode", "AI-Powered" if llm_connected else "Standard")
490
 
491
- # Setup instructions
492
- with st.expander("πŸ› οΈ Setup Instructions"):
493
  st.markdown("""
494
- ### Install Ollama for AI Features:
495
 
496
- 1. **Install Ollama:**
497
- ```bash
498
- # MacOS/Linux
499
- curl -fsSL https://ollama.ai/install.sh | sh
500
-
501
- # Windows - Download from https://ollama.ai
502
- ```
503
 
504
- 2. **Pull a model:**
505
- ```bash
506
- ollama pull llama3.2
507
- # or
508
- ollama pull mistral
509
- ollama pull codellama
510
- ```
511
 
512
- 3. **Start Ollama server:**
513
- ```bash
514
- ollama serve
515
- ```
516
 
517
- 4. **Restart this app** - LLM features will be automatically enabled!
 
 
 
518
 
519
- ### Recommended Models:
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
- **AI Features:**
535
- - πŸ€– Intelligent summarization (concise/detailed/explanatory)
536
- - 🌐 Multi-language translation
537
- - πŸ“ Context-aware explanations
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__":