import React, { useState, useRef, useEffect } from 'react'; import { MessageSquareText, ClipboardCheck, PenLine, Loader2, Copy, Upload } from 'lucide-react'; import { getToken } from '../services/auth'; import { listDocuments } from '../services/account'; import { useApp } from '../context/AppContext'; import { requestOfferAssistant } from '../services/offerAssistant'; /** * Teklif Asistanı sekmesi — üç araç (İhaleAB'den taşındı): * 1) Belgeye Sor: doküman/ilan metninde soru → [n] atıflı yanıt * 2) Taslağı Denetle: teklif taslağı × gereklilikler → karşılanıyor / eksik / belirsiz * 3) Taslak Üret: kapak / teknik / ticari / tam dosya (rakam uydurmaz, [DOLDURUN]) * Doküman metni sunucuda EKAP'tan alınır; yoksa buradan .txt/.pdf metni yapıştırılır. */ const ORNEKLER = ['Geçici teminat oranı ve süresi nedir?', 'Teslim süresi kaç gün?', 'Gecikme cezası ne kadar?', 'Hangi belgeler istenmiş?', 'Numune isteniyor mu?']; const DURUM = { karsilaniyor: ['Karşılanıyor', 'bg-emerald-100 text-emerald-800 border-emerald-200'], eksik: ['Eksik', 'bg-red-100 text-red-800 border-red-200'], belirsiz: ['Belirsiz', 'bg-amber-100 text-amber-800 border-amber-200'] }; export const TeklifAsistani = ({ tender }) => { const {authUser} = useApp(); const token = getToken(); const identity = useRef({token, revision:0}); if (identity.current.token !== token) identity.current = {token, revision:identity.current.revision + 1}; return ; }; const OfferAssistantSession = ({ tender }) => { const live = useRef(true); const session = useRef(getToken()); const busy = useRef(false); useEffect(() => { live.current = true; return () => { live.current = false; }; }, []); const current = () => live.current && Boolean(session.current) && getToken() === session.current; const post = (url, body, documents) => requestOfferAssistant({url, body, readToken:getToken, isCurrent:current, listDocuments:documents}); const [arac, setArac] = useState('sor'); const [metin, setMetin] = useState(''); // isteğe bağlı: yapıştırılan doküman metni const [sourceName, setSourceName] = useState(''); const [soru, setSoru] = useState(''); const [cevap, setCevap] = useState(null); const [taslak, setTaslak] = useState(''); const [denetim, setDenetim] = useState(null); const [tur, setTur] = useState('tam'); const [bolumler, setBolumler] = useState([]); const [draftReview, setDraftReview] = useState(null); const [draftSource, setDraftSource] = useState(null); const [mesgul, setMesgul] = useState(false); const [hata, setHata] = useState(null); const calistir = async (fn) => { if (busy.current || !current()) return; busy.current = true; setMesgul(true); setHata(null); try { await fn(); } catch (e) { if (current()) setHata(e.message); } finally { busy.current = false; if (current()) setMesgul(false); } }; const sor = (q) => calistir(async () => { const result = await post('/api/ai/belge-sor', {ikn:tender.ikn, soru:q, text:metin || undefined, tender}); if (current()) setCevap(result); }); const denetle = () => calistir(async () => { const result = await post('/api/ai/teklif-denetim', {ikn:tender.ikn, taslak, tender, text:metin || undefined}); if (current()) setDenetim(result); }); const uret = () => calistir(async () => { const result = await post('/api/ai/teklif-taslak', {ikn:tender.ikn, tur, tender, text:metin || undefined}, listDocuments); if (current()) { setBolumler(result.bolumler || []); setDraftReview(result.draftReview || null); setDraftSource(metin); } }); const kopyala = (t) => { if (current()) navigator.clipboard?.writeText(t).catch(() => {}); }; const dosyaOku = (f) => { if (!f) return; return calistir(async () => { const { extractText } = await import('../services/pdfText'); if (!current()) return; const r = await extractText(f); if (typeof r.text !== 'string' || !r.text.trim()) throw new Error('Dosyada okunabilir metin bulunamadı. Önceki kaynak değiştirilmedi. Başka bir dosya seçin.'); if (current()) { setMetin(r.text); setSourceName(f.name); setCevap(null); setDenetim(null); } }); }; const Sekme = ({ id, icon: Icon, children }) => ( ); return (
Belgeye Sor Taslağı Denetle Taslak Üret
{metin && }

Metin sağlanmazsa sunucu EKAP dokümanını / ilan metnini kullanır. Yanıtların kaynak parçalarını kontrol edin; AI yanıtları hata veya eksik bilgi içerebilir.

{hata &&

{hata}

} {arac === 'sor' && (
{ e.preventDefault(); if (soru.trim().length >= 3) sor(soru); }}> setSoru(e.target.value)} placeholder="Ör. Geçici teminat oranı nedir?" className="flex-1 bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm" />
{!cevap &&
{ORNEKLER.map((o) => )}
} {cevap && (

{cevap.cevap}

{cevap.kaynaklar?.map((k) => (
[{k.n}] kaynak parça
{k.alinti}
))} {cevap.kapsam &&

{cevap.kapsam} {cevap.sourceReview && `${cevap.sourceReview.totalChunks} parçadan ${cevap.sourceReview.selectedChunks} tanesi modele sunuldu.`}

} {cevap.kaynak &&

Kaynak: {cevap.kaynak}

}
)}
)} {arac === 'denet' && (