ismdrobiul489 commited on
Commit
0a7e81a
Β·
1 Parent(s): 8f2ed57

Add Trends module with PyTrends - Trending Now, Keyword Research, YouTube Trends

Browse files
modules/trends/__init__.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trends Module - Google Trends Analysis
3
+ Uses pytrends for trend data analysis
4
+ """
5
+ import logging
6
+ from fastapi import APIRouter
7
+ from config import NCAkitConfig
8
+
9
+ from .router import router
10
+ from .services.trends_client import TrendsClient
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Module will be initialized during registration
15
+ trends_client = None
16
+
17
+
18
+ def register(app, config: NCAkitConfig):
19
+ """Register trends module with the app"""
20
+ global trends_client
21
+
22
+ try:
23
+ # Initialize trends client
24
+ trends_client = TrendsClient()
25
+
26
+ # Include router
27
+ app.include_router(router, prefix="/api/trends", tags=["Trends"])
28
+
29
+ logger.info("Trends module registered successfully")
30
+ return True
31
+
32
+ except Exception as e:
33
+ logger.error(f"Failed to register trends module: {e}")
34
+ return False
modules/trends/router.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trends Module Router
3
+ API endpoints for Google Trends data
4
+ """
5
+ import logging
6
+ from fastapi import APIRouter, HTTPException
7
+
8
+ from .schemas import (
9
+ TrendingNowRequest,
10
+ TrendingNowResponse,
11
+ KeywordResearchRequest,
12
+ KeywordResearchResponse,
13
+ YouTubeTrendsRequest,
14
+ TrendingTopic
15
+ )
16
+ from . import trends_client
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ router = APIRouter()
21
+
22
+
23
+ @router.post("/trending-now",
24
+ response_model=TrendingNowResponse,
25
+ summary="Get trending topics",
26
+ description="Get currently trending searches for a country"
27
+ )
28
+ async def get_trending_now(request: TrendingNowRequest):
29
+ """
30
+ Get trending searches right now.
31
+
32
+ - Returns top trending topics sorted by popularity
33
+ - Supports different countries
34
+ """
35
+ try:
36
+ results = trends_client.get_trending_now(
37
+ country=request.country,
38
+ limit=request.limit
39
+ )
40
+
41
+ return TrendingNowResponse(
42
+ success=True,
43
+ count=len(results),
44
+ trends=[TrendingTopic(**r) for r in results]
45
+ )
46
+
47
+ except Exception as e:
48
+ logger.error(f"Error getting trending now: {e}")
49
+ raise HTTPException(status_code=500, detail=str(e))
50
+
51
+
52
+ @router.post("/keyword-research",
53
+ response_model=KeywordResearchResponse,
54
+ summary="Keyword research",
55
+ description="Get related topics and queries for a keyword"
56
+ )
57
+ async def keyword_research(request: KeywordResearchRequest):
58
+ """
59
+ Complete keyword research - related topics and queries.
60
+
61
+ - Related Topics: topics related to the keyword
62
+ - Related Queries: search queries people also search
63
+ - Sorted by search volume (highest first)
64
+ """
65
+ try:
66
+ results = trends_client.keyword_research(
67
+ keyword=request.keyword,
68
+ region=request.region,
69
+ timeframe=request.timeframe.value,
70
+ category=request.category.value,
71
+ search_type=request.search_type.value
72
+ )
73
+
74
+ return KeywordResearchResponse(
75
+ success=True,
76
+ **results
77
+ )
78
+
79
+ except Exception as e:
80
+ logger.error(f"Error in keyword research: {e}")
81
+ raise HTTPException(status_code=500, detail=str(e))
82
+
83
+
84
+ @router.post("/youtube-trends",
85
+ response_model=KeywordResearchResponse,
86
+ summary="YouTube trends",
87
+ description="Get YouTube-specific trends for a keyword"
88
+ )
89
+ async def youtube_trends(request: YouTubeTrendsRequest):
90
+ """
91
+ Get YouTube-specific trends and related content.
92
+ """
93
+ try:
94
+ results = trends_client.get_youtube_trends(
95
+ keyword=request.keyword,
96
+ region=request.region,
97
+ timeframe=request.timeframe.value
98
+ )
99
+
100
+ return KeywordResearchResponse(
101
+ success=True,
102
+ **results
103
+ )
104
+
105
+ except Exception as e:
106
+ logger.error(f"Error getting YouTube trends: {e}")
107
+ raise HTTPException(status_code=500, detail=str(e))
108
+
109
+
110
+ @router.get("/categories",
111
+ summary="List categories",
112
+ description="Get list of available categories"
113
+ )
114
+ async def list_categories():
115
+ """Get all available trend categories"""
116
+ from .services.trends_client import TrendsClient
117
+ return {
118
+ "categories": list(TrendsClient.CATEGORIES.keys())
119
+ }
120
+
121
+
122
+ @router.get("/countries",
123
+ summary="List countries",
124
+ description="Get list of supported countries"
125
+ )
126
+ async def list_countries():
127
+ """Get commonly used country codes"""
128
+ return {
129
+ "countries": [
130
+ {"code": "united_states", "name": "United States"},
131
+ {"code": "united_kingdom", "name": "United Kingdom"},
132
+ {"code": "india", "name": "India"},
133
+ {"code": "bangladesh", "name": "Bangladesh"},
134
+ {"code": "japan", "name": "Japan"},
135
+ {"code": "germany", "name": "Germany"},
136
+ {"code": "france", "name": "France"},
137
+ {"code": "brazil", "name": "Brazil"},
138
+ {"code": "canada", "name": "Canada"},
139
+ {"code": "australia", "name": "Australia"},
140
+ ]
141
+ }
modules/trends/schemas.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trends Module Schemas
3
+ Pydantic models for request/response
4
+ """
5
+ from pydantic import BaseModel, Field
6
+ from typing import Optional, List
7
+ from enum import Enum
8
+
9
+
10
+ # ===================
11
+ # Enums
12
+ # ===================
13
+
14
+ class CategoryEnum(str, Enum):
15
+ """Available categories"""
16
+ all = "all"
17
+ arts_entertainment = "arts_entertainment"
18
+ autos_vehicles = "autos_vehicles"
19
+ beauty_fitness = "beauty_fitness"
20
+ books_literature = "books_literature"
21
+ business_industrial = "business_industrial"
22
+ computers_electronics = "computers_electronics"
23
+ finance = "finance"
24
+ food_drink = "food_drink"
25
+ games = "games"
26
+ health = "health"
27
+ hobbies_leisure = "hobbies_leisure"
28
+ home_garden = "home_garden"
29
+ internet_telecom = "internet_telecom"
30
+ jobs_education = "jobs_education"
31
+ news = "news"
32
+ science = "science"
33
+ shopping = "shopping"
34
+ sports = "sports"
35
+ travel = "travel"
36
+
37
+
38
+ class TimeframeEnum(str, Enum):
39
+ """Available timeframes"""
40
+ now_1h = "now_1h"
41
+ now_4h = "now_4h"
42
+ now_1d = "now_1d"
43
+ now_7d = "now_7d"
44
+ today_1m = "today_1m"
45
+ today_3m = "today_3m"
46
+ today_12m = "today_12m"
47
+ today_5y = "today_5y"
48
+
49
+
50
+ class SearchTypeEnum(str, Enum):
51
+ """Search types"""
52
+ web = "web"
53
+ youtube = "youtube"
54
+ news = "news"
55
+ images = "images"
56
+ shopping = "shopping"
57
+
58
+
59
+ # ===================
60
+ # Request Models
61
+ # ===================
62
+
63
+ class TrendingNowRequest(BaseModel):
64
+ """Request for trending now"""
65
+ country: str = Field("united_states", description="Country name (e.g., 'bangladesh', 'india', 'united_states')")
66
+ limit: int = Field(20, ge=1, le=50, description="Number of results")
67
+
68
+
69
+ class KeywordResearchRequest(BaseModel):
70
+ """Request for keyword research"""
71
+ keyword: str = Field(..., description="Search keyword")
72
+ region: str = Field("", description="Region code (empty for worldwide, e.g., 'US', 'BD', 'IN')")
73
+ timeframe: TimeframeEnum = Field(TimeframeEnum.today_12m, description="Time range")
74
+ category: CategoryEnum = Field(CategoryEnum.all, description="Category")
75
+ search_type: SearchTypeEnum = Field(SearchTypeEnum.web, description="Search type")
76
+
77
+
78
+ class YouTubeTrendsRequest(BaseModel):
79
+ """Request for YouTube trends"""
80
+ keyword: str = Field(..., description="Search keyword")
81
+ region: str = Field("", description="Region code (empty for worldwide)")
82
+ timeframe: TimeframeEnum = Field(TimeframeEnum.today_12m, description="Time range")
83
+
84
+
85
+ # ===================
86
+ # Response Models
87
+ # ===================
88
+
89
+ class TrendingTopic(BaseModel):
90
+ """Single trending topic"""
91
+ rank: int
92
+ topic: str
93
+ country: str
94
+
95
+
96
+ class TrendingNowResponse(BaseModel):
97
+ """Response for trending now"""
98
+ success: bool
99
+ count: int
100
+ trends: List[TrendingTopic]
101
+
102
+
103
+ class TopicItem(BaseModel):
104
+ """Single topic item"""
105
+ topic: str
106
+ type: Optional[str] = None
107
+ value: int
108
+
109
+
110
+ class QueryItem(BaseModel):
111
+ """Single query item"""
112
+ query: str
113
+ value: str
114
+
115
+
116
+ class RelatedData(BaseModel):
117
+ """Related topics/queries data"""
118
+ keyword: str
119
+ top: List[dict]
120
+ rising: List[dict]
121
+
122
+
123
+ class KeywordResearchResponse(BaseModel):
124
+ """Response for keyword research"""
125
+ success: bool
126
+ keyword: str
127
+ region: str
128
+ timeframe: str
129
+ category: str
130
+ search_type: str
131
+ related_topics: dict
132
+ related_queries: dict
modules/trends/services/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Services init
modules/trends/services/trends_client.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trends Client - PyTrends Wrapper
3
+ Provides Google Trends and YouTube Trends data
4
+ """
5
+ import logging
6
+ from typing import Optional, List, Dict
7
+ from pytrends.request import TrendReq
8
+ import pandas as pd
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class TrendsClient:
14
+ """
15
+ Client for Google Trends data using pytrends.
16
+
17
+ Features:
18
+ - Trending Now (daily trends)
19
+ - Related Topics
20
+ - Related Queries
21
+ - YouTube Trends
22
+ """
23
+
24
+ # Category codes
25
+ CATEGORIES = {
26
+ "all": 0,
27
+ "arts_entertainment": 3,
28
+ "autos_vehicles": 47,
29
+ "beauty_fitness": 44,
30
+ "books_literature": 22,
31
+ "business_industrial": 12,
32
+ "computers_electronics": 5,
33
+ "finance": 7,
34
+ "food_drink": 71,
35
+ "games": 8,
36
+ "health": 45,
37
+ "hobbies_leisure": 65,
38
+ "home_garden": 11,
39
+ "internet_telecom": 13,
40
+ "jobs_education": 958,
41
+ "law_government": 19,
42
+ "news": 16,
43
+ "online_communities": 299,
44
+ "people_society": 14,
45
+ "pets_animals": 66,
46
+ "real_estate": 29,
47
+ "reference": 533,
48
+ "science": 174,
49
+ "shopping": 18,
50
+ "sports": 20,
51
+ "travel": 67,
52
+ }
53
+
54
+ # Timeframe options
55
+ TIMEFRAMES = {
56
+ "now_1h": "now 1-H",
57
+ "now_4h": "now 4-H",
58
+ "now_1d": "now 1-d",
59
+ "now_7d": "now 7-d",
60
+ "today_1m": "today 1-m",
61
+ "today_3m": "today 3-m",
62
+ "today_12m": "today 12-m",
63
+ "today_5y": "today 5-y",
64
+ }
65
+
66
+ def __init__(self, hl: str = "en-US", tz: int = 360):
67
+ """
68
+ Initialize TrendsClient.
69
+
70
+ Args:
71
+ hl: Host language
72
+ tz: Timezone offset
73
+ """
74
+ self.hl = hl
75
+ self.tz = tz
76
+ self.pytrends = TrendReq(hl=hl, tz=tz)
77
+
78
+ def get_trending_now(
79
+ self,
80
+ country: str = "united_states",
81
+ limit: int = 20
82
+ ) -> List[Dict]:
83
+ """
84
+ Get currently trending searches.
85
+
86
+ Args:
87
+ country: Country code (e.g., 'united_states', 'bangladesh', 'india')
88
+ limit: Number of results (default 20)
89
+
90
+ Returns:
91
+ List of trending topics with rank
92
+ """
93
+ try:
94
+ # Get trending searches
95
+ df = self.pytrends.trending_searches(pn=country)
96
+
97
+ results = []
98
+ for i, topic in enumerate(df[0].head(limit).tolist()):
99
+ results.append({
100
+ "rank": i + 1,
101
+ "topic": topic,
102
+ "country": country
103
+ })
104
+
105
+ logger.info(f"Got {len(results)} trending topics for {country}")
106
+ return results
107
+
108
+ except Exception as e:
109
+ logger.error(f"Error getting trending searches: {e}")
110
+ return []
111
+
112
+ def get_realtime_trends(
113
+ self,
114
+ country: str = "US",
115
+ category: str = "all",
116
+ limit: int = 20
117
+ ) -> List[Dict]:
118
+ """
119
+ Get realtime trending stories.
120
+
121
+ Args:
122
+ country: Country code (US, BD, IN, etc.)
123
+ category: Category name
124
+ limit: Number of results
125
+
126
+ Returns:
127
+ List of trending stories
128
+ """
129
+ try:
130
+ cat_code = self.CATEGORIES.get(category, 0)
131
+
132
+ df = self.pytrends.realtime_trending_searches(pn=country)
133
+
134
+ results = []
135
+ if not df.empty:
136
+ for i, row in df.head(limit).iterrows():
137
+ results.append({
138
+ "rank": i + 1,
139
+ "title": row.get('title', ''),
140
+ "entity_names": row.get('entityNames', []),
141
+ "articles": row.get('articles', [])
142
+ })
143
+
144
+ logger.info(f"Got {len(results)} realtime trends")
145
+ return results
146
+
147
+ except Exception as e:
148
+ logger.error(f"Error getting realtime trends: {e}")
149
+ return []
150
+
151
+ def get_related_topics(
152
+ self,
153
+ keyword: str,
154
+ region: str = "",
155
+ timeframe: str = "today 12-m",
156
+ category: int = 0,
157
+ search_type: str = ""
158
+ ) -> Dict:
159
+ """
160
+ Get related topics for a keyword.
161
+
162
+ Args:
163
+ keyword: Search keyword
164
+ region: Region code (empty for worldwide)
165
+ timeframe: Time range
166
+ category: Category code
167
+ search_type: 'youtube', 'news', 'images', 'froogle' or '' for web
168
+
169
+ Returns:
170
+ Dict with 'top' and 'rising' topics
171
+ """
172
+ try:
173
+ self.pytrends.build_payload(
174
+ kw_list=[keyword],
175
+ cat=category,
176
+ timeframe=timeframe,
177
+ geo=region,
178
+ gprop=search_type
179
+ )
180
+
181
+ data = self.pytrends.related_topics()
182
+
183
+ result = {
184
+ "keyword": keyword,
185
+ "top": [],
186
+ "rising": []
187
+ }
188
+
189
+ if keyword in data:
190
+ topic_data = data[keyword]
191
+
192
+ # Top topics
193
+ if 'top' in topic_data and topic_data['top'] is not None:
194
+ top_df = topic_data['top']
195
+ for _, row in top_df.iterrows():
196
+ result["top"].append({
197
+ "topic": row.get('topic_title', ''),
198
+ "type": row.get('topic_type', ''),
199
+ "value": int(row.get('value', 0))
200
+ })
201
+
202
+ # Rising topics
203
+ if 'rising' in topic_data and topic_data['rising'] is not None:
204
+ rising_df = topic_data['rising']
205
+ for _, row in rising_df.iterrows():
206
+ result["rising"].append({
207
+ "topic": row.get('topic_title', ''),
208
+ "type": row.get('topic_type', ''),
209
+ "value": str(row.get('value', ''))
210
+ })
211
+
212
+ # Sort by value
213
+ result["top"] = sorted(result["top"], key=lambda x: x["value"], reverse=True)
214
+
215
+ logger.info(f"Got {len(result['top'])} top and {len(result['rising'])} rising topics")
216
+ return result
217
+
218
+ except Exception as e:
219
+ logger.error(f"Error getting related topics: {e}")
220
+ return {"keyword": keyword, "top": [], "rising": [], "error": str(e)}
221
+
222
+ def get_related_queries(
223
+ self,
224
+ keyword: str,
225
+ region: str = "",
226
+ timeframe: str = "today 12-m",
227
+ category: int = 0,
228
+ search_type: str = ""
229
+ ) -> Dict:
230
+ """
231
+ Get related queries for a keyword.
232
+
233
+ Args:
234
+ keyword: Search keyword
235
+ region: Region code (empty for worldwide)
236
+ timeframe: Time range
237
+ category: Category code
238
+ search_type: 'youtube', 'news', 'images', 'froogle' or '' for web
239
+
240
+ Returns:
241
+ Dict with 'top' and 'rising' queries
242
+ """
243
+ try:
244
+ self.pytrends.build_payload(
245
+ kw_list=[keyword],
246
+ cat=category,
247
+ timeframe=timeframe,
248
+ geo=region,
249
+ gprop=search_type
250
+ )
251
+
252
+ data = self.pytrends.related_queries()
253
+
254
+ result = {
255
+ "keyword": keyword,
256
+ "top": [],
257
+ "rising": []
258
+ }
259
+
260
+ if keyword in data:
261
+ query_data = data[keyword]
262
+
263
+ # Top queries
264
+ if 'top' in query_data and query_data['top'] is not None:
265
+ top_df = query_data['top']
266
+ for _, row in top_df.iterrows():
267
+ result["top"].append({
268
+ "query": row.get('query', ''),
269
+ "value": int(row.get('value', 0))
270
+ })
271
+
272
+ # Rising queries
273
+ if 'rising' in query_data and query_data['rising'] is not None:
274
+ rising_df = query_data['rising']
275
+ for _, row in rising_df.iterrows():
276
+ result["rising"].append({
277
+ "query": row.get('query', ''),
278
+ "value": str(row.get('value', ''))
279
+ })
280
+
281
+ # Sort by value
282
+ result["top"] = sorted(result["top"], key=lambda x: x["value"], reverse=True)
283
+
284
+ logger.info(f"Got {len(result['top'])} top and {len(result['rising'])} rising queries")
285
+ return result
286
+
287
+ except Exception as e:
288
+ logger.error(f"Error getting related queries: {e}")
289
+ return {"keyword": keyword, "top": [], "rising": [], "error": str(e)}
290
+
291
+ def keyword_research(
292
+ self,
293
+ keyword: str,
294
+ region: str = "",
295
+ timeframe: str = "today 12-m",
296
+ category: str = "all",
297
+ search_type: str = "web"
298
+ ) -> Dict:
299
+ """
300
+ Complete keyword research - combines related topics and queries.
301
+
302
+ Args:
303
+ keyword: Search keyword
304
+ region: Region code (empty for worldwide)
305
+ timeframe: Time range key
306
+ category: Category name
307
+ search_type: 'web', 'youtube', 'news', 'images', 'shopping'
308
+
309
+ Returns:
310
+ Combined dict with topics and queries
311
+ """
312
+ # Convert params
313
+ cat_code = self.CATEGORIES.get(category, 0)
314
+ tf = self.TIMEFRAMES.get(timeframe, "today 12-m")
315
+ gprop = "" if search_type == "web" else search_type
316
+
317
+ # Get both
318
+ topics = self.get_related_topics(keyword, region, tf, cat_code, gprop)
319
+ queries = self.get_related_queries(keyword, region, tf, cat_code, gprop)
320
+
321
+ return {
322
+ "keyword": keyword,
323
+ "region": region if region else "worldwide",
324
+ "timeframe": timeframe,
325
+ "category": category,
326
+ "search_type": search_type,
327
+ "related_topics": topics,
328
+ "related_queries": queries
329
+ }
330
+
331
+ def get_youtube_trends(
332
+ self,
333
+ keyword: str,
334
+ region: str = "",
335
+ timeframe: str = "today 12-m"
336
+ ) -> Dict:
337
+ """
338
+ Get YouTube-specific trends for a keyword.
339
+ """
340
+ return self.keyword_research(
341
+ keyword=keyword,
342
+ region=region,
343
+ timeframe=timeframe,
344
+ category="all",
345
+ search_type="youtube"
346
+ )
requirements.txt CHANGED
@@ -25,3 +25,7 @@ groq
25
  python-multipart
