import { AiJobHistory } from './AiJobHistory'; import {getToken} from '../services/auth'; import React, { useState, useEffect, useId } from 'react'; import {useModalFocus} from '../hooks/useModalFocus'; 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'; import { DeepAnalysisReport } from './DeepAnalysisReport'; const tl = (n) => (typeof n === 'number' && isFinite(n) ? '₺' + n.toLocaleString('tr-TR') : '—'); export const AiSpecAnalyzerModal = () => { const { isAiSpecAnalyzerOpen, setIsAiSpecAnalyzerOpen, analyzeSpecPdfWithGemini, openSavedAnalysis, authUser, aiAnalysisResults, aiAnalyzing, aiJobStatus, activeAiModel, activeTenderDetail, } = useApp(); const dialogTitleId=useId(); const modalFocus=useModalFocus(isAiSpecAnalyzerOpen,()=>setIsAiSpecAnalyzerOpen(false)); const [isAnalyzing, setIsAnalyzing] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [error, setError] = useState(null); const [progress, setProgress] = useState(''); const [restoredSource,setRestoredSource]=useState(null); const [customBid, setCustomBid] = useState(null); const [aiCfg, setAiCfg] = useState(null); // Derin mod: ince detaylar, belge kontrol listesi ve yol haritası da üretilir. // Çıktısı standart şemanın üst kümesi olduğu için varsayılan açık. const [derin, setDerin] = useState(true); useEffect(() => { if (isAiSpecAnalyzerOpen && aiCfg === null) getAiConfig().then(setAiCfg); }, [isAiSpecAnalyzerOpen, aiCfg]); if (!isAiSpecAnalyzerOpen) return null; // Analiz doküman panelinden de başlatılabiliyor — o durumda isAnalyzing false // kalır, bu yüzden context'teki ortak sinyalle birleştiriyoruz. const mesgul = isAnalyzing || aiAnalyzing; const results = !activeTenderDetail || aiAnalysisResults?.tenderKey===(activeTenderDetail.ikn || activeTenderDetail.id) ? aiAnalysisResults : null; const bid = results?.bidAnalysis || {}; const runAnalysis = async (text, fileName) => { setError(null);setRestoredSource(null); setIsAnalyzing(true); setSelectedFile(fileName); try { setProgress(derin ? 'Derin analiz üretiliyor (1-2 dk sürebilir)…' : 'Model analiz ediyor…'); const r = await analyzeSpecPdfWithGemini({ text, fileName, tender: activeTenderDetail, mode: derin ? 'derin' : undefined }); 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);setRestoredSource(null); setIsAnalyzing(true); setSelectedFile(file.name); try { setProgress('Belge metni çıkarılıyor…'); const { extractText } = await import('../services/pdfText'); const reading = await extractText(file); const { text, usedPages, pageCount } = reading; 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, ${derin ? 'derin analiz üretiliyor (1-2 dk sürebilir)…' : 'model analiz ediyor…'}`); const r = await analyzeSpecPdfWithGemini({ text, fileName: file.name, extraction:{ocr:reading.ocr,pageCount,usedPages,complete:reading.complete,fileName:file.name}, tender: activeTenderDetail, mode: derin ? 'derin' : undefined }); 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.

{const result=await openSavedAnalysis(job);setRestoredSource({text:job.input.text,tenderKey:result.tenderKey});setSelectedFile(result.fileName || null);setCustomBid(result.bidAnalysis?.recommendedBid ?? null);setError(null);}} /> {results && restoredSource?.tenderKey===results.tenderKey &&
Kaydedilen analiz kaynak metni
{restoredSource.text || 'Bu eski kayıtta kaynak metni bulunmuyor.'}
} {mesgul&&

Analiz sunucuda sürüyor. Bu ekranı kapatabilirsiniz; sonucu Kayıtlı AI işleri bölümünden açabilirsiniz.

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

AI hizmeti şu anda kullanılamıyor. Kayıtlı sonuçlarınızı açabilirsiniz. Yeni analiz için daha sonra tekrar deneyin veya uygulama yöneticisine bildirin.

)} {/* 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.

{derin ? 'Elenme sebepleri, gözden kaçan ince detaylar, belge kontrol listesi, kritik tarihler ve teklife kadar adım adım yol haritası üretilir. Daha uzun sürer.' : 'Yalnızca özet, yeterlik kriterleri, riskler ve teklif aralığı üretilir. Daha hızlıdır.'}

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

Analiz tamamlanamadı

{error}

)} {/* Yükleniyor */} {mesgul && (
{progress || 'Evraklar analiz ediliyor…'}

{selectedFile}

)} {/* Sonuç */} {!mesgul && results && (
Şartname Özeti {results.mode === 'derin' && ( Derin Analiz )} {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 && (
  • Bu bölüm için yapılandırılmış yeterlik listesi çıkarılamadı. Derin analizdeki belge ve şartları kontrol edin.
  • )} {(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 && (

Bu bölümde yapılandırılmış ceza/risk listesi yok. Bu, belgede risk bulunmadığı anlamına gelmez.

)} {(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.

)}
); };