Verify provider failure preserves drafts and improve user feedback
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"environment": "Isolated local QA, real provider-not-configured response, current frontend",
|
||||||
|
"draftRetained": true,
|
||||||
|
"snapshot": " - dialog \"QA — Giresun laboratuvar\":\n - button \"Listeye Geri Dön\":\n - generic: Listeye Geri Dön\n - button \"İncelendi olarak işaretle\"\n - button \"Takip Et\":\n - generic: Takip Et\n - button \"🗂 Hazırlık Görevi Aç\"\n - button \"İhale detayını kapat\":\n - generic: Mal Alımı\n - generic: \"İKN: 2026/1582991\"\n - generic: \"Ekleme:\"\n - heading \"QA — Giresun laboratuvar\" [level=1]\n - generic: Kurum Adı\n - generic \"Giresun TEST KURUMU\"\n - generic: İhale Tarihi & Saat\n - generic: 10.09.2026 - 10:00\n - generic: Şehir / Bölge\n - generic: Giresun ()\n - generic: Yaklaşık Maliyet\n - generic: Gizli / Belirtilmemiş\n - button \"İhale Detayları\":\n - generic: İhale Detayları\n - button \"Birim Fiyat Cetveli (PDF & Kaşe) İnteraktif\":\n - generic: Birim Fiyat Cetveli (PDF & Kaşe)\n - generic: İnteraktif\n - button \"Yapay Zeka Asistanı\":\n - generic: Yapay Zeka Asistanı\n - button \"Hazırlık Checklist\":\n - generic: Hazırlık Checklist\n - button \"Teklif Asistanı AI\":\n - generic: Teklif Asistanı\n - generic: AI\n - button \"Belgeye Sor\":\n - text: Belgeye Sor\n - button \"Taslağı Denetle\":\n - text: Taslağı Denetle\n - button \"Taslak Üret\":\n - text: Taslak Üret\n - text: Doküman yükle (.pdf/.txt) — isteğe bağlı\n - paragraph: 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.\n - alert: AI hizmeti şu anda kullanılamıyor. Çalışmanız korundu; daha sonra yeniden deneyin. Sorun sürerse uygulama yöneticinize bildirin.\n - textbox \"Teklif mektubu / teknik teklif metninizi yapıştırın (en az 200 karakter)…\": SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez. SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez. SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez.\n - button \"Denetle\":\n - text: Denetle\n- button \"Kullanım kılavuzu (? tuşu)\":"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:5175/",
|
||||||
|
"scenario": "No AI provider configured in isolated local test server",
|
||||||
|
"source": "Browser DOM observation",
|
||||||
|
"draftRetained": true,
|
||||||
|
"snapshot": " - dialog \"QA — Giresun laboratuvar\":\n - button \"Listeye Geri Dön\":\n - generic: Listeye Geri Dön\n - button \"İncelendi olarak işaretle\"\n - button \"Takip Et\":\n - generic: Takip Et\n - button \"🗂 Hazırlık Görevi Aç\"\n - button \"İhale detayını kapat\":\n - generic: Mal Alımı\n - generic: \"İKN: 2026/1582991\"\n - generic: \"Ekleme:\"\n - heading \"QA — Giresun laboratuvar\" [level=1]\n - generic: Kurum Adı\n - generic \"Giresun TEST KURUMU\"\n - generic: İhale Tarihi & Saat\n - generic: 10.09.2026 - 10:00\n - generic: Şehir / Bölge\n - generic: Giresun ()\n - generic: Yaklaşık Maliyet\n - generic: Gizli / Belirtilmemiş\n - button \"İhale Detayları\":\n - generic: İhale Detayları\n - button \"Birim Fiyat Cetveli (PDF & Kaşe) İnteraktif\":\n - generic: Birim Fiyat Cetveli (PDF & Kaşe)\n - generic: İnteraktif\n - button \"Yapay Zeka Asistanı\":\n - generic: Yapay Zeka Asistanı\n - button \"Hazırlık Checklist\":\n - generic: Hazırlık Checklist\n - button \"Teklif Asistanı AI\":\n - generic: Teklif Asistanı\n - generic: AI\n - button \"Belgeye Sor\":\n - text: Belgeye Sor\n - button \"Taslağı Denetle\":\n - text: Taslağı Denetle\n - button \"Taslak Üret\":\n - text: Taslak Üret\n - text: Doküman yükle (.pdf/.txt) — isteğe bağlı\n - paragraph: 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.\n - paragraph: AI yapılandırılmamış — sunucuya bir sağlayıcı anahtarı ekleyin.\n - textbox \"Teklif mektubu / teknik teklif metninizi yapıştırın (en az 200 karakter)…\": SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez. SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez. SENTETİK QA TASLAĞI. Bu metin yalnız hata durumunda kullanıcının çalışmasının korunduğunu sınamak içindir. Gerçek teklif, taahhüt veya başvuru içermez.\n - button \"Denetle\":\n - text: Denetle\n- button \"Kullanım kılavuzu (? tuşu)\":"
|
||||||
|
}
|
||||||
@@ -471,3 +471,13 @@ AI HTTP isteği ve yanıt okumasına 120 saniyelik istemci zaman aşımı eklend
|
|||||||
Doğrulama: HTML/null/boş yanıt, geçersiz iç durum, askıda istek ve üç gerçek sunucu doğrulayıcısının ürettiği yanıtın istemci sözleşmesiyle uyumu test edildi. Tarayıcıdaki gerçek sağlayıcı arızası kabulü ayrıca açık.
|
Doğrulama: HTML/null/boş yanıt, geçersiz iç durum, askıda istek ve üç gerçek sunucu doğrulayıcısının ürettiği yanıtın istemci sözleşmesiyle uyumu test edildi. Tarayıcıdaki gerçek sağlayıcı arızası kabulü ayrıca açık.
|
||||||
|
|
||||||
Sonuç: Tüm 103 test ve üretim derlemesi geçti. Büyük paket uyarıları devam ediyor. Canlı yayın yapılmadı; tüm plan tamamlanmış değildir.
|
Sonuç: Tüm 103 test ve üretim derlemesi geçti. Büyük paket uyarıları devam ediyor. Canlı yayın yapılmadı; tüm plan tamamlanmış değildir.
|
||||||
|
|
||||||
|
### D09 — Sağlayıcı yokken gerçek tarayıcı hata kabulü
|
||||||
|
|
||||||
|
Mevcut localhost:5175 QA sunucusunun canlı olduğu doğrulandı; yeniden başlatılmadı. Güncel ön yüz üzerinden QA Giresun → Teklif Asistanı → Taslağı Denetle akışında 200 karakterden uzun sentetik taslak girildi. Anahtarsız izole sunucunun gerçek başarısız yanıtı sonrasında taslağın korunduğu ve Denetle düğmesinin yeniden etkin olduğu DOM'dan doğrulandı.
|
||||||
|
|
||||||
|
İlk mesaj normal kullanıcıya sunucu anahtarı eklemesini söylüyordu. Kullanıcıya yönelik hizmet kullanılamıyor/yeniden dene açıklaması eklendi; hata `role=alert` olarak duyurulur. Sunucu genel analiz ve teklif araçlarının yapılandırılmamış sağlayıcı mesajları da sadeleştirildi. Güncel istemci eski sunucu 503 mesajlarını da uygun açıklamaya dönüştürür.
|
||||||
|
|
||||||
|
Değişiklikten sonra aynı tarayıcı akışı tekrarlandı: yeni alert, aynı taslak ve kullanılabilir Denetle düğmesi doğrulandı. Kanıtlar: `qa-offer-provider-error.json`, `qa-offer-provider-error-after.json`. Bu kayıtlar sağlayıcı yokluğu hata yolunu kanıtlar; gerçek AI üretimi, iki hesaplı tarayıcı geçişi, uzun analiz veya başarılı çıktı kabulü değildir. Yeni backend metni çalışan eski sunucuda ayrıca sınanmadı; tarayıcı yeni istemci dönüşümünü doğruladı.
|
||||||
|
|
||||||
|
Tüm 104 test ve üretim derlemesi geçti. Büyük paket uyarıları sürüyor. Planın kalan kapsamı açık; canlı yayın yapılmadı.
|
||||||
|
|||||||
+2
-2
@@ -1306,7 +1306,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
if (!aiConfigured()) {
|
if (!aiConfigured()) {
|
||||||
return json(res, 503, {
|
return json(res, 503, {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'AI yapılandırılmamış — sunucuya en az bir sağlayıcı anahtarı ekleyin (ZAI_API_KEY / DEEPSEEK_API_KEY / GEMINI_API_KEY …)',
|
error: 'AI hizmeti şu anda kullanılamıyor. Daha sonra yeniden deneyin; sorun sürerse uygulama yöneticinize bildirin.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -1456,7 +1456,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
if ((url.pathname === '/api/ai/belge-sor' || url.pathname === '/api/ai/teklif-denetim' || url.pathname === '/api/ai/teklif-taslak') && req.method === 'POST') {
|
if ((url.pathname === '/api/ai/belge-sor' || url.pathname === '/api/ai/teklif-denetim' || url.pathname === '/api/ai/teklif-taslak') && req.method === 'POST') {
|
||||||
const user = userFromAuthHeader(req.headers['authorization']);
|
const user = userFromAuthHeader(req.headers['authorization']);
|
||||||
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
||||||
if (!aiConfigured()) return json(res, 503, { success: false, error: 'AI yapılandırılmamış — sunucuya bir sağlayıcı anahtarı ekleyin.' });
|
if (!aiConfigured()) return json(res, 503, { success: false, error: 'AI hizmeti şu anda kullanılamıyor. Daha sonra yeniden deneyin; sorun sürerse uygulama yöneticinize bildirin.' });
|
||||||
try {
|
try {
|
||||||
const body = await readBody(req, 2 * 1024 * 1024);
|
const body = await readBody(req, 2 * 1024 * 1024);
|
||||||
const ortak = { ikn: body.ikn, tender: body.tender, chain: body.chain, text: body.text };
|
const ortak = { ikn: body.ikn, tender: body.tender, chain: body.chain, text: body.text };
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const OfferAssistantSession = ({ tender }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[11px] text-slate-500">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.</p>
|
<p className="text-[11px] text-slate-500">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.</p>
|
||||||
{hata && <p className="text-xs text-red-700 bg-red-50 border border-red-200 rounded-lg p-2">{hata}</p>}
|
{hata && <p role="alert" className="text-xs text-red-700 bg-red-50 border border-red-200 rounded-lg p-2">{hata}</p>}
|
||||||
|
|
||||||
{arac === 'sor' && (
|
{arac === 'sor' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export async function requestOfferAssistant({url, body, readToken, isCurrent = (
|
|||||||
const response = await fetcher(url, {signal:controller.signal, method:'POST', headers:{'Content-Type':'application/json', Authorization:`Bearer ${token}`}, body:JSON.stringify(payload)});
|
const response = await fetcher(url, {signal:controller.signal, method:'POST', headers:{'Content-Type':'application/json', Authorization:`Bearer ${token}`}, body:JSON.stringify(payload)});
|
||||||
const data = await response.json().catch(() => { throw new Error('Sunucudan okunabilir JSON yanıtı alınamadı. Yeniden deneyin.'); });
|
const data = await response.json().catch(() => { throw new Error('Sunucudan okunabilir JSON yanıtı alınamadı. Yeniden deneyin.'); });
|
||||||
check();
|
check();
|
||||||
|
if (response.status === 503) throw new Error('AI hizmeti şu anda kullanılamıyor. Çalışmanız korundu; daha sonra yeniden deneyin. Sorun sürerse uygulama yöneticinize bildirin.');
|
||||||
if (!response.ok || data?.success === false) throw new Error(typeof data?.error === 'string' ? data.error : `HTTP ${response.status}`);
|
if (!response.ok || data?.success === false) throw new Error(typeof data?.error === 'string' ? data.error : `HTTP ${response.status}`);
|
||||||
return validateOfferResponse(url, data);
|
return validateOfferResponse(url, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -43,3 +43,9 @@ test('a stalled AI request times out instead of locking the assistant indefinite
|
|||||||
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body,readToken:()=>'first',timeoutMs:5,
|
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body,readToken:()=>'first',timeoutMs:5,
|
||||||
fetcher:async(url,{signal})=>new Promise((resolve,reject)=>signal.addEventListener('abort',()=>reject(new DOMException('Aborted','AbortError')),{once:true}))}),/zaman aşımı/);
|
fetcher:async(url,{signal})=>new Promise((resolve,reject)=>signal.addEventListener('abort',()=>reject(new DOMException('Aborted','AbortError')),{once:true}))}),/zaman aşımı/);
|
||||||
});
|
});
|
||||||
|
test('service unavailability gives ordinary users actionable feedback, not server setup instructions',async()=>{
|
||||||
|
await assert.rejects(requestOfferAssistant({url:'/api/ai/belge-sor',body,readToken:()=>'first',
|
||||||
|
fetcher:async()=>Response.json({success:false,error:'Internal provider setup details'},{status:503})}),error=>{
|
||||||
|
assert.match(error.message,/Çalışmanız korundu/);assert.doesNotMatch(error.message,/Internal provider/);return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user