3v324v23 commited on
Commit
fd9a771
·
1 Parent(s): e4d66af

fix: Hugging Face Space deployment - health check, dependencies, startup script

Browse files
.dockerignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv_new/
2
+ venv/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ *.pyd
7
+ .Python
8
+ *.so
9
+ *.egg
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+ .git/
14
+ .gitignore
15
+ .env
16
+ *.log
17
+ .DS_Store
18
+ output/job-*
19
+ output/run-*
20
+ output/*.db
21
+ *.md
22
+ !README.md
23
+ test_*.py
24
+ debug_*.py
.env.example CHANGED
@@ -1,8 +1,24 @@
1
- # DataForSEO API Credentials (free tier: 100 requests/day)
2
- # Sign up at https://dataforseo.com/
3
- DATAFORSEO_LOGIN=your_dataforseo_login
4
- DATAFORSEO_PASSWORD=your_dataforseo_password
5
-
6
- OPENAI_API_KEY=your_openai_api_key
7
- SERPAPI_KEY=your_serpapi_key
8
- ZENSERP_KEY=your_zenserp_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GEO Platform Environment Variables
2
+ # Copy this file to .env and fill in your API keys
3
+
4
+ # OpenAI API Key (required for AI analysis)
5
+ OPENAI_API_KEY=your_openai_key_here
6
+
7
+ # Groq API Key (alternative to OpenAI, faster)
8
+ GROQ_API_KEY=your_groq_key_here
9
+
10
+ # Perplexity API Key (for AI visibility checks)
11
+ PERPLEXITY_KEY=your_perplexity_key_here
12
+
13
+ # SerpAPI Key (for search results analysis)
14
+ SERPAPI_KEY=your_serpapi_key_here
15
+
16
+ # ZenSerp API Key (alternative to SerpAPI)
17
+ ZENSERP_KEY=your_zenserp_key_here
18
+
19
+ # DataForSEO Credentials (for SEO data)
20
+ DATAFORSEO_LOGIN=your_email@example.com
21
+ DATAFORSEO_PASSWORD=your_password_here
22
+
23
+ # Output directory (optional, defaults to ./output)
24
+ OUTPUT_DIR=/tmp/geo-output
COMPETITOR_INTEL_V2.md ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 Competitor Intelligence v2 — التحسينات المطبقة
2
+
3
+ ## ✅ المشاكل التي تم حلها
4
+
5
+ ### 1. ❌ مشكلة "نوع المنافسين" (BIGGEST PROBLEM) → ✅ محلولة
6
+
7
+ **المشكلة السابقة:**
8
+ - موقع `abayanoir.com` (eCommerce / fashion) يظهر له `digital marketing agencies` ❌
9
+
10
+ **الحل المطبق:**
11
+ ```python
12
+ def detect_niche(domain, url, industry_hint, api_keys):
13
+ """
14
+ ✅ AI يكتشف النيش الحقيقي من:
15
+ - اسم الدومين (abaya → fashion ecommerce)
16
+ - محتوى الموقع
17
+ - industry_hint من المستخدم
18
+ """
19
+
20
+ def _ai_filter_competitors(candidates, niche_data, region, api_keys):
21
+ """
22
+ ✅ AI يفلتر النتائج ويزيل:
23
+ - Directories (yellowpages, clutch)
24
+ - Agencies (when looking for ecommerce)
25
+ - Social media
26
+ - Blogs
27
+
28
+ ✅ AI يصنف كل منافس:
29
+ - Direct (نفس المنتج/الخدمة)
30
+ - Indirect (مرتبط)
31
+ - Aspirational (علامة كبيرة)
32
+ """
33
+ ```
34
+
35
+ **النتيجة:**
36
+ - `abayanoir.com` → يكتشف "fashion ecommerce" → يبحث عن "عبايات السعودية" → يجد منافسين حقيقيين ✅
37
+
38
+ ---
39
+
40
+ ### 2. ❌ مفيش Scoring حقيقي → ✅ Scoring System (0-100)
41
+
42
+ **الحل المطبق:**
43
+ ```python
44
+ def calculate_competitor_score(ps, content, serp_pos):
45
+ """
46
+ Weighted Formula:
47
+ - SEO (25%) — PageSpeed SEO score
48
+ - Performance (20%) — PageSpeed performance
49
+ - Content depth (20%) — word count, blog, FAQ, schema
50
+ - Authority (20%) — SERP position, reviews, HTTPS
51
+ - GEO fit (15%) — Arabic content, local signals
52
+
53
+ Returns: {
54
+ 'total': 78, # 0-100
55
+ 'grade': 'B', # A/B/C/D
56
+ 'breakdown': {...}
57
+ }
58
+ """
59
+ ```
60
+
61
+ **النتيجة:**
62
+ - كل منافس له Score واضح: `78/100` ✅
63
+ - Grade: `A` (85+), `B` (70+), `C` (55+), `D` (<55) ✅
64
+ - ترتيب تلقائي حسب الـ Score ✅
65
+
66
+ ---
67
+
68
+ ### 3. ❌ AI Output Generic → ✅ Grounded AI Insights
69
+
70
+ **المشكلة السابقة:**
71
+ ```
72
+ "Creating engaging content" ❌
73
+ "Optimizing website" ❌
74
+ ```
75
+
76
+ **الحل المطبق:**
77
+ ```python
78
+ prompt = f"""
79
+ Generate SPECIFIC, DATA-DRIVEN insights. NO generic advice.
80
+ Every insight must reference actual data.
81
+
82
+ Example:
83
+ ❌ "improve SEO"
84
+ ✅ "SEO score 78 vs competitor.com 92 — add FAQ schema (5/7 competitors missing it)"
85
+
86
+ ❌ "create content"
87
+ ✅ "Add Arabic blog — only 2/7 competitors have it, opportunity for 'عبايات سوداء فاخرة' keyword"
88
+ """
89
+ ```
90
+
91
+ **النتيجة:**
92
+ ```json
93
+ {
94
+ "your_strengths": ["SEO score 85 beats 4/7 competitors"],
95
+ "your_weaknesses": ["abayanoir.com has no blog while competitor.com publishes 2x/week"],
96
+ "quick_wins": [
97
+ {"win": "Add FAQ schema - 5/7 competitors missing it", "effort": "Low"}
98
+ ],
99
+ "opportunities": [
100
+ {"action": "Add Arabic content - only 2/7 competitors have it", "impact": "High"}
101
+ ]
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ### 4. ❌ مفيش Data حقيقية → ✅ Real Data Grounding
108
+
109
+ **المصادر المستخدمة:**
110
+
111
+ 1. **Google PageSpeed API** (مجاني 100%)
112
+ - Performance, SEO, Accessibility scores
113
+ - Core Web Vitals (FCP, LCP, CLS, TBT)
114
+ - HTTPS check
115
+
116
+ 2. **SerpAPI** (100 بحث/شهر مجاناً)
117
+ - نتائج Google حقيقية
118
+ - SERP position
119
+ - Snippets
120
+
121
+ 3. **Content Scraping** (مجاني)
122
+ - Word count
123
+ - Arabic detection
124
+ - Schema.org check
125
+ - Blog/FAQ/Reviews detection
126
+ - Image/Video count
127
+
128
+ 4. **Groq AI** (مجاني)
129
+ - Niche detection
130
+ - Competitor filtering
131
+ - Strategic insights
132
+
133
+ **النتيجة:**
134
+ - كل insight مبني على data حقيقية ✅
135
+ - مفيش "تخمين" من AI ✅
136
+
137
+ ---
138
+
139
+ ### 5. ❌ مفيش Differentiation → ✅ Decision Engine
140
+
141
+ **التحول:**
142
+
143
+ **قبل:**
144
+ ```
145
+ "ChatGPT + list competitors" ❌
146
+ ```
147
+
148
+ **بعد:**
149
+ ```
150
+ ✅ Niche Detection (AI detects what you actually sell)
151
+ ✅ Smart Keyword Generation (niche-specific, not generic)
152
+ ✅ Competitor Discovery (SerpAPI + AI filtering)
153
+ ✅ Data Enrichment (PageSpeed + content signals)
154
+ ✅ Scoring Engine (weighted formula 0-100)
155
+ ✅ Segmentation (Direct / Indirect / Aspirational)
156
+ ✅ Grounded AI Insights (specific, not generic)
157
+ ✅ GEO Intelligence (regional fit per competitor)
158
+ ✅ Quick Wins (specific keyword opportunities)
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🚀 الميزات الجديدة
164
+
165
+ ### 1. Competitor Segmentation
166
+ ```javascript
167
+ // UI Tabs
168
+ [الكل] [منافسون مباشرون] [غير مباشرين] [علامات كبرى]
169
+
170
+ // Backend Classification
171
+ {
172
+ "segmentation": {
173
+ "direct": [...], // Same product/service
174
+ "indirect": [...], // Related
175
+ "aspirational": [...] // Big brands
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### 2. Market Position Ranking
181
+ ```python
182
+ # Your rank among all competitors
183
+ your_rank = 3 # #3 out of 8 sites
184
+ market_position = "Top 3" # Leader | Top 3 | Challenger | Newcomer
185
+ ```
186
+
187
+ ### 3. Enhanced UI
188
+ - Score badges (0-100) with color coding
189
+ - Grade display (A/B/C/D)
190
+ - Type badges (مباشر / غير مباشر / علامة كبرى)
191
+ - Filterable by segment
192
+ - Real-time data source indicators
193
+
194
+ ---
195
+
196
+ ## 📊 مثال على النتيجة النهائية
197
+
198
+ ### Input:
199
+ ```
200
+ URL: https://abayanoir.com
201
+ Region: Saudi Arabia
202
+ Industry: (auto-detected)
203
+ ```
204
+
205
+ ### Output:
206
+ ```json
207
+ {
208
+ "your_domain": "abayanoir.com",
209
+ "your_score": {
210
+ "total": 78,
211
+ "grade": "B",
212
+ "breakdown": {
213
+ "seo": 85,
214
+ "performance": 72,
215
+ "content": 80,
216
+ "authority": 75,
217
+ "geo_fit": 78
218
+ }
219
+ },
220
+ "your_rank": 3,
221
+ "market_position": "Top 3",
222
+ "niche": "fashion ecommerce - abayas",
223
+ "niche_detected": true,
224
+ "competitors": [
225
+ {
226
+ "domain": "competitor1.com",
227
+ "score": {"total": 92, "grade": "A"},
228
+ "competitor_type": "Direct",
229
+ "serp_position": 1,
230
+ "pagespeed": {...},
231
+ "content": {
232
+ "has_arabic": true,
233
+ "has_blog": true,
234
+ "word_count": 2500
235
+ }
236
+ }
237
+ ],
238
+ "insights": {
239
+ "market_position": "Top 3",
240
+ "market_summary": "abayanoir.com ranks #3 in Saudi fashion ecommerce. competitor1.com leads with score 92 vs your 78.",
241
+ "your_strengths": [
242
+ "SEO score 85 beats 5/7 competitors",
243
+ "Arabic content present (only 3/7 have it)"
244
+ ],
245
+ "your_weaknesses": [
246
+ "No blog while competitor1.com publishes 2x/week",
247
+ "Word count 800 vs competitor1.com 2500"
248
+ ],
249
+ "quick_wins": [
250
+ {
251
+ "win": "Add FAQ schema - 5/7 competitors missing it",
252
+ "keyword": "عبايات سوداء فاخرة",
253
+ "effort": "Low"
254
+ }
255
+ ],
256
+ "opportunities": [
257
+ {
258
+ "action": "Start Arabic blog targeting 'عبايات للمناسبات'",
259
+ "reason": "Only 2/7 competitors have blogs",
260
+ "impact": "High"
261
+ }
262
+ ]
263
+ }
264
+ }
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 🎯 التقييم النهائي
270
+
271
+ | العنصر | قبل | بعد |
272
+ |--------|-----|-----|
273
+ | الفكرة | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
274
+ | التنفيذ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
275
+ | الدقة | ⭐ | ⭐⭐⭐⭐⭐ |
276
+ | القيمة الفعلية | ⭐⭐ | ⭐⭐⭐⭐⭐ |
277
+ | قابلية التطوير | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
278
+
279
+ ---
280
+
281
+ ## 🔥 الخطوات التالية (اختياري)
282
+
283
+ ### 1. Traffic Estimation (يحتاج SimilarWeb API)
284
+ ```python
285
+ def get_traffic_estimate(domain):
286
+ # Monthly visits
287
+ # Traffic sources (organic, direct, social)
288
+ # Top countries
289
+ ```
290
+
291
+ ### 2. Keyword Gap Analysis (يحتاج SEMrush/Ahrefs API)
292
+ ```python
293
+ def keyword_gap(your_domain, competitor_domain):
294
+ # Keywords they rank for but you don't
295
+ # Opportunity score per keyword
296
+ ```
297
+
298
+ ### 3. Backlink Analysis (يحتاج Ahrefs API)
299
+ ```python
300
+ def backlink_profile(domain):
301
+ # Domain Rating
302
+ # Referring domains
303
+ # Top backlinks
304
+ ```
305
+
306
+ ### 4. Content Gap (AI-based, مجاني)
307
+ ```python
308
+ def content_gap(your_content, competitor_content):
309
+ # Topics they cover but you don't
310
+ # Content types (video, infographic, etc.)
311
+ ```
312
+
313
+ ---
314
+
315
+ ## 📝 الملخص
316
+
317
+ ✅ **تم حل جميع المشاكل الحرجة:**
318
+ 1. ✅ تصنيف المنافسين دقيق (AI + filtering)
319
+ 2. ✅ Scoring System حقيقي (0-100)
320
+ 3. ✅ AI Insights محددة (not generic)
321
+ 4. ✅ Data grounding (PageSpeed + SERP + scraping)
322
+ 5. ✅ Differentiation واضح (Decision Engine)
323
+
324
+ 🚀 **المشروع الآن:**
325
+ - من "ChatGPT + list" → **AI Competitive Intelligence System**
326
+ - من "عرض بيانات" → **Decision Engine**
327
+ - جاهز للتطوير إلى **SaaS startup** 🎯
Dockerfile CHANGED
@@ -4,7 +4,7 @@ WORKDIR /app
4
 
5
  # Install system deps
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
- gcc g++ && \
8
  rm -rf /var/lib/apt/lists/*
9
 
10
  # Install Python deps
@@ -17,10 +17,17 @@ RUN python -m spacy download en_core_web_sm
17
  # Copy project
18
  COPY . .
19
 
 
 
 
20
  # Create output dir
21
  ENV OUTPUT_DIR=/tmp/geo-output
22
  RUN mkdir -p /tmp/geo-output
23
 
 
 
 
 
24
  EXPOSE 7860
25
 
26
- CMD ["uvicorn", "server.api:app", "--host", "0.0.0.0", "--port", "7860"]
 
4
 
5
  # Install system deps
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ gcc g++ curl && \
8
  rm -rf /var/lib/apt/lists/*
9
 
10
  # Install Python deps
 
17
  # Copy project
18
  COPY . .
19
 
20
+ # Make startup script executable
21
+ RUN chmod +x start.sh
22
+
23
  # Create output dir
24
  ENV OUTPUT_DIR=/tmp/geo-output
25
  RUN mkdir -p /tmp/geo-output
26
 
27
+ # Health check
28
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
29
+ CMD curl -f http://localhost:7860/health || exit 1
30
+
31
  EXPOSE 7860
32
 
33
+ CMD ["./start.sh"]
HUGGINGFACE_DEPLOYMENT.md ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Space Deployment Guide
2
+
3
+ ## Quick Fix for "No Logs" Issue
4
+
5
+ ### Problem
6
+ Your Hugging Face Space shows "No logs" and the app doesn't start.
7
+
8
+ ### Solution
9
+
10
+ **1. Check Build Logs First**
11
+ - Go to your Space settings
12
+ - Click on "Logs" tab
13
+ - Select "build" (not "container")
14
+ - Look for error messages during Docker build
15
+
16
+ **2. Common Issues & Fixes**
17
+
18
+ #### Issue: Missing Dependencies
19
+ **Fix:** Updated `requirements.txt` with all required packages:
20
+ ```
21
+ requests>=2.28
22
+ beautifulsoup4>=4.11
23
+ spacy>=3.5
24
+ python-dotenv>=1.0
25
+ fastapi>=0.95
26
+ uvicorn[standard]>=0.22
27
+ openai>=1.0
28
+ groq>=0.1
29
+ lxml>=4.9
30
+ html5lib>=1.1
31
+ ```
32
+
33
+ #### Issue: No Health Check
34
+ **Fix:** Added `/health` endpoint in `server/api.py`:
35
+ ```python
36
+ @app.get('/health')
37
+ async def health_check():
38
+ return {'status': 'healthy', 'service': 'GEO Platform'}
39
+ ```
40
+
41
+ #### Issue: Large Docker Image
42
+ **Fix:** Created `.dockerignore` to exclude unnecessary files:
43
+ ```
44
+ venv_new/
45
+ __pycache__/
46
+ *.pyc
47
+ output/job-*
48
+ test_*.py
49
+ ```
50
+
51
+ #### Issue: Startup Failures
52
+ **Fix:** Created `start.sh` with error handling:
53
+ ```bash
54
+ #!/bin/bash
55
+ set -e
56
+ mkdir -p /tmp/geo-output
57
+ python -m spacy download en_core_web_sm
58
+ exec uvicorn server.api:app --host 0.0.0.0 --port 7860
59
+ ```
60
+
61
+ **3. Environment Variables**
62
+
63
+ Hugging Face Spaces requires secrets to be set in Settings > Repository secrets:
64
+
65
+ Required secrets:
66
+ - `OPENAI_API_KEY` - For AI analysis
67
+ - `GROQ_API_KEY` - Alternative AI backend
68
+ - `SERPAPI_KEY` - For search analysis (optional)
69
+
70
+ **4. Dockerfile Configuration**
71
+
72
+ Updated Dockerfile includes:
73
+ - Health check endpoint
74
+ - Proper timeout settings
75
+ - Startup script for graceful error handling
76
+ - Minimal system dependencies
77
+
78
+ **5. Verify Deployment**
79
+
80
+ After pushing changes:
81
+
82
+ ```bash
83
+ git add .
84
+ git commit -m "fix: Hugging Face Space deployment configuration"
85
+ git push
86
+ ```
87
+
88
+ Then check:
89
+ 1. Build logs complete without errors
90
+ 2. Container logs show "Starting FastAPI server"
91
+ 3. Health check returns 200 OK
92
+ 4. App loads at your-space-url.hf.space
93
+
94
+ **6. Test Locally First**
95
+
96
+ Before deploying to Hugging Face:
97
+
98
+ ```bash
99
+ # Build Docker image
100
+ docker build -t geo-platform .
101
+
102
+ # Run container
103
+ docker run -p 7860:7860 geo-platform
104
+
105
+ # Test health endpoint
106
+ curl http://localhost:7860/health
107
+ ```
108
+
109
+ **7. Debugging Container Logs**
110
+
111
+ If container still shows "No logs":
112
+
113
+ 1. Check Space is set to "Running" (not "Sleeping")
114
+ 2. Verify port 7860 is exposed in Dockerfile
115
+ 3. Check app_port in README.md matches (should be 7860)
116
+ 4. Restart the Space from Settings
117
+
118
+ **8. README.md Configuration**
119
+
120
+ Ensure your README.md has correct metadata:
121
+
122
+ ```yaml
123
+ ---
124
+ title: GEO Platform
125
+ emoji: 🌍
126
+ colorFrom: blue
127
+ colorTo: purple
128
+ sdk: docker
129
+ app_port: 7860
130
+ pinned: false
131
+ ---
132
+ ```
133
+
134
+ ## Success Indicators
135
+
136
+ ✅ Build logs show "Successfully built"
137
+ ✅ Container logs show "Starting FastAPI server"
138
+ ✅ `/health` endpoint returns 200
139
+ ✅ Homepage loads without errors
140
+ ✅ No "Application startup failed" errors
141
+
142
+ ## Still Not Working?
143
+
144
+ 1. Check Hugging Face Space status page
145
+ 2. Verify your account has Docker SDK enabled
146
+ 3. Try rebuilding from Settings > Factory reboot
147
+ 4. Check Space visibility (Public vs Private)
148
+ 5. Review Hugging Face Spaces documentation
149
+
150
+ ## Performance Optimization
151
+
152
+ For production deployment:
153
+ - Enable persistent storage for database
154
+ - Set up proper logging
155
+ - Configure rate limiting
156
+ - Add monitoring endpoints
157
+ - Use environment variables for all secrets
158
+
159
+ ## Support
160
+
161
+ If issues persist:
162
+ - Check Hugging Face Community forums
163
+ - Review Space logs carefully
164
+ - Test Docker build locally first
165
+ - Verify all dependencies are compatible
frontend/competitor-intel-v2.html ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ar" dir="rtl">
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
6
+ <title>محلل المنافسين الذكي — GEO Platform</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="/theme.css"/>
9
+ <style>
10
+ body { font-family: 'Cairo', sans-serif; }
11
+ .config-card {
12
+ background: var(--surface); border: 1px solid var(--border);
13
+ border-radius: 24px; padding: 36px; backdrop-filter: blur(20px);
14
+ position: relative; overflow: hidden;
15
+ }
16
+ .config-card::before {
17
+ content: ''; position: absolute; top: -1px; left: 10%; right: 10%; height: 2px;
18
+ background: var(--accent-grad);
19
+ }
20
+ .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 24px; }
21
+ .form-group { display: flex; flex-direction: column; gap: 6px; }
22
+ .form-group label { font-size: 13px; color: var(--muted); font-weight: 600; }
23
+ .form-group input, .form-group select {
24
+ background: rgba(0,0,0,0.4); border: 1px solid var(--border);
25
+ color: white; padding: 14px 18px; border-radius: 12px;
26
+ font-size: 14px; font-family: 'Cairo', sans-serif; transition: all 0.3s;
27
+ }
28
+ .form-group input:focus, .form-group select:focus {
29
+ outline: none; border-color: var(--accent); box-shadow: 0 0 20px rgba(0,242,255,0.1);
30
+ }
31
+ .form-group.full { grid-column: 1 / -1; }
32
+ .btn-analyze {
33
+ background: var(--accent-grad); color: #000;
34
+ font-family: 'Cairo', sans-serif; font-weight: 800; font-size: 15px;
35
+ padding: 16px 40px; border-radius: 16px; border: none; cursor: pointer;
36
+ box-shadow: 0 8px 25px rgba(0,242,255,0.3); transition: all 0.3s; margin-top: 8px;
37
+ }
38
+ .btn-analyze:hover { transform: translateY(-3px); box-shadow: 0 15px 40px rgba(0,242,255,0.5); }
39
+ .btn-analyze:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
40
+ #results { display: none; margin-top: 32px; }
41
+ .your-card {
42
+ background: rgba(0,242,255,0.04); border: 1px solid rgba(0,242,255,0.2);
43
+ border-radius: 20px; padding: 24px; margin-bottom: 24px;
44
+ }
45
+ .seg-tab {
46
+ background: rgba(255,255,255,0.04); border: 1px solid var(--border);
47
+ color: var(--muted); padding: 8px 16px; border-radius: 10px;
48
+ font-size: 13px; font-weight: 600; cursor: pointer;
49
+ transition: all 0.3s; font-family: 'Cairo', sans-serif;
50
+ }
51
+ .seg-tab:hover { border-color: var(--accent); color: var(--text); }
52
+ .seg-tab.active { background: var(--accent-grad); color: #000; border-color: transparent; font-weight: 800; }
53
+ .comp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-top: 20px; }
54
+ .comp-card {
55
+ background: var(--surface); border: 1px solid var(--border);
56
+ border-radius: 20px; padding: 24px; transition: all 0.4s cubic-bezier(0.16,1,0.3,1);
57
+ position: relative; overflow: hidden;
58
+ }
59
+ .comp-card:hover { border-color: rgba(0,242,255,0.3); transform: translateY(-6px); box-shadow: 0 20px 60px rgba(0,0,0,0.4); }
60
+ .comp-rank {
61
+ position: absolute; top: 16px; left: 16px; width: 32px; height: 32px; border-radius: 50%;
62
+ background: var(--accent-grad); color: #000; font-weight: 900; font-size: 13px;
63
+ display: flex; align-items: center; justify-content: center;
64
+ }
65
+ .comp-type-badge {
66
+ position: absolute; top: 16px; right: 16px;
67
+ font-size: 10px; padding: 4px 10px; border-radius: 8px;
68
+ font-weight: 700; text-transform: uppercase;
69
+ }
70
+ .type-direct { background: rgba(0,255,149,0.1); color: var(--green); border: 1px solid rgba(0,255,149,0.2); }
71
+ .type-indirect { background: rgba(255,204,0,0.1); color: var(--yellow); border: 1px solid rgba(255,204,0,0.2); }
72
+ .type-aspirational { background: rgba(124,58,237,0.1); color: #a78bfa; border: 1px solid rgba(124,58,237,0.2); }
73
+ .comp-domain { font-weight: 800; font-size: 16px; margin-bottom: 6px; padding-right: 8px; }
74
+ .comp-snippet { font-size: 12px; color: var(--muted); line-height: 1.6; margin-bottom: 16px; }
75
+ .comp-score-big { font-size: 28px; font-weight: 900; margin: 12px 0 8px; text-align: center; }
76
+ .comp-grade { text-align: center; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
77
+ .speed-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
78
+ .speed-item { text-align: center; }
79
+ .speed-score { font-size: 22px; font-weight: 900; display: block; margin-bottom: 2px; }
80
+ .speed-label { font-size: 10px; color: var(--muted); text-transform: uppercase; }
81
+ .vitals-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
82
+ .vital-badge {
83
+ font-size: 11px; padding: 3px 8px; border-radius: 6px;
84
+ background: rgba(255,255,255,0.04); border: 1px solid var(--border); color: var(--muted);
85
+ }
86
+ .ai-card {
87
+ background: linear-gradient(135deg, rgba(124,58,237,0.08), rgba(0,242,255,0.04));
88
+ border: 1px solid rgba(124,58,237,0.2); border-radius: 20px; padding: 28px; margin-top: 24px;
89
+ }
90
+ .ai-section { margin-bottom: 20px; }
91
+ .ai-section h4 { font-size: 14px; font-weight: 700; color: var(--accent); margin-bottom: 10px; }
92
+ .ai-item {
93
+ font-size: 13px; color: var(--muted); padding: 8px 12px;
94
+ background: rgba(255,255,255,0.02); border-radius: 8px;
95
+ margin-bottom: 6px; border-right: 3px solid var(--accent); line-height: 1.6;
96
+ }
97
+ .data-sources { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
98
+ .source-badge { font-size: 11px; padding: 4px 12px; border-radius: 20px; font-weight: 700; }
99
+ .source-active { background: rgba(0,255,149,0.1); color: var(--green); border: 1px solid rgba(0,255,149,0.2); }
100
+ .source-demo { background: rgba(255,204,0,0.1); color: var(--yellow); border: 1px solid rgba(255,204,0,0.2); }
101
+ @media(max-width:768px) {
102
+ .form-grid { grid-template-columns: 1fr; }
103
+ .speed-grid { grid-template-columns: 1fr 1fr; }
104
+ }
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <nav>
109
+ <div class="nav-logo">GEO<span>.</span>AI</div>
110
+ <div class="nav-links">
111
+ <a href="/jobs.html">المهام</a>
112
+ <a href="/recommendations.html">التوصيات</a>
113
+ <a href="/search.html">تحليل البحث</a>
114
+ <a href="/content_v2.html">المحتوى</a>
115
+ <a href="/ads.html">الإعلانات</a>
116
+ <a href="/competitor-intel.html" style="color:var(--accent)">المنافسون</a>
117
+ </div>
118
+ <a href="/" class="nav-cta">الرئيسية</a>
119
+ </nav>
120
+
121
+ <div class="wrap" style="padding-top:100px;max-width:1200px">
122
+ <div class="stagger-item" style="animation-delay:0.1s;margin-bottom:32px">
123
+ <h1 class="shine-text" style="font-size:42px;margin-bottom:10px">🧠 محلل المنافسين الذكي v2</h1>
124
+ <p style="color:var(--muted);font-size:16px">اكتشف منافسيك الحقيقيين + Scoring System + AI Insights محددة</p>
125
+ <div class="data-sources" style="margin-top:12px">
126
+ <span class="source-badge source-active">✅ Google PageSpeed — مجاني 100%</span>
127
+ <span id="serp-badge" class="source-badge source-demo">⚡ SerpAPI — 100 بحث/شهر مجاناً</span>
128
+ <span id="ai-badge" class="source-badge source-demo">🤖 Groq AI — تحليل استراتيجي</span>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="config-card stagger-item" style="animation-delay:0.2s">
133
+ <h2 style="margin:0 0 4px;font-size:22px">إعداد التحليل</h2>
134
+ <p style="color:var(--muted);font-size:14px;margin:0">أدخل موقعك وحدد المنطقة للعثور على منافسيك الحقيقيين</p>
135
+ <div class="form-grid">
136
+ <div class="form-group full">
137
+ <label>رابط موقعك</label>
138
+ <input id="yourUrl" type="url" placeholder="https://abayanoir.com" value=""/>
139
+ </div>
140
+ <div class="form-group">
141
+ <label>المنطقة المستهدفة</label>
142
+ <select id="region">
143
+ <option value="Saudi Arabia">🇸🇦 السعودية</option>
144
+ <option value="Egypt">🇪🇬 مصر</option>
145
+ <option value="UAE">🇦🇪 الإمارات</option>
146
+ <option value="Kuwait">🇰🇼 الكويت</option>
147
+ <option value="Jordan">🇯🇴 الأردن</option>
148
+ <option value="Global">🌍 عالمي</option>
149
+ </select>
150
+ </div>
151
+ <div class="form-group">
152
+ <label>المجال / النيش (اختياري)</label>
153
+ <input id="industry" placeholder="مثال: عبايات، تسويق رقمي"/>
154
+ </div>
155
+ <div class="form-group">
156
+ <label>عدد المنافسين</label>
157
+ <select id="count">
158
+ <option value="5">5 منافسين</option>
159
+ <option value="7" selected>7 منافسين</option>
160
+ <option value="10">10 منافسين</option>
161
+ </select>
162
+ </div>
163
+ <div class="form-group" style="align-self:end">
164
+ <button class="btn-analyze" id="analyzeBtn" onclick="runAnalysis()">🔍 تحليل المنافسين</button>
165
+ </div>
166
+ </div>
167
+ <div id="status" style="margin-top:12px;font-size:13px;color:var(--muted)"></div>
168
+ </div>
169
+
170
+ <div id="results">
171
+ <div class="your-card stagger-item">
172
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
173
+ <div>
174
+ <div style="font-size:12px;color:var(--accent);font-weight:700;margin-bottom:4px">موقعك</div>
175
+ <div id="yourDomain" style="font-size:20px;font-weight:800"></div>
176
+ </div>
177
+ <div style="text-align:left">
178
+ <div id="yourScore" style="font-size:32px;font-weight:900;color:var(--accent)"></div>
179
+ <div id="yourRank" style="font-size:13px;color:var(--muted)"></div>
180
+ </div>
181
+ </div>
182
+ <div class="speed-grid" id="yourSpeed"></div>
183
+ <div class="vitals-row" id="yourVitals"></div>
184
+ </div>
185
+
186
+ <div style="display:flex;gap:12px;margin:24px 0 16px;flex-wrap:wrap">
187
+ <button class="seg-tab active" data-seg="all" onclick="filterSegment('all')">الكل</button>
188
+ <button class="seg-tab" data-seg="direct" onclick="filterSegment('direct')">🎯 منافسون مباشرون</button>
189
+ <button class="seg-tab" data-seg="indirect" onclick="filterSegment('indirect')">🔗 غير مباشرين</button>
190
+ <button class="seg-tab" data-seg="aspirational" onclick="filterSegment('aspirational')">⭐ علامات كبرى</button>
191
+ </div>
192
+
193
+ <h2 style="font-size:20px;font-weight:800;margin-bottom:4px">
194
+ المنافسون المكتشفون <span id="compCount" style="color:var(--accent);font-size:16px"></span>
195
+ </h2>
196
+ <p style="color:var(--muted);font-size:13px;margin-bottom:0" id="dataSourceNote"></p>
197
+ <div class="comp-grid" id="compGrid"></div>
198
+
199
+ <div class="ai-card stagger-item" id="aiCard" style="display:none">
200
+ <h3 style="margin:0 0 20px;font-size:20px;font-weight:800;color:#a78bfa">🧠 التحليل الاستراتيجي بالذكاء الاصطناعي</h3>
201
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px" id="aiGrid"></div>
202
+ <div id="aiSummary" style="margin-top:16px;padding:14px;background:rgba(255,255,255,0.02);border-radius:10px;font-size:13px;color:var(--muted);line-height:1.7"></div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <script>
208
+ const storedKeys = JSON.parse(localStorage.getItem('geo_keys') || '{}');
209
+ if (storedKeys.serpapi || storedKeys.serp) {
210
+ document.getElementById('serp-badge').className = 'source-badge source-active';
211
+ document.getElementById('serp-badge').textContent = '✅ SerpAPI — متصل';
212
+ }
213
+ if (storedKeys.groq || storedKeys.openai) {
214
+ document.getElementById('ai-badge').className = 'source-badge source-active';
215
+ document.getElementById('ai-badge').textContent = '✅ AI — متصل';
216
+ }
217
+
218
+ let currentData = null;
219
+ let currentSegment = 'all';
220
+
221
+ function scoreColor(s) {
222
+ if (s === null || s === undefined) return 'var(--muted)';
223
+ return s >= 80 ? 'var(--green)' : s >= 50 ? 'var(--yellow)' : 'var(--red)';
224
+ }
225
+
226
+ function renderSpeedGrid(ps, containerId, vitalsId) {
227
+ const el = document.getElementById(containerId);
228
+ const vitalsEl = document.getElementById(vitalsId);
229
+ if (!ps) { el.innerHTML = '<div style="color:var(--muted);font-size:13px">جارٍ تحميل...</div>'; return; }
230
+ const metrics = [
231
+ { label: 'الأداء', val: ps.performance },
232
+ { label: 'SEO', val: ps.seo },
233
+ { label: 'وصول', val: ps.accessibility },
234
+ ];
235
+ el.innerHTML = metrics.map(m => `
236
+ <div class="speed-item">
237
+ <span class="speed-score" style="color:${scoreColor(m.val)}">${m.val ?? '—'}</span>
238
+ <span class="speed-label">${m.label}</span>
239
+ </div>`).join('');
240
+ vitalsEl.innerHTML = [['FCP', ps.fcp], ['LCP', ps.lcp], ['CLS', ps.cls], ['TBT', ps.tbt]]
241
+ .map(([k,v]) => `<span class="vital-badge">${k}: ${v}</span>`).join('');
242
+ }
243
+
244
+ function filterSegment(seg) {
245
+ currentSegment = seg;
246
+ document.querySelectorAll('.seg-tab').forEach(t => t.classList.remove('active'));
247
+ document.querySelector(`[data-seg="${seg}"]`).classList.add('active');
248
+ if (currentData) renderCompetitors(currentData.competitors, seg);
249
+ }
250
+
251
+ function renderCompetitors(competitors, segment = 'all') {
252
+ const grid = document.getElementById('compGrid');
253
+ grid.innerHTML = '';
254
+ let filtered = competitors;
255
+ if (segment !== 'all') {
256
+ filtered = competitors.filter(c => (c.competitor_type || 'Direct').toLowerCase() === segment.toLowerCase());
257
+ }
258
+ if (filtered.length === 0) {
259
+ grid.innerHTML = '<div style="color:var(--muted);padding:40px;text-align:center">لا يوجد منافسون في هذه الفئة</div>';
260
+ return;
261
+ }
262
+ filtered.forEach((c, i) => {
263
+ const ps = c.pagespeed || {};
264
+ const score = c.score || {};
265
+ const typeClass = `type-${(c.competitor_type || 'direct').toLowerCase()}`;
266
+ const typeLabel = { 'Direct': 'مباشر', 'Indirect': 'غير مباشر', 'Aspirational': 'علامة كبرى' }[c.competitor_type] || 'مباشر';
267
+ const card = document.createElement('div');
268
+ card.className = 'comp-card stagger-item';
269
+ card.style.animationDelay = `${i * 0.05}s`;
270
+ card.innerHTML = `
271
+ <div class="comp-rank">${c.serp_position || i+1}</div>
272
+ <div class="comp-type-badge ${typeClass}">${typeLabel}</div>
273
+ <div class="comp-domain">${c.domain}</div>
274
+ <div class="comp-snippet">${c.snippet || c.title || ''}</div>
275
+ <div class="comp-score-big" style="color:${scoreColor(score.total)}">${score.total || '—'}</div>
276
+ <div class="comp-grade">��قييم ${score.grade || '?'}</div>
277
+ <div class="speed-grid" style="margin-top:16px">
278
+ ${[['SEO', ps.seo], ['أداء', ps.performance], ['وصول', ps.accessibility]].map(([l,v]) => `
279
+ <div class="speed-item">
280
+ <span class="speed-score" style="color:${scoreColor(v)};font-size:18px">${v ?? '—'}</span>
281
+ <span class="speed-label">${l}</span>
282
+ </div>`).join('')}
283
+ </div>
284
+ <div class="vitals-row" style="margin-top:12px">
285
+ ${[['FCP', ps.fcp], ['LCP', ps.lcp], ['CLS', ps.cls]].map(([k,v]) => `<span class="vital-badge">${k}: ${v}</span>`).join('')}
286
+ </div>
287
+ <div style="margin-top:12px">
288
+ <a href="https://${c.domain}" target="_blank" style="font-size:12px;color:var(--accent);text-decoration:none">زيارة الموقع ↗</a>
289
+ </div>`;
290
+ grid.appendChild(card);
291
+ });
292
+ }
293
+
294
+ function renderAIAnalysis(insights) {
295
+ if (!insights || !insights.market_summary) return;
296
+ document.getElementById('aiCard').style.display = 'block';
297
+ const grid = document.getElementById('aiGrid');
298
+ const sections = [
299
+ { title: '🎯 موقعك في السوق', items: [insights.market_position || 'Challenger'] },
300
+ { title: '💪 نقاط قوتك', items: insights.your_strengths || [] },
301
+ { title: '⚠️ نقاط ضعفك', items: insights.your_weaknesses || [] },
302
+ { title: '⚡ انتصارات سريعة', items: (insights.quick_wins || []).map(w => w.win || w) },
303
+ { title: '🎓 فرص ذهبية', items: (insights.opportunities || []).map(o => o.action || o) },
304
+ { title: '🔴 تهديدات', items: (insights.direct_threats || []).map(t => `${t.competitor}: ${t.threat}`) },
305
+ ];
306
+ grid.innerHTML = sections.map(s => {
307
+ const items = Array.isArray(s.items)
308
+ ? s.items.map(v => `<div class="ai-item">${v}</div>`).join('')
309
+ : `<div class="ai-item" style="font-size:16px;font-weight:800;color:var(--accent)">${s.items}</div>`;
310
+ return `<div class="ai-section"><h4>${s.title}</h4>${items}</div>`;
311
+ }).join('');
312
+ if (insights.market_summary) {
313
+ document.getElementById('aiSummary').innerHTML = `<strong style="color:var(--text)">ملخص السوق:</strong> ${insights.market_summary}`;
314
+ }
315
+ }
316
+
317
+ async function runAnalysis() {
318
+ const url = document.getElementById('yourUrl').value.trim();
319
+ if (!url) { document.getElementById('status').textContent = 'أدخل رابط موقعك أولاً'; return; }
320
+ const btn = document.getElementById('analyzeBtn');
321
+ const status = document.getElementById('status');
322
+ btn.disabled = true;
323
+ btn.textContent = '⏳ جارٍ التحليل...';
324
+ document.getElementById('results').style.display = 'none';
325
+ status.innerHTML = '<span class="shine-text">⏳ جارٍ البحث عن المنافسين وقياس الأداء...</span>';
326
+ try {
327
+ const keys = JSON.parse(localStorage.getItem('geo_keys') || '{}');
328
+ const resp = await fetch('/api/competitor/intelligence', {
329
+ method: 'POST',
330
+ headers: { 'Content-Type': 'application/json' },
331
+ body: JSON.stringify({
332
+ url, region: document.getElementById('region').value,
333
+ industry: document.getElementById('industry').value,
334
+ count: parseInt(document.getElementById('count').value),
335
+ api_keys: keys
336
+ })
337
+ });
338
+ const data = await resp.json();
339
+ if (!data.ok) { status.textContent = '❌ خطأ: ' + data.error; btn.disabled = false; btn.textContent = '🔍 تحليل المنافسين'; return; }
340
+ const r = data.result;
341
+ currentData = r;
342
+ status.innerHTML = `<span style="color:var(--green)">✅ تم اكتشاف ${r.competitor_count} منافس</span>`;
343
+ const note = [];
344
+ if (r.data_sources.serp) note.push('✅ SerpAPI: نتائج حقيقية من Google');
345
+ else note.push('⚠️ بدون SerpAPI: المنافسون مقترحون بالذكاء الاصطناعي');
346
+ if (r.data_sources.pagespeed) note.push('✅ PageSpeed: بيانات أداء حقيقية');
347
+ if (r.data_sources.ai) note.push('✅ AI: تحليل استراتيجي حقيقي');
348
+ else note.push('⚠️ أضف Groq API للتحليل الاستراتيجي');
349
+ document.getElementById('dataSourceNote').textContent = note.join(' · ');
350
+ document.getElementById('yourDomain').textContent = r.your_domain;
351
+ document.getElementById('yourScore').textContent = r.your_score?.total || '?';
352
+ document.getElementById('yourRank').textContent = `#${r.your_rank || '?'} في ${r.competitor_count + 1} مواقع · ${r.market_position || 'Challenger'}`;
353
+ renderSpeedGrid(r.your_pagespeed, 'yourSpeed', 'yourVitals');
354
+ document.getElementById('compCount').textContent = ` (${r.competitor_count})`;
355
+ renderCompetitors(r.competitors, 'all');
356
+ renderAIAnalysis(r.insights);
357
+ document.getElementById('results').style.display = 'block';
358
+ } catch (e) {
359
+ status.textContent = '❌ فشل الطلب: ' + e.message;
360
+ } finally {
361
+ btn.disabled = false;
362
+ btn.textContent = '🔍 تحليل المنافسين';
363
+ }
364
+ }
365
+ document.getElementById('yourUrl').addEventListener('keydown', e => { if (e.key === 'Enter') runAnalysis(); });
366
+ </script>
367
+ </body>
368
+ </html>
frontend/competitor-intel.html CHANGED
@@ -142,6 +142,36 @@
142
  }
143
  @keyframes spin { to { transform: rotate(360deg); } }
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  @media(max-width:768px) {
146
  .form-grid { grid-template-columns: 1fr; }
147
  .speed-grid { grid-template-columns: 1fr 1fr; }
@@ -226,12 +256,23 @@
226
  <div style="font-size:12px;color:var(--accent);font-weight:700;margin-bottom:4px">موقعك</div>
227
  <div id="yourDomain" style="font-size:20px;font-weight:800"></div>
228
  </div>
229
- <div id="yourPosition" style="font-size:13px;color:var(--muted)"></div>
 
 
 
230
  </div>
231
  <div class="speed-grid" id="yourSpeed"></div>
232
  <div class="vitals-row" id="yourVitals"></div>
233
  </div>
234
 
 
 
 
 
 
 
 
 
235
  <!-- Competitors -->
236
  <h2 style="font-size:20px;font-weight:800;margin-bottom:4px">
237
  المنافسون المكتشفون
@@ -300,13 +341,37 @@
300
  grid.innerHTML = '';
301
  competitors.forEach((c, i) => {
302
  const ps = c.pagespeed || {};
 
303
  const card = document.createElement('div');
304
  card.className = 'comp-card stagger-item';
305
  card.style.animationDelay = `${i * 0.05}s`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  card.innerHTML = `
307
- <div class="comp-rank">${c.position || i+1}</div>
308
  <div class="comp-domain">${c.domain}</div>
 
309
  <div class="comp-snippet">${c.snippet || c.title || ''}</div>
 
 
 
 
 
 
 
 
310
  <div class="speed-grid">
311
  ${[['الأداء', ps.performance], ['SEO', ps.seo], ['وصول', ps.accessibility]].map(([l,v]) => `
312
  <div class="speed-item">
@@ -334,19 +399,31 @@
334
  const grid = document.getElementById('aiGrid');
335
 
336
  const sections = [
337
- { title: ' موقعك في السوق', key: 'market_position', single: true },
338
- { title: ' فرصك الذهبية', key: 'your_opportunities' },
 
339
  { title: '⚡ انتصارات سريعة', key: 'quick_wins' },
340
- { title: ' كلمات مفتاحية مقترحة', key: 'recommended_keywords' },
341
- { title: ' التهديدات', key: 'threats' },
342
- { title: ' مميزات المنافسين', key: 'key_differentiators' },
343
  ];
344
 
345
  grid.innerHTML = sections.map(s => {
346
  const val = ai[s.key];
347
- const items = s.single
348
- ? `<div class="ai-item" style="font-size:16px;font-weight:800;color:var(--accent)">${val}</div>`
349
- : (Array.isArray(val) ? val.map(v => `<div class="ai-item">${v}</div>`).join('') : '');
 
 
 
 
 
 
 
 
 
 
 
 
350
  return `<div class="ai-section"><h4>${s.title}</h4>${items}</div>`;
351
  }).join('');
352
 
@@ -397,6 +474,18 @@
397
 
398
  // Your site
399
  document.getElementById('yourDomain').textContent = r.your_domain;
 
 
 
 
 
 
 
 
 
 
 
 
400
  renderSpeedGrid(r.your_pagespeed, 'yourSpeed', 'yourVitals');
401
 
402
  // Competitors
@@ -404,7 +493,7 @@
404
  renderCompetitors(r.competitors);
405
 
406
  // AI Analysis
407
- renderAIAnalysis(r.ai_analysis);
408
 
409
  document.getElementById('results').style.display = 'block';
410
  } catch (e) {
 
142
  }
143
  @keyframes spin { to { transform: rotate(360deg); } }
144
 
145
+ .seg-tab {
146
+ background: rgba(255,255,255,0.04); border: 1px solid var(--border);
147
+ color: var(--muted); padding: 8px 16px; border-radius: 10px;
148
+ font-size: 13px; font-weight: 600; cursor: pointer;
149
+ transition: all 0.3s; font-family: 'Cairo', sans-serif;
150
+ }
151
+ .seg-tab:hover { border-color: var(--accent); color: var(--text); }
152
+ .seg-tab.active {
153
+ background: var(--accent-grad); color: #000;
154
+ border-color: transparent; font-weight: 800;
155
+ }
156
+
157
+ .comp-type-badge {
158
+ position: absolute; top: 16px; right: 16px;
159
+ font-size: 10px; padding: 4px 10px; border-radius: 8px;
160
+ font-weight: 700; text-transform: uppercase;
161
+ }
162
+ .type-direct { background: rgba(0,255,149,0.1); color: var(--green); border: 1px solid rgba(0,255,149,0.2); }
163
+ .type-indirect { background: rgba(255,204,0,0.1); color: var(--yellow); border: 1px solid rgba(255,204,0,0.2); }
164
+ .type-aspirational { background: rgba(124,58,237,0.1); color: #a78bfa; border: 1px solid rgba(124,58,237,0.2); }
165
+
166
+ .comp-score-big {
167
+ font-size: 28px; font-weight: 900; margin: 12px 0 8px;
168
+ text-align: center;
169
+ }
170
+ .comp-grade {
171
+ text-align: center; font-size: 11px; color: var(--muted);
172
+ text-transform: uppercase; letter-spacing: 1px;
173
+ }
174
+
175
  @media(max-width:768px) {
176
  .form-grid { grid-template-columns: 1fr; }
177
  .speed-grid { grid-template-columns: 1fr 1fr; }
 
256
  <div style="font-size:12px;color:var(--accent);font-weight:700;margin-bottom:4px">موقعك</div>
257
  <div id="yourDomain" style="font-size:20px;font-weight:800"></div>
258
  </div>
259
+ <div style="text-align:left">
260
+ <div id="yourScore" style="font-size:32px;font-weight:900;color:var(--accent)"></div>
261
+ <div id="yourRank" style="font-size:13px;color:var(--muted)"></div>
262
+ </div>
263
  </div>
264
  <div class="speed-grid" id="yourSpeed"></div>
265
  <div class="vitals-row" id="yourVitals"></div>
266
  </div>
267
 
268
+ <!-- Segmentation Tabs -->
269
+ <div style="display:flex;gap:12px;margin:24px 0 16px;flex-wrap:wrap" id="segmentTabs">
270
+ <button class="seg-tab active" data-seg="all" onclick="filterSegment('all')">الكل</button>
271
+ <button class="seg-tab" data-seg="direct" onclick="filterSegment('direct')">منافسون مباشرون</button>
272
+ <button class="seg-tab" data-seg="indirect" onclick="filterSegment('indirect')">منافسون غير مباشرين</button>
273
+ <button class="seg-tab" data-seg="aspirational" onclick="filterSegment('aspirational')">علامات كبرى</button>
274
+ </div>
275
+
276
  <!-- Competitors -->
277
  <h2 style="font-size:20px;font-weight:800;margin-bottom:4px">
278
  المنافسون المكتشفون
 
341
  grid.innerHTML = '';
342
  competitors.forEach((c, i) => {
343
  const ps = c.pagespeed || {};
344
+ const score = c.score || {};
345
  const card = document.createElement('div');
346
  card.className = 'comp-card stagger-item';
347
  card.style.animationDelay = `${i * 0.05}s`;
348
+
349
+ const dataQuality = score.data_quality || 'unknown';
350
+ const qualityBadge = dataQuality === 'pagespeed' ? '✓ حقيقي' : dataQuality === 'estimated' ? '≈ تقديري' : '~ تقريبي';
351
+ const qualityColor = dataQuality === 'pagespeed' ? 'var(--green)' : 'var(--yellow)';
352
+
353
+ const brandTier = score.brand_tier || 'unknown';
354
+ const tierLabel = {
355
+ 'global_giant': '🌍 عملاق عالمي',
356
+ 'regional_leader': '⭐ قائد إقليمي',
357
+ 'established': '✓ لاعب معروف',
358
+ 'niche': '🎯 متخصص',
359
+ 'unknown': ''
360
+ }[brandTier];
361
+
362
  card.innerHTML = `
363
+ <div class="comp-rank">${c.serp_position || i+1}</div>
364
  <div class="comp-domain">${c.domain}</div>
365
+ ${tierLabel ? `<div style="font-size:10px;color:var(--accent);margin-bottom:8px">${tierLabel}</div>` : ''}
366
  <div class="comp-snippet">${c.snippet || c.title || ''}</div>
367
+ <div style="text-align:center;margin:12px 0">
368
+ <div style="font-size:28px;font-weight:900;color:${scoreColor(score.total)}">${score.total || '—'}</div>
369
+ <div style="font-size:11px;color:var(--muted);text-transform:uppercase">تقييم ${score.grade || '?'} <span style="color:${qualityColor};font-size:9px">${qualityBadge}</span></div>
370
+ <div style="display:flex;gap:8px;justify-content:center;margin-top:8px;font-size:10px">
371
+ <span style="color:var(--muted)">موقع: ${score.website_quality || '?'}</span>
372
+ <span style="color:var(--accent)">سوق: ${score.market_power || '?'}</span>
373
+ </div>
374
+ </div>
375
  <div class="speed-grid">
376
  ${[['الأداء', ps.performance], ['SEO', ps.seo], ['وصول', ps.accessibility]].map(([l,v]) => `
377
  <div class="speed-item">
 
399
  const grid = document.getElementById('aiGrid');
400
 
401
  const sections = [
402
+ { title: '🎯 موقعك في السوق', key: 'market_position', single: true },
403
+ { title: '💪 نقاط قوتك', key: 'your_strengths' },
404
+ { title: '⚠️ نقاط ضعفك', key: 'your_weaknesses' },
405
  { title: '⚡ انتصارات سريعة', key: 'quick_wins' },
406
+ { title: '🎓 فرص ذهبية', key: 'opportunities' },
407
+ { title: '🔴 تهديدات', key: 'direct_threats' },
 
408
  ];
409
 
410
  grid.innerHTML = sections.map(s => {
411
  const val = ai[s.key];
412
+ let items = '';
413
+ if (s.single) {
414
+ items = `<div class="ai-item" style="font-size:16px;font-weight:800;color:var(--accent)">${val || 'N/A'}</div>`;
415
+ } else if (Array.isArray(val)) {
416
+ items = val.map(v => {
417
+ let text = v;
418
+ if (typeof v === 'object') {
419
+ if (v.win) text = v.win;
420
+ else if (v.action) text = v.action;
421
+ else if (v.competitor && v.threat) text = `${v.competitor}: ${v.threat}`;
422
+ else text = JSON.stringify(v);
423
+ }
424
+ return `<div class="ai-item">${text}</div>`;
425
+ }).join('');
426
+ }
427
  return `<div class="ai-section"><h4>${s.title}</h4>${items}</div>`;
428
  }).join('');
429
 
 
474
 
475
  // Your site
476
  document.getElementById('yourDomain').textContent = r.your_domain;
477
+ const yourScore = r.your_score || {};
478
+ document.getElementById('yourScore').textContent = yourScore.total || '?';
479
+ const tierEmoji = {
480
+ 'global_giant': '🌍',
481
+ 'regional_leader': '⭐',
482
+ 'established': '✓',
483
+ 'niche': '🎯'
484
+ }[yourScore.brand_tier] || '';
485
+ document.getElementById('yourRank').innerHTML = `
486
+ ${tierEmoji} #${r.your_rank || '?'} في ${r.competitor_count + 1} مواقع · ${r.market_position || 'Niche Player'}<br>
487
+ <span style="font-size:11px;color:var(--muted)">موقع: ${yourScore.website_quality || '?'} | سوق: ${yourScore.market_power || '?'}</span>
488
+ `;
489
  renderSpeedGrid(r.your_pagespeed, 'yourSpeed', 'yourVitals');
490
 
491
  // Competitors
 
493
  renderCompetitors(r.competitors);
494
 
495
  // AI Analysis
496
+ renderAIAnalysis(r.insights);
497
 
498
  document.getElementById('results').style.display = 'block';
499
  } catch (e) {
output/analysis.json CHANGED
@@ -6,7 +6,8 @@
6
  },
7
  "groq": {
8
  "enabled": true,
9
- "error": "Error code: 401 - {'error': {'message': 'Invalid API Key', 'type': 'invalid_request_error', 'code': 'invalid_api_key'}}"
 
10
  }
11
  },
12
  "geo_score": {
@@ -23,6 +24,40 @@
23
  "critical": 2,
24
  "warnings": 0,
25
  "passed": 0
 
 
 
 
 
 
 
 
 
26
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
  }
 
6
  },
7
  "groq": {
8
  "enabled": true,
9
+ "raw": "('id', 'chatcmpl-897ecf70-f2fb-4a4a-827b-3262ae237c2d')('choices', [Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='```json\\n{\\n \"title\": \"Rabhanagency - أفضل شركة تسويق في السعودية\",\\n \"url\": \"https://rabhanagency.com/\",\\n \"text\": \"Rabhanagency\",\\n \"additional_url\": \"https://rabhanagency.com/#content\"\\n}\\n```', role='assistant', annotations=None, executed_tools=None, function_call=None, reasoning=None, tool_calls=None))])('created', 1774441634)('model', 'llama-3.1-8b-instant')('object', 'chat.completion')('mcp_list_tools', None)('service_tier', 'on_demand')('system_fingerprint', 'fp_6a1eabf260')('usage', CompletionUsage(completion_tokens=62, prompt_tokens=106, total_tokens=168, completion_time=0.068615903, completion_tokens_details=None, prompt_time=0.007829861, prompt_tokens_details=None, queue_time=0.023641068, total_time=0.076445764))('usage_breakdown', None)('x_groq', XGroq(id='req_01kmjfd70wed5ta3tw2sc9a77e', debug=None, seed=1900544051, usage=None))",
10
+ "parse_error": "No JSON found in LLM response"
11
  }
12
  },
13
  "geo_score": {
 
24
  "critical": 2,
25
  "warnings": 0,
26
  "passed": 0
27
+ },
28
+ "v2": {
29
+ "score": 2.0,
30
+ "breakdown": {
31
+ "seo_rank": 0,
32
+ "ai_visibility": 0.0,
33
+ "traffic": 10.0
34
+ },
35
+ "avg_rank": 52.0
36
  }
37
+ },
38
+ "competitor_insight": {
39
+ "monthly_visits": "10K - 50K",
40
+ "traffic_sources": {
41
+ "search": 40,
42
+ "direct": 30,
43
+ "social": 20,
44
+ "referral": 10
45
+ },
46
+ "top_competitors": [
47
+ {
48
+ "name": "منافس 1",
49
+ "domain": "comp1.com",
50
+ "overlap_score": 90,
51
+ "region": "SA"
52
+ },
53
+ {
54
+ "name": "منافس 2",
55
+ "domain": "comp2.com",
56
+ "overlap_score": 85,
57
+ "region": "UAE"
58
+ }
59
+ ],
60
+ "industry": "خدمات رقمية",
61
+ "seo_rankings": []
62
  }
63
  }
output/audit.json CHANGED
@@ -8,8 +8,8 @@
8
  "Rabhanagency"
9
  ],
10
  "links": [
11
- "https://rabhanagency.com/#content",
12
- "https://rabhanagency.com/"
13
  ]
14
  },
15
  {
@@ -20,8 +20,8 @@
20
  "Rabhanagency"
21
  ],
22
  "links": [
23
- "https://rabhanagency.com/#content",
24
- "https://rabhanagency.com/"
25
  ]
26
  }
27
  ],
@@ -57,14 +57,13 @@
57
  "enabled": true,
58
  "results": [
59
  {
60
- "query": "What is Rabhanagency?",
61
  "error": "\n\nYou tried to access openai.ChatCompletion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.\n\nYou can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. \n\nAlternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`\n\nA detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742\n"
62
  },
63
  {
64
- "query": "Best services for Rabhanagency",
65
  "error": "\n\nYou tried to access openai.ChatCompletion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.\n\nYou can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. \n\nAlternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`\n\nA detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742\n"
66
  }
67
  ]
68
- },
69
- "org_name": "Rabhanagency"
70
  }
 
8
  "Rabhanagency"
9
  ],
10
  "links": [
11
+ "https://rabhanagency.com/",
12
+ "https://rabhanagency.com/#content"
13
  ]
14
  },
15
  {
 
20
  "Rabhanagency"
21
  ],
22
  "links": [
23
+ "https://rabhanagency.com/",
24
+ "https://rabhanagency.com/#content"
25
  ]
26
  }
27
  ],
 
57
  "enabled": true,
58
  "results": [
59
  {
60
+ "query": "What is rabhanagency?",
61
  "error": "\n\nYou tried to access openai.ChatCompletion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.\n\nYou can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. \n\nAlternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`\n\nA detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742\n"
62
  },
63
  {
64
+ "query": "Best services for rabhanagency",
65
  "error": "\n\nYou tried to access openai.ChatCompletion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.\n\nYou can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. \n\nAlternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`\n\nA detailed migration guide is available here: https://github.com/openai/openai-python/discussions/742\n"
66
  }
67
  ]
68
+ }
 
69
  }
requirements.txt CHANGED
@@ -6,3 +6,5 @@ fastapi>=0.95
6
  uvicorn[standard]>=0.22
7
  openai>=1.0
8
  groq>=0.1
 
 
 
6
  uvicorn[standard]>=0.22
7
  openai>=1.0
8
  groq>=0.1
9
+ lxml>=4.9
10
+ html5lib>=1.1
server/api.py CHANGED
@@ -342,6 +342,10 @@ async def api_results(ts: str | None = None):
342
  out['schema'] = schema_path.read_text(encoding='utf-8')
343
  return out
344
 
 
 
 
 
345
  @app.get('/')
346
  async def index():
347
  index_file = frontend_dir / 'index.html'
 
342
  out['schema'] = schema_path.read_text(encoding='utf-8')
343
  return out
344
 
345
+ @app.get('/health')
346
+ async def health_check():
347
+ return {'status': 'healthy', 'service': 'GEO Platform'}
348
+
349
  @app.get('/')
350
  async def index():
351
  index_file = frontend_dir / 'index.html'
start.sh ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo " Starting GEO Platform..."
5
+
6
+ # Create output directory
7
+ mkdir -p /tmp/geo-output
8
+
9
+ # Check if spaCy model is installed
10
+ if ! python -c "import spacy; spacy.load('en_core_web_sm')" 2>/dev/null; then
11
+ echo " Downloading spaCy model..."
12
+ python -m spacy download en_core_web_sm
13
+ fi
14
+
15
+ # Start the server
16
+ echo " Starting FastAPI server on port 7860..."
17
+ exec uvicorn server.api:app --host 0.0.0.0 --port 7860 --timeout-keep-alive 75