26
  huggingface_hub
27
  imageio-ffmpeg>=0.4.9
 
 
 
 
 
25
  python-multipart
26
  huggingface_hub
27
  imageio-ffmpeg>=0.4.9
28
+
29
+ # Trends Analysis
30
+ pytrends
31
+ pandas
static/index.html CHANGED
@@ -273,6 +273,9 @@
273
  <button class="tab-btn" data-tab="fact">
274
  🧠 Fact Image
275
  </button>
 
 
 
276
  </div>
277
 
278
  <!-- Story Reels Tab -->
@@ -460,6 +463,112 @@
460
  </div>
461
  </div>
462
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  <script>
464
  // Tab switching
465
  document.querySelectorAll('.tab-btn').forEach(btn => {
@@ -674,6 +783,105 @@
674
  }
675
  });
676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  // ==========================================
678
  // GEMINI CHATBOT TEST
679
  // ==========================================
 
273
  <button class="tab-btn" data-tab="fact">
274
  🧠 Fact Image
275
  </button>
276
+ <button class="tab-btn" data-tab="trends">
277
+ πŸ“Š Trends
278
+ </button>
279
  </div>
280
 
281
  <!-- Story Reels Tab -->
 
463
  </div>
464
  </div>
465
 
466
+ <!-- Trends Tab -->
467
+ <div id="trends-tab" class="tab-content">
468
+ <div class="card">
469
+ <h2>πŸ“Š Trends Analysis</h2>
470
+ <p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
471
+ Analyze Google Trends data for content planning
472
+ </p>
473
+
474
+ <!-- Trending Now Section -->
475
+ <div style="margin-bottom: 2rem;">
476
+ <h3 style="margin-bottom: 1rem;">πŸ”₯ Trending Now</h3>
477
+ <form id="trendingForm">
478
+ <div class="form-row">
479
+ <div class="form-group">
480
+ <label>Country</label>
481
+ <select id="trendCountry">
482
+ <option value="united_states">United States</option>
483
+ <option value="bangladesh">Bangladesh</option>
484
+ <option value="india">India</option>
485
+ <option value="united_kingdom">United Kingdom</option>
486
+ <option value="japan">Japan</option>
487
+ <option value="germany">Germany</option>
488
+ <option value="brazil">Brazil</option>
489
+ <option value="canada">Canada</option>
490
+ </select>
491
+ </div>
492
+ <div class="form-group">
493
+ <label>Limit</label>
494
+ <select id="trendLimit">
495
+ <option value="10">10</option>
496
+ <option value="20" selected>20</option>
497
+ <option value="30">30</option>
498
+ <option value="50">50</option>
499
+ </select>
500
+ </div>
501
+ </div>
502
+ <button type="submit" class="submit-btn">Get Trending</button>
503
+ </form>
504
+ <div id="trendingResults" style="margin-top: 1rem;"></div>
505
+ </div>
506
+
507
+ <hr style="border-color: var(--border); margin: 2rem 0;">
508
+
509
+ <!-- Keyword Research Section -->
510
+ <div>
511
+ <h3 style="margin-bottom: 1rem;">πŸ” Keyword Research</h3>
512
+ <form id="keywordForm">
513
+ <div class="form-group">
514
+ <label>Keyword *</label>
515
+ <input type="text" id="researchKeyword" placeholder="e.g., AI, Python, Gaming" required>
516
+ </div>
517
+ <div class="form-row">
518
+ <div class="form-group">
519
+ <label>Region</label>
520
+ <select id="researchRegion">
521
+ <option value="">Worldwide</option>
522
+ <option value="US">United States</option>
523
+ <option value="BD">Bangladesh</option>
524
+ <option value="IN">India</option>
525
+ <option value="GB">United Kingdom</option>
526
+ </select>
527
+ </div>
528
+ <div class="form-group">
529
+ <label>Timeframe</label>
530
+ <select id="researchTimeframe">
531
+ <option value="now_1d">Last 24 hours</option>
532
+ <option value="now_7d">Last 7 days</option>
533
+ <option value="today_1m">Last month</option>
534
+ <option value="today_3m">Last 3 months</option>
535
+ <option value="today_12m" selected>Last 12 months</option>
536
+ </select>
537
+ </div>
538
+ </div>
539
+ <div class="form-row">
540
+ <div class="form-group">
541
+ <label>Category</label>
542
+ <select id="researchCategory">
543
+ <option value="all">All Categories</option>
544
+ <option value="computers_electronics">Computers & Electronics</option>
545
+ <option value="games">Games</option>
546
+ <option value="arts_entertainment">Arts & Entertainment</option>
547
+ <option value="science">Science</option>
548
+ <option value="news">News</option>
549
+ <option value="sports">Sports</option>
550
+ <option value="business_industrial">Business</option>
551
+ </select>
552
+ </div>
553
+ <div class="form-group">
554
+ <label>Search Type</label>
555
+ <select id="researchType">
556
+ <option value="web">Web Search</option>
557
+ <option value="youtube">YouTube Search</option>
558
+ <option value="news">News Search</option>
559
+ <option value="images">Image Search</option>
560
+ </select>
561
+ </div>
562
+ </div>
563
+ <button type="submit" class="submit-btn">Analyze Keyword</button>
564
+ </form>
565
+ <div id="keywordResults" style="margin-top: 1rem;"></div>
566
+ </div>
567
+
568
+ <div id="trendsStatus" class="status hidden"></div>
569
+ </div>
570
+ </div>
571
+
572
  <script>
