Verify analysis recovery in browser and refresh new job status
This commit is contained in:
@@ -11,7 +11,14 @@ export function AiJobHistory({ikn,onOpen,revision,locked=false,operationFilter='
|
||||
try {await fn();}catch(e){if(alive.current)setError(e.message);}finally {if(alive.current)setBusy(false);}
|
||||
};
|
||||
const refresh=async()=>{const data=await listAiJobs(ikn);if(!Array.isArray(data.jobs))throw new Error('İş listesi alınamadı.');if(alive.current)setJobs(data.jobs.filter(job=>operationFilter==='offer'?job.operation!=='analiz':job.operation===operationFilter));};
|
||||
useEffect(()=>{void act(refresh);},[ikn,revision,operationFilter]);
|
||||
useEffect(()=>{
|
||||
let current=true;setBusy(true);setError(null);
|
||||
listAiJobs(ikn).then(data=>{
|
||||
if(!Array.isArray(data.jobs))throw new Error('İş listesi alınamadı.');
|
||||
if(current)setJobs(data.jobs.filter(job=>operationFilter==='offer'?job.operation!=='analiz':job.operation===operationFilter));
|
||||
}).catch(e=>{if(current)setError(e.message);}).finally(()=>{if(current)setBusy(false);});
|
||||
return()=>{current=false;};
|
||||
},[ikn,revision,operationFilter]);
|
||||
return <details className="border rounded-xl p-3 text-xs">
|
||||
<summary className="font-bold cursor-pointer">Kayıtlı AI işleri</summary>
|
||||
<p className="mt-2">Ekran kapansa da sunucudaki işler devam eder. Son 50 iş gösterilir. Kesilen işi yeniden başlatmak sağlayıcıya yeni istek gönderir. Eski sonucu açmak asistanın mevcut metinlerini değiştirir.</p>
|
||||
|
||||
@@ -12,7 +12,7 @@ const tl = (n) => (typeof n === 'number' && isFinite(n) ? '₺' + n.toLocaleStri
|
||||
export const AiSpecAnalyzerModal = () => {
|
||||
const {
|
||||
isAiSpecAnalyzerOpen, setIsAiSpecAnalyzerOpen,
|
||||
analyzeSpecPdfWithGemini, openSavedAnalysis, authUser, aiAnalysisResults, aiAnalyzing, activeAiModel, activeTenderDetail,
|
||||
analyzeSpecPdfWithGemini, openSavedAnalysis, authUser, aiAnalysisResults, aiAnalyzing, aiJobStatus, activeAiModel, activeTenderDetail,
|
||||
} = useApp();
|
||||
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
@@ -114,29 +114,21 @@ export const AiSpecAnalyzerModal = () => {
|
||||
<p className="text-xs text-blue-200">Şartname dosyanızı yükleyin; yeterlik, riskler ve teklif aralığı gerçek AI ile analiz edilsin.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => setIsAiSpecAnalyzerOpen(false)} className="text-slate-400 hover:text-white p-1 rounded-lg transition-colors">
|
||||
<button aria-label="Analiz ekranını kapat" onClick={() => setIsAiSpecAnalyzerOpen(false)} className="text-slate-400 hover:text-white p-1 rounded-lg transition-colors">
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6 overflow-y-auto">
|
||||
<AiJobHistory key={`${authUser?.email}:${getToken()}:${activeTenderDetail?.ikn || ''}`} ikn={activeTenderDetail?.ikn}
|
||||
operationFilter="analiz" locked={mesgul} revision={`${isAiSpecAnalyzerOpen}:${aiAnalyzing}`}
|
||||
operationFilter="analiz" locked={mesgul} revision={`${isAiSpecAnalyzerOpen}:${aiAnalyzing}:${aiJobStatus}`}
|
||||
onOpen={async job=>{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 && <details className="border rounded p-3 text-sm"><summary>Kaydedilen analiz kaynak metni</summary><pre className="whitespace-pre-wrap max-h-64 overflow-auto">{restoredSource.text || 'Bu eski kayıtta kaynak metni bulunmuyor.'}</pre></details>}
|
||||
{mesgul&&<p role="status" className="text-sm text-blue-800">Analiz sunucuda sürüyor. Bu ekranı kapatabilirsiniz; sonucu Kayıtlı AI işleri bölümünden açabilirsiniz.</p>}
|
||||
|
||||
{/* AI yapılandırılmamış uyarısı */}
|
||||
{aiCfg && !aiCfg.configured && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3">
|
||||
<Info className="w-4 h-4 text-amber-700 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-amber-900 leading-relaxed">
|
||||
<strong>AI henüz yapılandırılmamış.</strong> En kolayı: sunucuya tek bir{' '}
|
||||
<code className="bg-amber-100 px-1 rounded">OPENROUTER_API_KEY</code> 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.</p>
|
||||
</div>
|
||||
<p role="alert" className="text-sm text-amber-800 bg-amber-50 border border-amber-200 rounded p-3">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.</p>
|
||||
)}
|
||||
|
||||
{/* Uploader */}
|
||||
|
||||
@@ -159,7 +159,7 @@ export const AppProvider = ({ children }) => {
|
||||
|
||||
// Giriş yapınca kullanıcı verisini SUNUCUDAN yükle (localStorage'ı ezer)
|
||||
useEffect(() => {
|
||||
aiRunRef.current=null;setAiAnalyzing(false);
|
||||
aiRunRef.current=null;setAiAnalyzing(false);setAiJobStatus('');
|
||||
setVeriSenkron(false); setPlan(null); setSaveError(null);
|
||||
setFavoriteKurumlar([]); setTeamMembers([]); setSavedFilters([]); setCompetitorWatchlist([]);
|
||||
setReminderSettings(defaultReminder()); setUserProfile(defaultProfile(authUser));
|
||||
@@ -338,6 +338,7 @@ export const AppProvider = ({ children }) => {
|
||||
// tetiklenebiliyor; modalın beklerken boş görünmemesi için ortak sinyal.
|
||||
const [aiAnalyzing, setAiAnalyzing] = useState(false);
|
||||
const aiRunRef=useRef(null);
|
||||
const [aiJobStatus,setAiJobStatus]=useState('');
|
||||
|
||||
// Enterprise Module States
|
||||
// Rakip izleme listesi — kullanıcı gerçek kazanan firma analizinden ekler (hesaba özel sunucu kaydı)
|
||||
@@ -454,7 +455,7 @@ export const AppProvider = ({ children }) => {
|
||||
setAiAnalysisResults(null);
|
||||
try {
|
||||
const { analyzeSpecText } = await import('../services/ai');
|
||||
const data = await analyzeSpecText({ text, fileName, tender, chain: aiModelConfig.chain, mode, extraction, reviewedSource });
|
||||
const data = await analyzeSpecText({ text, fileName, tender, chain: aiModelConfig.chain, mode, extraction, reviewedSource },{onProgress:job=>{if(getToken()===session && aiRunRef.current===run)setAiJobStatus(`${job.id}:${job.state}`);}});
|
||||
const {analysisView}=await import('../services/ai');
|
||||
const result=analysisView(data,{tender,mode,fileName});
|
||||
if(getToken() !== session || aiRunRef.current!==run) throw new Error('Oturum değişti; analiz sonucu bu hesaba aktarılmadı.');
|
||||
@@ -785,6 +786,7 @@ export const AppProvider = ({ children }) => {
|
||||
openSavedAnalysis,
|
||||
aiAnalysisResults,
|
||||
aiAnalyzing,
|
||||
aiJobStatus,
|
||||
competitorWatchlist,
|
||||
setCompetitorWatchlist,
|
||||
vaultDocuments,
|
||||
|
||||
+2
-2
@@ -14,10 +14,10 @@ export async function getAiConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function analyzeSpecText(input) {
|
||||
export async function analyzeSpecText(input,{onProgress}={}) {
|
||||
const session=getToken();
|
||||
const check=()=>{if(getToken()!==session)throw new Error('Oturum değişti; kayıtlı analiz bu hesaba aktarılmadı.');};
|
||||
return runDurableAiJob('analiz',input,{check});
|
||||
return runDurableAiJob('analiz',input,{check,onProgress});
|
||||
}
|
||||
|
||||
export function analysisView(data,input) {
|
||||
|
||||
Reference in New Issue
Block a user