Spaces:
Running
Running
fix: Hugging Face Space deployment - health check, dependencies, startup script
Browse files- .dockerignore +24 -0
- .env.example +24 -8
- COMPETITOR_INTEL_V2.md +327 -0
- Dockerfile +9 -2
- HUGGINGFACE_DEPLOYMENT.md +165 -0
- frontend/competitor-intel-v2.html +368 -0
- frontend/competitor-intel.html +100 -11
- output/analysis.json +36 -1
- output/audit.json +7 -8
- requirements.txt +2 -0
- server/api.py +4 -0
- start.sh +17 -0
.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 |
-
#
|
| 2 |
-
#
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ["
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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: '
|
|
|
|
| 339 |
{ title: '⚡ انتصارات سريعة', key: 'quick_wins' },
|
| 340 |
-
{ title: '
|
| 341 |
-
{ title: '
|
| 342 |
-
{ title: ' مميزات المنافسين', key: 'key_differentiators' },
|
| 343 |
];
|
| 344 |
|
| 345 |
grid.innerHTML = sections.map(s => {
|
| 346 |
const val = ai[s.key];
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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 |
-
"
|
|
|
|
| 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/
|
| 12 |
-
"https://rabhanagency.com/"
|
| 13 |
]
|
| 14 |
},
|
| 15 |
{
|
|
@@ -20,8 +20,8 @@
|
|
| 20 |
"Rabhanagency"
|
| 21 |
],
|
| 22 |
"links": [
|
| 23 |
-
"https://rabhanagency.com/
|
| 24 |
-
"https://rabhanagency.com/"
|
| 25 |
]
|
| 26 |
}
|
| 27 |
],
|
|
@@ -57,14 +57,13 @@
|
|
| 57 |
"enabled": true,
|
| 58 |
"results": [
|
| 59 |
{
|
| 60 |
-
"query": "What is
|
| 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
|
| 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
|