"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import useStore from "@/lib/store"; import { translations } from "@/lib/i18n/translations"; import modelAdapter from "@/lib/services/adapter-instance"; import { Search, Globe, Plus, Trash2, ShieldAlert, BarChart3, TrendingUp, Target, Rocket, Lightbulb, CheckCircle2, AlertCircle, Loader2, X, ExternalLink } from "lucide-react"; import { cn } from "@/lib/utils"; const MarketResearcher = () => { const { language, selectedProvider, selectedModels, apiKeys, setMarketResearchResult, marketResearchResult } = useStore(); const t = translations[language].marketResearch; const common = translations[language].common; const [websiteUrl, setWebsiteUrl] = useState(""); const [additionalUrls, setAdditionalUrls] = useState([""]); const [competitorUrls, setCompetitorUrls] = useState(["", "", ""]); const [productMapping, setProductMapping] = useState(""); const [specialInstructions, setSpecialInstructions] = useState(""); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const [thoughtIndex, setThoughtIndex] = useState(0); const [error, setError] = useState(null); const selectedModel = selectedModels[selectedProvider]; const handleAddUrl = () => setAdditionalUrls([...additionalUrls, ""]); const handleRemoveUrl = (index: number) => { const newUrls = [...additionalUrls]; newUrls.splice(index, 1); setAdditionalUrls(newUrls); }; const handleAddCompetitor = () => { if (competitorUrls.length < 10) { setCompetitorUrls([...competitorUrls, ""]); } }; const handleRemoveCompetitor = (index: number) => { const newUrls = [...competitorUrls]; newUrls.splice(index, 1); setCompetitorUrls(newUrls); }; const validateUrls = () => { const urlRegex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/; if (!websiteUrl || !urlRegex.test(websiteUrl)) return t.invalidPrimaryUrl; const validCompetitors = competitorUrls.filter(url => url.trim().length > 0); if (validCompetitors.length < 2) return t.minCompetitors; for (const url of validCompetitors) { if (!urlRegex.test(url)) return `${t.invalidCompetitorUrl}: ${url}`; } return null; }; useEffect(() => { let interval: NodeJS.Timeout; if (isProcessing) { setProgress(0); setThoughtIndex(0); interval = setInterval(() => { setProgress(prev => { if (prev >= 95) return prev; return prev + (prev < 30 ? 2 : prev < 70 ? 1 : 0.5); }); }, 300); const thoughtInterval = setInterval(() => { setThoughtIndex(prev => (prev < (t.thoughts?.length || 0) - 1 ? prev + 1 : prev)); }, 4000); return () => { clearInterval(interval); clearInterval(thoughtInterval); }; } else { setProgress(0); } }, [isProcessing, t.thoughts]); const handleStartResearch = async () => { const validationError = validateUrls(); if (validationError) { setError(validationError); return; } const apiKey = apiKeys[selectedProvider]; const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth(); if (!isQwenOAuth && (!apiKey || !apiKey.trim())) { setError(`${common.configApiKey}`); return; } setIsProcessing(true); setError(null); setMarketResearchResult(null); try { const filteredCompetitors = competitorUrls.filter(u => u.trim() !== ""); const filteredAddUrls = additionalUrls.filter(u => u.trim() !== ""); const result = await modelAdapter.generateMarketResearch({ websiteUrl, additionalUrls: filteredAddUrls, competitors: filteredCompetitors, productMapping, specialInstructions }, selectedProvider, selectedModel); if (result.success && result.data) { setProgress(100); try { const cleanJson = result.data.replace(/```json\s*([\s\S]*?)\s*```/i, '$1').trim(); const parsed = JSON.parse(cleanJson); setMarketResearchResult({ ...parsed, id: Math.random().toString(36).substr(2, 9), websiteUrl, additionalUrls: filteredAddUrls, competitors: filteredCompetitors, productMapping: [{ productName: productMapping || t.mainProduct, features: [] }], generatedAt: new Date(), rawContent: result.data }); } catch (e) { console.error("Failed to parse market research JSON:", e); setError(t.parseError); } } else { setError(result.error || t.researchFailed); } } catch (err) { setError(err instanceof Error ? err.message : t.unexpectedError); } finally { setIsProcessing(false); } }; const renderPriceMatrix = () => { if (!marketResearchResult?.priceComparisonMatrix) return null; return (
{marketResearchResult.competitors.map((comp, i) => ( ))} {marketResearchResult.priceComparisonMatrix.map((item, i) => ( {marketResearchResult.competitors.map((comp) => { const compPrice = item.competitorPrices.find(cp => cp.competitor === comp || comp.includes(cp.competitor)); return ( ); })} ))}
{t.product} {t.yourPrice}{comp}
{item.product} {item.userPrice}
{compPrice ? compPrice.price : t.notAvailable} {compPrice?.url && ( {t.viewProduct} )}
); }; const renderFeatureTable = () => { if (!marketResearchResult?.featureComparisonTable) return null; return (
{marketResearchResult.competitors.map((comp, i) => ( ))} {marketResearchResult.featureComparisonTable.map((item, i) => ( {marketResearchResult.competitors.map((comp) => { const compStatus = item.competitorStatus.find(cs => cs.competitor === comp || comp.includes(cs.competitor)); return ( ); })} ))}
{t.feature} {t.you}{comp}
{item.feature} {typeof item.userStatus === 'boolean' ? ( item.userStatus ? : ) : {item.userStatus}} {compStatus ? ( typeof compStatus.status === 'boolean' ? ( compStatus.status ? : ) : {compStatus.status} ) : t.notAvailable}
); }; return (
{/* Header Section */}

{t.title}

{t.description}

{/* Configuration Panel */}
{t.companyProfile}
setWebsiteUrl(e.target.value)} className="bg-slate-50 border-slate-200 focus:bg-white transition-all font-medium" />
{additionalUrls.map((url, i) => (
{ const newUrls = [...additionalUrls]; newUrls[i] = e.target.value; setAdditionalUrls(newUrls); }} className="bg-slate-50/50 border-slate-200 focus:bg-white transition-all text-xs" />
))}
{t.competitiveIntel}
{competitorUrls.map((url, i) => (
{ const newUrls = [...competitorUrls]; newUrls[i] = e.target.value; setCompetitorUrls(newUrls); }} className="bg-slate-50/50 border-slate-200 focus:bg-white transition-all text-xs" /> {competitorUrls.length > 2 && ( )}
))}