Spaces:
Sleeping
Sleeping
Upload frontend/src/components/EntityDetail.jsx
Browse files
frontend/src/components/EntityDetail.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
import React from 'react'
|
| 2 |
-
import { X } from 'lucide-react'
|
| 3 |
|
| 4 |
const ENTITY_COLORS = {
|
| 5 |
startup: '#3B82F6', sme: '#10B981', college_ecell: '#FBBF24',
|
|
@@ -19,6 +19,125 @@ function formatFunding(v) {
|
|
| 19 |
return `₹${(v/100000).toFixed(0)} L`
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
export default function EntityDetail({ entity, onClose, onEntityClick, isMobile }) {
|
| 23 |
if (!entity) return null
|
| 24 |
|
|
@@ -78,6 +197,12 @@ export default function EntityDetail({ entity, onClose, onEntityClick, isMobile
|
|
| 78 |
|
| 79 |
{/* Scrollable body */}
|
| 80 |
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-4 scrollbar-thin">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
{/* Description */}
|
| 82 |
{e.description && (
|
| 83 |
<p className="text-sm text-atlas-muted leading-relaxed">{e.description}</p>
|
|
@@ -104,24 +229,12 @@ export default function EntityDetail({ entity, onClose, onEntityClick, isMobile
|
|
| 104 |
|
| 105 |
{/* Additional Info */}
|
| 106 |
<div className="space-y-2">
|
| 107 |
-
{e.stage && (
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
{e.
|
| 111 |
-
|
| 112 |
-
)}
|
| 113 |
-
{e.dpiit_category && (
|
| 114 |
-
<InfoRow label="DPIIT Category" value={e.dpiit_category} />
|
| 115 |
-
)}
|
| 116 |
-
{e.college_name && (
|
| 117 |
-
<InfoRow label="College" value={e.college_name} />
|
| 118 |
-
)}
|
| 119 |
-
{e.funding_stage && (
|
| 120 |
-
<InfoRow label="Funding Stage" value={e.funding_stage} />
|
| 121 |
-
)}
|
| 122 |
-
{e.valuation_usd && (
|
| 123 |
-
<InfoRow label="Valuation" value={`$${(e.valuation_usd / 1e9).toFixed(1)}B`} />
|
| 124 |
-
)}
|
| 125 |
</div>
|
| 126 |
|
| 127 |
{/* Investors */}
|
|
@@ -136,17 +249,8 @@ export default function EntityDetail({ entity, onClose, onEntityClick, isMobile
|
|
| 136 |
</div>
|
| 137 |
)}
|
| 138 |
|
| 139 |
-
{/*
|
| 140 |
-
<
|
| 141 |
-
<h4 className="text-xs font-semibold text-atlas-muted uppercase tracking-wider">Links</h4>
|
| 142 |
-
{e.website && <LinkRow icon="🌐" label="Website" url={e.website} />}
|
| 143 |
-
{e.linkedin_url && <LinkRow icon="💼" label="LinkedIn" url={e.linkedin_url} />}
|
| 144 |
-
{e.twitter_url && <LinkRow icon="𝕏" label="Twitter" url={e.twitter_url} />}
|
| 145 |
-
{e.instagram_url && <LinkRow icon="📸" label="Instagram" url={e.instagram_url} />}
|
| 146 |
-
{!e.website && !e.linkedin_url && !e.twitter_url && !e.instagram_url && (
|
| 147 |
-
<p className="text-xs text-atlas-muted/50">No links available</p>
|
| 148 |
-
)}
|
| 149 |
-
</div>
|
| 150 |
|
| 151 |
{/* Nearby Entities */}
|
| 152 |
{nearby.length > 0 && (
|
|
@@ -207,14 +311,3 @@ function InfoRow({ label, value }) {
|
|
| 207 |
</div>
|
| 208 |
)
|
| 209 |
}
|
| 210 |
-
|
| 211 |
-
function LinkRow({ icon, label, url }) {
|
| 212 |
-
return (
|
| 213 |
-
<a href={url} target="_blank" rel="noopener noreferrer"
|
| 214 |
-
className="flex items-center gap-2 text-sm text-brand-500 hover:text-brand-400 transition-colors">
|
| 215 |
-
<span>{icon}</span>
|
| 216 |
-
<span className="underline underline-offset-2">{label}</span>
|
| 217 |
-
<span className="text-atlas-muted/40">↗</span>
|
| 218 |
-
</a>
|
| 219 |
-
)
|
| 220 |
-
}
|
|
|
|
| 1 |
+
import React, { useState } from 'react'
|
| 2 |
+
import { X, Globe, ExternalLink } from 'lucide-react'
|
| 3 |
|
| 4 |
const ENTITY_COLORS = {
|
| 5 |
startup: '#3B82F6', sme: '#10B981', college_ecell: '#FBBF24',
|
|
|
|
| 19 |
return `₹${(v/100000).toFixed(0)} L`
|
| 20 |
}
|
| 21 |
|
| 22 |
+
const SOCIAL_CONFIG = {
|
| 23 |
+
linkedin: { icon: '💼', color: '#0A66C2', label: 'LinkedIn' },
|
| 24 |
+
twitter: { icon: '𝕏', color: '#000000', label: 'X (Twitter)' },
|
| 25 |
+
instagram: { icon: '📸', color: '#E4405F', label: 'Instagram' },
|
| 26 |
+
facebook: { icon: '📘', color: '#1877F2', label: 'Facebook' },
|
| 27 |
+
website: { icon: '🌐', color: '#6366F1', label: 'Website' },
|
| 28 |
+
crunchbase: { icon: 'CB', color: '#0288D1', label: 'Crunchbase' },
|
| 29 |
+
tracxn: { icon: 'TX', color: '#FF6F00', label: 'Tracxn' },
|
| 30 |
+
google: { icon: '🔍', color: '#4285F4', label: 'Google Search' },
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
function SocialLinksGrid({ entity }) {
|
| 34 |
+
const links = []
|
| 35 |
+
const name = entity.name
|
| 36 |
+
|
| 37 |
+
if (entity.linkedin_url) links.push({ ...SOCIAL_CONFIG.linkedin, url: entity.linkedin_url, verified: true })
|
| 38 |
+
if (entity.twitter_url) links.push({ ...SOCIAL_CONFIG.twitter, url: entity.twitter_url, verified: true })
|
| 39 |
+
if (entity.instagram_url) links.push({ ...SOCIAL_CONFIG.instagram, url: entity.instagram_url, verified: true })
|
| 40 |
+
if (entity.facebook_url) links.push({ ...SOCIAL_CONFIG.facebook, url: entity.facebook_url, verified: true })
|
| 41 |
+
if (entity.website) links.push({ ...SOCIAL_CONFIG.website, url: entity.website, verified: true })
|
| 42 |
+
|
| 43 |
+
// Search links (always available)
|
| 44 |
+
const slug = name.toLowerCase().replace(/\s+/g, '-').replace('.', '')
|
| 45 |
+
const encName = encodeURIComponent(name)
|
| 46 |
+
if (!entity.linkedin_url) links.push({ ...SOCIAL_CONFIG.linkedin, url: `https://www.linkedin.com/search/results/companies/?keywords=${encName}`, search: true })
|
| 47 |
+
if (!entity.twitter_url) links.push({ ...SOCIAL_CONFIG.twitter, url: `https://x.com/search?q=${encName}&src=typed_query`, search: true })
|
| 48 |
+
if (!entity.instagram_url) links.push({ ...SOCIAL_CONFIG.instagram, url: `https://www.instagram.com/${slug}`, search: true })
|
| 49 |
+
if (!entity.website) links.push({ ...SOCIAL_CONFIG.website, url: `https://www.google.com/search?q=${encName}+company`, search: true })
|
| 50 |
+
links.push({ ...SOCIAL_CONFIG.crunchbase, url: `https://www.crunchbase.com/organization/${slug}`, search: true })
|
| 51 |
+
links.push({ ...SOCIAL_CONFIG.tracxn, url: `https://tracxn.com/d/companies/${slug}/`, search: true })
|
| 52 |
+
links.push({ ...SOCIAL_CONFIG.google, url: `https://www.google.com/search?q=${encName}+startup+India+news+2024+2025`, search: true })
|
| 53 |
+
|
| 54 |
+
return (
|
| 55 |
+
<div className="grid grid-cols-4 gap-1.5">
|
| 56 |
+
{links.map(l => (
|
| 57 |
+
<a key={l.label + l.url}
|
| 58 |
+
href={l.url}
|
| 59 |
+
target="_blank"
|
| 60 |
+
rel="noopener noreferrer"
|
| 61 |
+
title={`${l.label}${l.search ? ' (search)' : ''}`}
|
| 62 |
+
className="flex flex-col items-center gap-1 p-2 rounded-lg hover:bg-atlas-surface transition-colors group"
|
| 63 |
+
>
|
| 64 |
+
<span className="text-lg" style={{ color: l.color }}>{l.icon}</span>
|
| 65 |
+
<span className="text-[9px] text-atlas-muted group-hover:text-atlas-text text-center leading-tight">{l.label}</span>
|
| 66 |
+
{l.verified && <span className="w-1 h-1 rounded-full bg-emerald-400" title="Verified" />}
|
| 67 |
+
</a>
|
| 68 |
+
))}
|
| 69 |
+
</div>
|
| 70 |
+
)
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
function AIAnalysisSection({ entity }) {
|
| 74 |
+
const [analysis, setAnalysis] = useState(null)
|
| 75 |
+
const [loading, setLoading] = useState(false)
|
| 76 |
+
|
| 77 |
+
const runAnalysis = async () => {
|
| 78 |
+
setLoading(true)
|
| 79 |
+
try {
|
| 80 |
+
const resp = await fetch('/api/agent/analyze-startup', {
|
| 81 |
+
method: 'POST',
|
| 82 |
+
headers: { 'Content-Type': 'application/json' },
|
| 83 |
+
body: JSON.stringify({
|
| 84 |
+
company_name: entity.name,
|
| 85 |
+
sector: Array.isArray(entity.sectors) ? entity.sectors[0] : entity.dpiit_category || '',
|
| 86 |
+
city: entity.city || '',
|
| 87 |
+
}),
|
| 88 |
+
})
|
| 89 |
+
const data = await resp.json()
|
| 90 |
+
setAnalysis(data)
|
| 91 |
+
} catch (e) {
|
| 92 |
+
setAnalysis({ summary: 'Analysis temporarily unavailable. Try again later.', latest_news: [], confidence: 'low' })
|
| 93 |
+
}
|
| 94 |
+
setLoading(false)
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
return (
|
| 98 |
+
<div className="border-t border-atlas-border/30 pt-3">
|
| 99 |
+
{!analysis && (
|
| 100 |
+
<button onClick={runAnalysis} disabled={loading}
|
| 101 |
+
className="w-full flex items-center justify-center gap-2 py-2 rounded-lg bg-brand-500/10 text-brand-400 hover:bg-brand-500/20 transition-colors text-xs font-medium">
|
| 102 |
+
{loading ? (
|
| 103 |
+
<><span className="w-3 h-3 border-2 border-brand-400 border-t-transparent rounded-full animate-spin" /> Analyzing...</>
|
| 104 |
+
) : (
|
| 105 |
+
<><span>🤖</span> AI Web Analysis — Latest News & Trends</>
|
| 106 |
+
)}
|
| 107 |
+
</button>
|
| 108 |
+
)}
|
| 109 |
+
{analysis && (
|
| 110 |
+
<div className="space-y-2">
|
| 111 |
+
<div className="flex items-center gap-2 mb-1">
|
| 112 |
+
<span className="text-xs font-semibold text-atlas-muted">🤖 AI Analysis</span>
|
| 113 |
+
<span className={`text-[10px] px-1.5 py-0.5 rounded-full ${
|
| 114 |
+
analysis.confidence === 'high' ? 'bg-emerald-500/15 text-emerald-300' :
|
| 115 |
+
analysis.confidence === 'medium' ? 'bg-amber-500/15 text-amber-300' :
|
| 116 |
+
'bg-red-500/15 text-red-300'
|
| 117 |
+
}`}>{analysis.confidence} confidence</span>
|
| 118 |
+
</div>
|
| 119 |
+
<p className="text-xs text-atlas-muted leading-relaxed">{analysis.summary}</p>
|
| 120 |
+
{analysis.latest_news?.length > 0 && (
|
| 121 |
+
<div className="space-y-1.5">
|
| 122 |
+
<span className="text-[10px] font-semibold text-atlas-muted/70 uppercase">Latest News</span>
|
| 123 |
+
{analysis.latest_news.slice(0, 3).map((n, i) => (
|
| 124 |
+
<a key={i} href={n.url} target="_blank" rel="noopener noreferrer"
|
| 125 |
+
className="block text-xs text-brand-400 hover:text-brand-300 hover:bg-brand-500/5 rounded px-2 py-1.5 transition-colors truncate">
|
| 126 |
+
• {n.title}
|
| 127 |
+
</a>
|
| 128 |
+
))}
|
| 129 |
+
</div>
|
| 130 |
+
)}
|
| 131 |
+
<button onClick={() => setAnalysis(null)}
|
| 132 |
+
className="text-[10px] text-atlas-muted/60 hover:text-atlas-muted transition-colors">
|
| 133 |
+
↻ Run again
|
| 134 |
+
</button>
|
| 135 |
+
</div>
|
| 136 |
+
)}
|
| 137 |
+
</div>
|
| 138 |
+
)
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
export default function EntityDetail({ entity, onClose, onEntityClick, isMobile }) {
|
| 142 |
if (!entity) return null
|
| 143 |
|
|
|
|
| 197 |
|
| 198 |
{/* Scrollable body */}
|
| 199 |
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-4 scrollbar-thin">
|
| 200 |
+
{/* Social Media Links */}
|
| 201 |
+
<div>
|
| 202 |
+
<h4 className="text-xs font-semibold text-atlas-muted uppercase tracking-wider mb-2">Social & Profiles</h4>
|
| 203 |
+
<SocialLinksGrid entity={e} />
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
{/* Description */}
|
| 207 |
{e.description && (
|
| 208 |
<p className="text-sm text-atlas-muted leading-relaxed">{e.description}</p>
|
|
|
|
| 229 |
|
| 230 |
{/* Additional Info */}
|
| 231 |
<div className="space-y-2">
|
| 232 |
+
{e.stage && <InfoRow label="Stage" value={e.stage.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())} />}
|
| 233 |
+
{e.business_model && <InfoRow label="Business Model" value={e.business_model.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())} />}
|
| 234 |
+
{e.dpiit_category && <InfoRow label="DPIIT Category" value={e.dpiit_category} />}
|
| 235 |
+
{e.college_name && <InfoRow label="College" value={e.college_name} />}
|
| 236 |
+
{e.funding_stage && <InfoRow label="Funding Stage" value={e.funding_stage} />}
|
| 237 |
+
{e.valuation_usd && <InfoRow label="Valuation" value={`$${(e.valuation_usd / 1e9).toFixed(1)}B`} />}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
</div>
|
| 239 |
|
| 240 |
{/* Investors */}
|
|
|
|
| 249 |
</div>
|
| 250 |
)}
|
| 251 |
|
| 252 |
+
{/* AI Web Analysis */}
|
| 253 |
+
<AIAnalysisSection entity={e} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
{/* Nearby Entities */}
|
| 256 |
{nearby.length > 0 && (
|
|
|
|
| 311 |
</div>
|
| 312 |
)
|
| 313 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|