573
  // Tab switching
574
  document.querySelectorAll('.tab-btn').forEach(btn => {
 
783
  }
784
  });
785
 
786
+ // ==========================================
787
+ // TRENDS MODULE
788
+ // ==========================================
789
+
790
+ // Trending Now Form
791
+ document.getElementById('trendingForm').addEventListener('submit', async (e) => {
792
+ e.preventDefault();
793
+ const results = document.getElementById('trendingResults');
794
+ results.innerHTML = '<p>⏳ Loading trends...</p>';
795
+
796
+ const data = {
797
+ country: document.getElementById('trendCountry').value,
798
+ limit: parseInt(document.getElementById('trendLimit').value)
799
+ };
800
+
801
+ try {
802
+ const res = await fetch('/api/trends/trending-now', {
803
+ method: 'POST',
804
+ headers: { 'Content-Type': 'application/json' },
805
+ body: JSON.stringify(data)
806
+ });
807
+ const result = await res.json();
808
+
809
+ if (result.success) {
810
+ let html = '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem; max-height: 400px; overflow-y: auto;">';
811
+ result.trends.forEach(t => {
812
+ html += `<div style="padding: 0.5rem 0; border-bottom: 1px solid var(--border);">
813
+ <span style="color: var(--accent); font-weight: bold;">#${t.rank}</span> ${t.topic}
814
+ </div>`;
815
+ });
816
+ html += '</div>';
817
+ results.innerHTML = html;
818
+ } else {
819
+ results.innerHTML = '<p style="color: var(--error);">❌ Error: ' + (result.detail || 'Failed') + '</p>';
820
+ }
821
+ } catch (err) {
822
+ results.innerHTML = '<p style="color: var(--error);">❌ Error: ' + err.message + '</p>';
823
+ }
824
+ });
825
+
826
+ // Keyword Research Form
827
+ document.getElementById('keywordForm').addEventListener('submit', async (e) => {
828
+ e.preventDefault();
829
+ const results = document.getElementById('keywordResults');
830
+ results.innerHTML = '<p>⏳ Analyzing keyword...</p>';
831
+
832
+ const data = {
833
+ keyword: document.getElementById('researchKeyword').value,
834
+ region: document.getElementById('researchRegion').value,
835
+ timeframe: document.getElementById('researchTimeframe').value,
836
+ category: document.getElementById('researchCategory').value,
837
+ search_type: document.getElementById('researchType').value
838
+ };
839
+
840
+ try {
841
+ const res = await fetch('/api/trends/keyword-research', {
842
+ method: 'POST',
843
+ headers: { 'Content-Type': 'application/json' },
844
+ body: JSON.stringify(data)
845
+ });
846
+ const result = await res.json();
847
+
848
+ if (result.success) {
849
+ let html = '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">';
850
+
851
+ // Related Topics
852
+ html += '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem;">';
853
+ html += '<h4 style="margin-bottom: 0.5rem;">πŸ“Œ Related Topics</h4>';
854
+ if (result.related_topics.top && result.related_topics.top.length > 0) {
855
+ result.related_topics.top.slice(0, 10).forEach(t => {
856
+ html += `<div style="padding: 0.25rem 0; font-size: 0.9rem;">${t.topic} <span style="color: var(--text-secondary);">(${t.value})</span></div>`;
857
+ });
858
+ } else {
859
+ html += '<p style="color: var(--text-secondary); font-size: 0.9rem;">No data</p>';
860
+ }
861
+ html += '</div>';
862
+
863
+ // Related Queries
864
+ html += '<div style="background: var(--bg-secondary); border-radius: 8px; padding: 1rem;">';
865
+ html += '<h4 style="margin-bottom: 0.5rem;">πŸ” Related Queries</h4>';
866
+ if (result.related_queries.top && result.related_queries.top.length > 0) {
867
+ result.related_queries.top.slice(0, 10).forEach(q => {
868
+ html += `<div style="padding: 0.25rem 0; font-size: 0.9rem;">${q.query} <span style="color: var(--text-secondary);">(${q.value})</span></div>`;
869
+ });
870
+ } else {
871
+ html += '<p style="color: var(--text-secondary); font-size: 0.9rem;">No data</p>';
872
+ }
873
+ html += '</div>';
874
+
875
+ html += '</div>';
876
+ results.innerHTML = html;
877
+ } else {
878
+ results.innerHTML = '<p style="color: var(--error);">❌ Error: ' + (result.detail || 'Failed') + '</p>';
879
+ }
880
+ } catch (err) {
881
+ results.innerHTML = '<p style="color: var(--error);">❌ Error: ' + err.message + '</p>';
882
+ }
883
+ });
884
+
885
  // ==========================================
886
  // GEMINI CHATBOT TEST
887
  // ==========================================