amauricunha commited on
Commit
c4b42b3
·
verified ·
1 Parent(s): 0efa332

Delete content_curator.py

Browse files
Files changed (1) hide show
  1. content_curator.py +0 -409
content_curator.py DELETED
@@ -1,409 +0,0 @@
1
- # content_curator.py
2
- import requests
3
- import json
4
- import re
5
- from datetime import datetime, timedelta
6
- from urllib.parse import urlparse, urljoin
7
- from bs4 import BeautifulSoup
8
- import feedparser
9
- import logging
10
- from groq import Groq
11
- import google.generativeai as genai
12
- import os
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- # Initialize AI clients
17
- groq_client = None
18
- genai_client = None
19
-
20
- try:
21
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
22
- if GROQ_API_KEY:
23
- groq_client = Groq(api_key=GROQ_API_KEY)
24
- except Exception as e:
25
- logger.warning(f"Groq client not available: {e}")
26
-
27
- try:
28
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
29
- if GEMINI_API_KEY:
30
- genai.configure(api_key=GEMINI_API_KEY)
31
- genai_client = genai
32
- except Exception as e:
33
- logger.warning(f"Gemini client not available: {e}")
34
-
35
- class ContentCurator:
36
- def __init__(self):
37
- # Import here to avoid circular imports
38
- self.track_tokens = None
39
- try:
40
- from admin_module import admin_manager
41
- self.admin_manager = admin_manager
42
- except ImportError:
43
- self.admin_manager = None
44
-
45
- def _track_token_usage(self, user_id, provider, input_tokens, output_tokens, operation):
46
- """Track token usage for admin monitoring"""
47
- try:
48
- if self.admin_manager:
49
- self.admin_manager.record_token_usage(user_id, provider, input_tokens, output_tokens, operation)
50
- except Exception as e:
51
- logger.error(f"Error tracking tokens: {e}")
52
-
53
- self.search_engines = {
54
- # Using free APIs and web scraping
55
- 'news_sources': {
56
- 'bbc': 'https://feeds.bbci.co.uk/news/rss.xml',
57
- 'reuters': 'https://www.reuters.com/arcio/rss/',
58
- 'techcrunch': 'https://techcrunch.com/feed/',
59
- 'medium': 'https://medium.com/feed/tag/{topic}',
60
- },
61
- 'content_categories': {
62
- 'technology': ['tech', 'software', 'AI', 'cybersecurity', 'automotive'],
63
- 'business': ['business', 'management', 'leadership', 'finance'],
64
- 'science': ['science', 'research', 'innovation'],
65
- 'professional': ['career', 'professional-development', 'skills']
66
- }
67
- }
68
-
69
- def search_content(self, interests, english_level, context_focus, limit=10):
70
- """Search for content based on user interests and level"""
71
- try:
72
- results = []
73
-
74
- for interest in interests:
75
- # Search RSS feeds
76
- rss_results = self._search_rss_feeds(interest, limit=3)
77
- results.extend(rss_results)
78
-
79
- # Search Medium articles
80
- medium_results = self._search_medium(interest, limit=2)
81
- results.extend(medium_results)
82
-
83
- # Filter and rank results
84
- filtered_results = self._filter_by_level_and_context(
85
- results, english_level, context_focus
86
- )
87
-
88
- return filtered_results[:limit]
89
-
90
- except Exception as e:
91
- logger.error(f"Error searching content: {e}")
92
- return []
93
-
94
- def _search_rss_feeds(self, topic, limit=5):
95
- """Search RSS feeds for relevant content"""
96
- results = []
97
-
98
- try:
99
- # Map topic to appropriate RSS feeds
100
- relevant_feeds = []
101
-
102
- if any(keyword in topic.lower() for keyword in ['tech', 'software', 'cyber', 'adas', 'automotive']):
103
- relevant_feeds.extend([
104
- 'https://feeds.bbci.co.uk/news/technology/rss.xml',
105
- 'https://techcrunch.com/feed/',
106
- 'https://www.wired.com/feed/rss'
107
- ])
108
-
109
- if any(keyword in topic.lower() for keyword in ['business', 'management', 'product']):
110
- relevant_feeds.extend([
111
- 'https://feeds.bbci.co.uk/news/business/rss.xml',
112
- 'https://feeds.harvard.edu/news/rss/business.xml'
113
- ])
114
-
115
- # Default to general news if no specific match
116
- if not relevant_feeds:
117
- relevant_feeds = ['https://feeds.bbci.co.uk/news/rss.xml']
118
-
119
- for feed_url in relevant_feeds[:2]: # Limit to 2 feeds to avoid timeout
120
- try:
121
- feed = feedparser.parse(feed_url)
122
-
123
- for entry in feed.entries[:limit]:
124
- if self._is_relevant_to_topic(entry.title + " " + entry.get('summary', ''), topic):
125
- results.append({
126
- 'title': entry.title,
127
- 'url': entry.link,
128
- 'summary': entry.get('summary', '')[:200] + '...',
129
- 'source': urlparse(feed_url).netloc,
130
- 'published': entry.get('published', ''),
131
- 'relevance_score': self._calculate_relevance(entry.title, topic)
132
- })
133
-
134
- except Exception as e:
135
- logger.warning(f"Error parsing feed {feed_url}: {e}")
136
- continue
137
-
138
- except Exception as e:
139
- logger.error(f"Error in RSS search: {e}")
140
-
141
- return sorted(results, key=lambda x: x['relevance_score'], reverse=True)
142
-
143
- def _search_medium(self, topic, limit=3):
144
- """Search Medium articles (simplified approach)"""
145
- results = []
146
-
147
- try:
148
- # Use Medium's RSS feed for topics
149
- search_terms = topic.lower().replace(' ', '-')
150
- medium_url = f"https://medium.com/feed/tag/{search_terms}"
151
-
152
- feed = feedparser.parse(medium_url)
153
-
154
- for entry in feed.entries[:limit]:
155
- results.append({
156
- 'title': entry.title,
157
- 'url': entry.link,
158
- 'summary': entry.get('summary', '')[:200] + '...',
159
- 'source': 'Medium',
160
- 'published': entry.get('published', ''),
161
- 'relevance_score': self._calculate_relevance(entry.title, topic)
162
- })
163
-
164
- except Exception as e:
165
- logger.warning(f"Error searching Medium: {e}")
166
-
167
- return results
168
-
169
- def _is_relevant_to_topic(self, text, topic):
170
- """Check if text is relevant to topic"""
171
- text_lower = text.lower()
172
- topic_words = topic.lower().split()
173
-
174
- # Simple relevance check
175
- matches = sum(1 for word in topic_words if word in text_lower)
176
- return matches >= len(topic_words) * 0.5 # At least 50% of topic words present
177
-
178
- def _calculate_relevance(self, title, topic):
179
- """Calculate relevance score between title and topic"""
180
- title_lower = title.lower()
181
- topic_lower = topic.lower()
182
-
183
- # Simple scoring based on word matches
184
- topic_words = topic_lower.split()
185
- score = 0
186
-
187
- for word in topic_words:
188
- if word in title_lower:
189
- score += 1
190
-
191
- return score / len(topic_words) if topic_words else 0
192
-
193
- def _filter_by_level_and_context(self, results, english_level, context_focus):
194
- """Filter results by English level and context"""
195
- # Level difficulty mapping
196
- level_complexity = {
197
- 'A1': 1, 'A2': 2, 'B1': 3, 'B2': 4, 'C1': 5, 'C2': 6
198
- }
199
-
200
- user_level = level_complexity.get(english_level, 3)
201
-
202
- filtered = []
203
- for result in results:
204
- # Estimate content difficulty (simplified)
205
- difficulty = self._estimate_content_difficulty(result['title'] + " " + result['summary'])
206
-
207
- # Filter by level (allow content slightly above user level)
208
- if difficulty <= user_level + 1:
209
- result['estimated_difficulty'] = difficulty
210
- filtered.append(result)
211
-
212
- return filtered
213
-
214
- def _estimate_content_difficulty(self, text):
215
- """Estimate content difficulty (1-6 scale)"""
216
- # Simple heuristics for difficulty estimation
217
- word_count = len(text.split())
218
- avg_word_length = sum(len(word) for word in text.split()) / word_count if word_count > 0 else 0
219
-
220
- # Technical terms increase difficulty
221
- technical_terms = ['algorithm', 'implementation', 'architecture', 'methodology', 'paradigm']
222
- tech_score = sum(1 for term in technical_terms if term in text.lower())
223
-
224
- # Calculate difficulty score
225
- difficulty = 1
226
- if avg_word_length > 6:
227
- difficulty += 1
228
- if tech_score > 0:
229
- difficulty += 1
230
- if word_count > 200:
231
- difficulty += 1
232
-
233
- return min(difficulty, 6)
234
-
235
- def extract_content_from_url(self, url):
236
- """Extract readable content from URL"""
237
- try:
238
- headers = {
239
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
240
- }
241
-
242
- response = requests.get(url, headers=headers, timeout=10)
243
- response.raise_for_status()
244
-
245
- soup = BeautifulSoup(response.content, 'html.parser')
246
-
247
- # Remove unwanted elements
248
- for element in soup(['script', 'style', 'nav', 'header', 'footer', 'aside']):
249
- element.decompose()
250
-
251
- # Extract main content
252
- main_content = soup.find('main') or soup.find('article') or soup.find('div', class_=re.compile(r'content|article|post'))
253
-
254
- if main_content:
255
- text = main_content.get_text(separator=' ', strip=True)
256
- else:
257
- # Fallback to body text
258
- text = soup.get_text(separator=' ', strip=True)
259
-
260
- # Clean up text
261
- text = re.sub(r'\s+', ' ', text) # Multiple spaces to single
262
- text = text[:5000] # Limit length
263
-
264
- return {
265
- 'success': True,
266
- 'content': text,
267
- 'title': soup.find('title').text if soup.find('title') else '',
268
- 'word_count': len(text.split())
269
- }
270
-
271
- except Exception as e:
272
- logger.error(f"Error extracting content from {url}: {e}")
273
- return {
274
- 'success': False,
275
- 'error': str(e)
276
- }
277
-
278
- def generate_personalized_recommendations(self, user_interests, recent_articles, english_level, context_focus, user_id=None):
279
- """Generate AI-powered content recommendations"""
280
- try:
281
- if not groq_client and not genai_client:
282
- return []
283
-
284
- # Prepare context for AI
285
- interests_text = ', '.join(user_interests.keys())
286
- recent_titles = [article.get('title', '') for article in recent_articles[-5:]]
287
- recent_text = '; '.join(recent_titles)
288
-
289
- prompt = f"""
290
- User Profile:
291
- - English Level: {english_level}
292
- - Context Focus: {context_focus}
293
- - Interests: {interests_text}
294
- - Recently read: {recent_text}
295
-
296
- Recommend 5 specific article topics or search terms that would be perfect for this user's English learning journey.
297
- Consider their level and interests. Focus on practical, engaging content.
298
-
299
- Format as JSON array: [
300
- {{"topic": "topic name", "reason": "why this is good for the user", "difficulty": "estimated level"}},
301
- ...
302
- ]
303
- """
304
-
305
- # Try Groq first, then Gemini
306
- response_text = None
307
- if groq_client:
308
- response = groq_client.chat.completions.create(
309
- model="llama-3.1-8b-instant",
310
- messages=[{"role": "user", "content": prompt}],
311
- temperature=0.7
312
- )
313
- response_text = response.choices[0].message.content
314
-
315
- # Track token usage
316
- if user_id and hasattr(response, 'usage'):
317
- self._track_token_usage(
318
- user_id, 'groq',
319
- response.usage.prompt_tokens,
320
- response.usage.completion_tokens,
321
- 'content_recommendations'
322
- )
323
- elif genai_client:
324
- model = genai_client.GenerativeModel('gemini-2.5-flash-latest')
325
- response = model.generate_content(prompt)
326
- response_text = response.text
327
-
328
- # Track token usage for Gemini (estimated)
329
- if user_id:
330
- # Estimate tokens (rough approximation: 1 token ≈ 4 characters)
331
- input_tokens = len(prompt) // 4
332
- output_tokens = len(response_text) // 4
333
- self._track_token_usage(
334
- user_id, 'gemini',
335
- input_tokens,
336
- output_tokens,
337
- 'content_recommendations'
338
- )
339
-
340
- if response_text:
341
- # Extract JSON from response
342
- json_match = re.search(r'\[.*\]', response_text, re.DOTALL)
343
- if json_match:
344
- recommendations = json.loads(json_match.group())
345
- return recommendations
346
-
347
- return []
348
-
349
- except Exception as e:
350
- logger.error(f"Error generating recommendations: {e}")
351
- return []
352
-
353
- def analyze_content_for_learning(self, content, user_level):
354
- """Analyze content and suggest learning points"""
355
- try:
356
- if not groq_client and not genai_client:
357
- return {}
358
-
359
- # Truncate content for analysis
360
- analysis_content = content[:2000] + "..." if len(content) > 2000 else content
361
-
362
- prompt = f"""
363
- Analyze this English text for a {user_level} level English learner:
364
-
365
- "{analysis_content}"
366
-
367
- Provide:
368
- 1. Key vocabulary words (5-8 words) with definitions
369
- 2. Important grammar patterns used
370
- 3. Main topics/themes
371
- 4. Difficulty assessment (1-10)
372
- 5. Learning suggestions for this level
373
-
374
- Format as JSON: {{
375
- "vocabulary": [{{"word": "...", "definition": "..."}}, ...],
376
- "grammar_patterns": ["pattern1", "pattern2", ...],
377
- "topics": ["topic1", "topic2", ...],
378
- "difficulty": 7,
379
- "learning_suggestions": ["suggestion1", "suggestion2", ...]
380
- }}
381
- """
382
-
383
- response_text = None
384
- if groq_client:
385
- response = groq_client.chat.completions.create(
386
- model="llama-3.1-8b-instant",
387
- messages=[{"role": "user", "content": prompt}],
388
- temperature=0.3
389
- )
390
- response_text = response.choices[0].message.content
391
- elif genai_client:
392
- model = genai_client.GenerativeModel('gemini-2.5-flash-latest')
393
- response = model.generate_content(prompt)
394
- response_text = response.text
395
-
396
- if response_text:
397
- json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
398
- if json_match:
399
- analysis = json.loads(json_match.group())
400
- return analysis
401
-
402
- return {}
403
-
404
- except Exception as e:
405
- logger.error(f"Error analyzing content: {e}")
406
- return {}
407
-
408
- # Global instance
409
- content_curator = ContentCurator()