Analyze server-resolved document reviews with revision checks
This commit is contained in:
@@ -245,6 +245,9 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
{kaynakGecerlilik.status === 'conflict' && <p role="alert" className="text-red-700">Kaynaklarda farklı süreler var. Güncel resmi dokümanı belirleyip analizi yenileyin; çelişki çözülmeden çıktı alınamaz.</p>}
|
||||
{[...new Set(kaynakGecerlilik.evidence.map(e=>e.quote))].map(quote=><blockquote key={quote}>{quote}</blockquote>)}
|
||||
</div>}
|
||||
{effectiveResults.sourceReview?.humanReview && <p className="text-xs text-slate-600">
|
||||
Metin incelemesi: sürüm {effectiveResults.sourceReview.humanReview.revision} · {effectiveResults.sourceReview.humanReview.reviewedBy} · {effectiveResults.sourceReview.humanReview.reviewedAt}. Kullanıcı karşılaştırmasıdır; resmi onay değildir.
|
||||
</p>}
|
||||
{analizEksik && <p role="alert" className="text-xs text-red-700">Analiz kısmi: kaynak, OCR veya kalem bilgisi kontrol gerektiriyor. {(effectiveResults.sourceReview.issues || []).join(' ')} Tam ve doğrulanabilir kaynakla analizi tamamlayın.</p>}
|
||||
{/* Sizden beklenen bilgiler */}
|
||||
{eksikler.length > 0 && (
|
||||
|
||||
@@ -2,7 +2,7 @@ import React,{useEffect,useRef,useState} from 'react';
|
||||
import {getDocumentTextReview,saveDocumentTextReview} from '../services/account';
|
||||
import {getToken} from '../services/auth';
|
||||
|
||||
export function DocumentTextReview({documentId,extractedText}) {
|
||||
export function DocumentTextReview({documentId,extractedText,onAnalyze}) {
|
||||
const [source,setSource]=useState(null);
|
||||
const [text,setText]=useState(extractedText || '');
|
||||
const [note,setNote]=useState('');
|
||||
@@ -10,6 +10,7 @@ export function DocumentTextReview({documentId,extractedText}) {
|
||||
const [busy,setBusy]=useState(false);
|
||||
const [error,setError]=useState(null);
|
||||
const [saved,setSaved]=useState(null);
|
||||
const [analyzing,setAnalyzing]=useState(false);
|
||||
const live=useRef(true);
|
||||
const sequence=useRef(0);
|
||||
const load=async()=>{
|
||||
@@ -38,27 +39,39 @@ export function DocumentTextReview({documentId,extractedText}) {
|
||||
} catch(e) {if(live.current && token===getToken())setError(e.message);}
|
||||
finally {if(live.current && token===getToken())setBusy(false);}
|
||||
};
|
||||
const analyze=async()=>{
|
||||
if(!source?.review || source.stale || busy || analyzing)return;
|
||||
const token=getToken();setAnalyzing(true);setError(null);
|
||||
try {await onAnalyze({documentId,revision:source.review.revision});}
|
||||
catch(e) {if(live.current && token===getToken())setError(e.message);}
|
||||
finally {if(live.current && token===getToken())setAnalyzing(false);}
|
||||
};
|
||||
return <section aria-label="Belge metni incelemesi" className="border-t pt-3 space-y-2">
|
||||
<h5 className="font-bold text-sm">Metni karşılaştır ve düzelt</h5>
|
||||
<p className="text-xs text-slate-600">Belgenin tüm sayfalarını açıp metin, miktar ve birimleri karşılaştırın. Bu kayıt kullanıcı incelemesidir; resmî onay veya e-imza değildir. Kaydetmek tek başına teklif dosyasını hazır yapmaz.</p>
|
||||
{source?.stale && <p role="alert" className="text-xs text-amber-800">Kaynak dosya değişmiş. Önceki inceleme geçerli değil; tüm sayfaları yeniden karşılaştırın.</p>}
|
||||
{source?.review && <p className="text-xs text-slate-500">Son kayıt: sürüm {source.review.revision} · {new Date(source.review.reviewed_at).toLocaleString('tr-TR')}</p>}
|
||||
<label className="block text-xs font-semibold">Kontrol edilen metin
|
||||
<textarea value={text} onChange={e=>{setText(e.target.value);setCompared(false);setSaved(null);}} disabled={busy}
|
||||
<textarea value={text} onChange={e=>{setText(e.target.value);setCompared(false);setSaved(null);}} disabled={busy || analyzing}
|
||||
className="block w-full h-64 border rounded p-2 font-mono font-normal mt-1" />
|
||||
</label>
|
||||
<label className="block text-xs font-semibold">Karşılaştırılan sayfalar ve düzeltmeler
|
||||
<textarea value={note} onChange={e=>{setNote(e.target.value);setCompared(false);setSaved(null);}} disabled={busy}
|
||||
<textarea value={note} onChange={e=>{setNote(e.target.value);setCompared(false);setSaved(null);}} disabled={busy || analyzing}
|
||||
placeholder="Örnek: Sayfa 1–3 karşılaştırıldı; 2. sayfadaki miktar 100 olarak düzeltildi."
|
||||
className="block w-full border rounded p-2 font-normal mt-1" />
|
||||
</label>
|
||||
<label className="flex gap-2 text-xs"><input type="checkbox" checked={compared} onChange={e=>setCompared(e.target.checked)} disabled={busy}/>
|
||||
<label className="flex gap-2 text-xs"><input type="checkbox" checked={compared} onChange={e=>setCompared(e.target.checked)} disabled={busy || analyzing}/>
|
||||
Bu metni kaynak dosyanın tüm sayfalarıyla karşılaştırdım.</label>
|
||||
<button onClick={save} disabled={busy || !source || !compared || text.trim().length<40 || note.trim().length<20}
|
||||
<button onClick={save} disabled={busy || analyzing || !source || !compared || text.trim().length<40 || note.trim().length<20}
|
||||
className="text-xs bg-blue-700 text-white px-3 py-2 rounded disabled:opacity-40">{busy ? 'İşleniyor…' : 'İncelemeyi kaydet'}</button>
|
||||
{onAnalyze && source?.review && !source.stale && <button onClick={analyze}
|
||||
disabled={busy || analyzing || text.trim()!==source.review.corrected_text || note.trim()!==source.review.review_note}
|
||||
className="ml-2 text-xs bg-amber-500 px-3 py-2 rounded disabled:opacity-40">
|
||||
{analyzing ? 'İncelenmiş metin analiz ediliyor…' : 'Kaydedilmiş incelemeyi analiz et'}
|
||||
</button>}
|
||||
{error && <div role="alert" className="text-xs text-red-700">{error}
|
||||
<p>Metin taslağınız bu ekranda korunuyor. Sürüm değiştiyse taslağı kopyalayıp güncel kaydı yükleyin.</p>
|
||||
<button onClick={load} disabled={busy} className="underline">Güncel kaydı yükle (taslağı değiştirir)</button>
|
||||
<button onClick={load} disabled={busy || analyzing} className="underline">Güncel kaydı yükle (taslağı değiştirir)</button>
|
||||
</div>}
|
||||
{saved && <p role="status" className="text-xs text-green-800">İnceleme sürüm {saved.revision} sunucuda kaydedildi. Analiz ve teklif kontrolleri ayrıca tamamlanmalıdır.</p>}
|
||||
</section>;
|
||||
|
||||
@@ -420,7 +420,10 @@ export const TenderDocuments = ({ tender }) => {
|
||||
Metni resmi dokümanla karşılaştırın; okunmuş olması doğruluğunun onaylandığı anlamına gelmez.
|
||||
</p>
|
||||
<pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words text-xs">{metinOnizleme.text || 'Okunabilir metin bulunamadı.'}</pre>
|
||||
{metinOnizleme.type === 'ihale' && <DocumentTextReview key={metinOnizleme.documentId} documentId={metinOnizleme.documentId} extractedText={metinOnizleme.text} />}
|
||||
{metinOnizleme.type === 'ihale' && <DocumentTextReview key={metinOnizleme.documentId} documentId={metinOnizleme.documentId} extractedText={metinOnizleme.text} onAnalyze={async(reviewedSource)=>{
|
||||
await analyzeSpecPdfWithGemini({reviewedSource,tender,fileName:metinOnizleme.name});
|
||||
setIsAiSpecAnalyzerOpen(true);
|
||||
}} />}
|
||||
<button onClick={()=>setMetinOnizleme(null)} className="text-xs underline">Metin önizlemesini kapat</button>
|
||||
</section>}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user