import React, { useState, useEffect } from 'react'; import { useApp } from '../context/AppContext'; import { Sparkles, Upload, FileText, CheckCircle2, AlertTriangle, X, Loader2, TrendingUp, Sliders, Info } from 'lucide-react'; import confetti from 'canvas-confetti'; import { getAiConfig } from '../services/ai'; const tl = (n) => (typeof n === 'number' && isFinite(n) ? '₺' + n.toLocaleString('tr-TR') : '—'); export const AiSpecAnalyzerModal = () => { const { isAiSpecAnalyzerOpen, setIsAiSpecAnalyzerOpen, analyzeSpecPdfWithGemini, aiAnalysisResults, activeAiModel, activeTenderDetail, } = useApp(); const [isAnalyzing, setIsAnalyzing] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [error, setError] = useState(null); const [progress, setProgress] = useState(''); const [customBid, setCustomBid] = useState(null); const [aiCfg, setAiCfg] = useState(null); useEffect(() => { if (isAiSpecAnalyzerOpen && aiCfg === null) getAiConfig().then(setAiCfg); }, [isAiSpecAnalyzerOpen, aiCfg]); if (!isAiSpecAnalyzerOpen) return null; const results = aiAnalysisResults; const bid = results?.bidAnalysis || {}; const runAnalysis = async (text, fileName) => { setError(null); setIsAnalyzing(true); setSelectedFile(fileName); try { setProgress('Model analiz ediyor…'); const r = await analyzeSpecPdfWithGemini({ text, fileName, tender: activeTenderDetail }); if (typeof r?.bidAnalysis?.recommendedBid === 'number') setCustomBid(r.bidAnalysis.recommendedBid); confetti({ particleCount: 80, spread: 70 }); } catch (e) { setError(e.message); } finally { setIsAnalyzing(false); setProgress(''); } }; const handleFile = async (file) => { setError(null); setIsAnalyzing(true); setSelectedFile(file.name); try { setProgress('Belge metni çıkarılıyor…'); const { extractText } = await import('../services/pdfText'); const { text, usedPages, pageCount } = await extractText(file); if (!text || text.length < 40) { throw new Error('Belgeden metin çıkarılamadı. Taranmış (görüntü) PDF olabilir — metin tabanlı bir dosya deneyin.'); } setProgress(`${usedPages}/${pageCount} sayfa okundu, model analiz ediyor…`); const r = await analyzeSpecPdfWithGemini({ text, fileName: file.name, tender: activeTenderDetail }); if (typeof r?.bidAnalysis?.recommendedBid === 'number') setCustomBid(r.bidAnalysis.recommendedBid); confetti({ particleCount: 80, spread: 70 }); } catch (e) { setError(e.message); } finally { setIsAnalyzing(false); setProgress(''); } }; // Açık ihalenin ilan metnini analiz et const analyzeCurrentTender = () => { const t = activeTenderDetail; const text = [t?.baslik, t?.kurum, t?.aciklama, ...(t?.yeterlikKriterleri || [])].filter(Boolean).join('\n'); if (!text || text.length < 40) { setError('Açık ihalenin ilan metni analiz için yeterli değil. Bir şartname dosyası yükleyin.'); return; } runAnalysis(text, `${t.ikn || 'ihale'} — ilan metni`); }; const modelEtiketi = aiCfg?.providers?.length ? aiCfg.providers.map((p) => p.label).join(' → ') : `${activeAiModel.label} (${activeAiModel.provider})`; return (
{/* Header */}

Teknik Şartname & Teklif Analiz Asistanı

{modelEtiketi}

Şartname dosyanızı yükleyin; yeterlik, riskler ve teklif aralığı gerçek AI ile analiz edilsin.

{/* AI yapılandırılmamış uyarısı */} {aiCfg && !aiCfg.configured && (

AI henüz yapılandırılmamış. En kolayı: sunucuya tek bir{' '} OPENROUTER_API_KEY ekleyin — zincirdeki tüm modeller (GLM, DeepSeek, Gemini, Grok, GPT, Claude) bu tek anahtarla çalışır. Dilerseniz tek tek sağlayıcı anahtarı da verebilirsiniz; doğrudan anahtar OpenRouter'ı ezer. Model sırası Admin → AI Model Ayarları'ndan belirlenir.

)} {/* Uploader */}

Teknik Şartname Dosyası Yükleyin

Metin tabanlı .pdf veya .txt dosyası — belge tarayıcınızda okunur, yalnızca metni analiz için gönderilir.

{activeTenderDetail && ( )}
{/* Hata */} {error && (

Analiz tamamlanamadı

{error}

)} {/* Yükleniyor */} {isAnalyzing && (
{progress || 'İşleniyor…'}

{selectedFile}

)} {/* Sonuç */} {!isAnalyzing && results && (
Şartname Özeti {results.model} · {results.analyzedAt}

{results.summary || 'Özet üretilemedi.'}

{results.fileName &&

Kaynak: {results.fileName}

}
{/* Teklif analizi — yalnızca model sayı ürettiyse */} {(bid.estimatedCost != null || bid.recommendedBid != null) && (

Önerilen Teklif Aralığı

{bid.winProbability != null && ( %{bid.winProbability} Kazanma Tahmini )}
İdare Yaklaşık Maliyeti {tl(bid.estimatedCost)}
Önerilen Optimal Teklif {tl(bid.recommendedBid)}
Rekabet Değerlendirmesi {bid.competitionLevel || '—'}
{bid.minBidLimit != null && bid.maxBidLimit != null && bid.maxBidLimit > bid.minBidLimit && (
Teklif Fiyatınızı Simüle Edin: {tl(customBid ?? bid.recommendedBid)}
setCustomBid(Number(e.target.value))} className="w-full accent-emerald-500 cursor-pointer h-2 bg-slate-800 rounded-lg" />
Min: {tl(bid.minBidLimit)} Önerilen: {tl(bid.recommendedBid)} Maks: {tl(bid.maxBidLimit)}
)} {bid.notes &&

{bid.notes}

}
)}

Yeterlik Kriterleri & Zorunlu Belgeler

    {(results.qualificationCriteria || []).length === 0 && (
  • Belgede yeterlik kriteri tespit edilemedi.
  • )} {(results.qualificationCriteria || []).map((item, idx) => (
  • {item}
  • ))}
{(results.criticalRequirements || []).length > 0 && ( <>

Kritik Şartlar

    {results.criticalRequirements.map((item, idx) => (
  • {item}
  • ))}
)}

Riskler & Cezai Hükümler

{(results.risksAndPenalties || []).length === 0 && (

Belgede cezai hüküm/risk tespit edilemedi.

)} {(results.risksAndPenalties || []).map((risk, idx) => (
{risk.clause || '—'} {risk.level || 'Bilgi'} Risk

{risk.text}

))}

Analiz yapay zeka tarafından üretilmiştir; teklif kararı öncesi şartnamenin aslını doğrulayın.

)}
); };