diff --git a/server/ai-teklif.mjs b/server/ai-teklif.mjs index b2ff5e1..291e68e 100644 --- a/server/ai-teklif.mjs +++ b/server/ai-teklif.mjs @@ -71,7 +71,7 @@ export async function belgeyeSor({ ikn, soru, text, tender, chain }) { const out = await analyzeSpec({ text: `PARÇALAR:\n\n${baglam}\n\nSORU: ${q}\n\nYalnızca {"cevap":"..."} JSON'u döndür.`, tender, chain, systemOverride: sistem }); const cevap = String(out?.result?.cevap ?? '').trim() || 'Yanıt üretilemedi.'; const kullanilan = new Set([...cevap.matchAll(/\[(\d+)\]/g)].map((m) => Number(m[1]))); - return { cevap, kaynaklar: (kullanilan.size ? kaynaklar.filter((k) => kullanilan.has(k.n)) : kaynaklar.slice(0, 3)).map((k) => ({ n: k.n, alinti: k.icerik.slice(0, 1400) })), kaynak, model: out.model?.label }; + return { cevap, kaynaklar: (kullanilan.size ? kaynaklar.filter((k) => kullanilan.has(k.n)) : kaynaklar.slice(0, 3)).map((k) => ({ n: k.n, alinti: k.icerik.slice(0, 1400) })), kaynak }; } function gereklilikMetni(tender, gereklilikler, metin) { @@ -79,8 +79,17 @@ function gereklilikMetni(tender, gereklilikler, metin) { for (const x of Array.isArray(gereklilikler) ? gereklilikler : []) if (x) g.push(typeof x === 'string' ? x : x.belge || x.item || x.ad || JSON.stringify(x)); for (const y of Array.isArray(tender?.yeterlikKriterleri) ? tender.yeterlikKriterleri : []) if (y) g.push(String(y)); for (const i of Array.isArray(tender?.items) ? tender.items.slice(0, 30) : []) g.push(`Kalem: ${i.ad} ${i.miktar ?? ''} ${i.birim ?? ''}`.trim()); - if (tender?.ikn && kalemStore.available()) for (const k of (kalemStore.kalemOku(tender.ikn) || []).slice(0, 30)) g.push(`Kalem: ${k.ad} ${k.miktar ?? ''} ${k.birim ?? ''}`.trim()); - if (metin) for (const s of ['teminat', 'teslim süresi', 'geçerlilik', 'ceza', 'numune', 'belge']) { const p = getir(parcala(metin), s, 1)[0]; if (p) g.push(`${s}: ${p.icerik.slice(0, 300).replace(/\s+/g, ' ')}`); } + if (tender?.ikn && kalemStore.available()) for (const k of ((kalemStore.kalemOku(tender.ikn) || {}).kalemler || []).slice(0, 30)) g.push(`Kalem: ${k.ad} ${k.miktar ?? ''} ${k.birim ?? ''}`.trim()); + if (metin) { + // Önce anahtar sözcüğü geçen SATIR (madde) alınır; bütün metni yapıştırmak okunmaz oluyordu. + const satirlar = String(metin).split(/\r?\n+/).map((x) => x.trim()).filter((x) => x.length > 15); + for (const s of ['teminat', 'teslim süresi', 'geçerlilik', 'ceza', 'numune', 'belge']) { + const satir = satirlar.find((x) => x.toLocaleLowerCase('tr-TR').includes(s)); + if (satir) { g.push(`${s}: ${satir.slice(0, 300).replace(/\s+/g, ' ')}`); continue; } + const p = getir(parcala(metin), s, 1)[0]; + if (p) g.push(`${s}: ${p.icerik.slice(0, 300).replace(/\s+/g, ' ')}`); + } + } return [...new Set(g)].slice(0, 60); } @@ -100,7 +109,7 @@ export async function teklifDenetle({ ikn, taslak, gereklilikler, tender, chain, }); const ok = maddeler.filter((m) => m.durum === 'karsilaniyor').length; const yarim = maddeler.filter((m) => m.durum === 'belirsiz').length; - return { ozet: String(out?.result?.ozet ?? '').slice(0, 800), maddeler, uyum_yuzde: Math.round(((ok + yarim * 0.5) / maddeler.length) * 100), eksikler: maddeler.filter((m) => m.durum === 'eksik').map((m) => m.gereklilik), model: out.model?.label }; + return { ozet: String(out?.result?.ozet ?? '').slice(0, 800), maddeler, uyum_yuzde: Math.round(((ok + yarim * 0.5) / maddeler.length) * 100), eksikler: maddeler.filter((m) => m.durum === 'eksik').map((m) => m.gereklilik) }; } const BOLUMLER = { @@ -127,5 +136,5 @@ export async function teklifTaslak({ ikn, tur, tender, firma, belgeler, fiyat, g .map((b) => ({ baslik: String(b?.baslik ?? '').slice(0, 200), icerik: String(b?.icerik ?? '').slice(0, 12000) })) .filter((b) => b.baslik && b.icerik); if (!bolumler.length) throw new Error('Model bölüm üretmedi; tekrar deneyin.'); - return { bolumler, model: out.model?.label }; + return { bolumler }; } diff --git a/server/ai.mjs b/server/ai.mjs index 65c3e1e..db194d6 100644 --- a/server/ai.mjs +++ b/server/ai.mjs @@ -276,7 +276,39 @@ function extractJson(raw) { const start = s.indexOf('{'); const end = s.lastIndexOf('}'); if (start === -1 || end === -1) throw new Error('Yanıtta JSON bulunamadı'); - return JSON.parse(s.slice(start, end + 1)); + const govde = s.slice(start, end + 1); + try { return JSON.parse(govde); } catch (e) { + const onarilmis = kesikJsonOnar(s.slice(start)); + if (onarilmis) return onarilmis; + throw e; + } +} + +/** + * Model çıktısı max_tokens'ta kesildiğinde JSON yarım kalır. Sondan geriye + * doğru güvenli kesme noktaları denenir; açık kalan dizi/nesneler kapatılır. + */ +function kesikJsonOnar(s) { + for (let cut = s.length; cut > 50; ) { + cut = Math.max(s.lastIndexOf('}', cut - 1), s.lastIndexOf(']', cut - 1), s.lastIndexOf('"', cut - 1)); + if (cut <= 0) return null; + let parca = s.slice(0, cut + 1).replace(/,\s*$/, ''); + // yığın: string dışındaki açık parantezler + const yigin = []; let strIcinde = false, kacis = false; + for (const ch of parca) { + if (strIcinde) { if (kacis) kacis = false; else if (ch === '\\') kacis = true; else if (ch === '"') strIcinde = false; continue; } + if (ch === '"') strIcinde = true; + else if (ch === '{' || ch === '[') yigin.push(ch); + else if (ch === '}' || ch === ']') yigin.pop(); + } + if (strIcinde) { cut -= 1; continue; } + parca = parca.replace(/,\s*$/, ''); + // "anahtar": ile biten yarım çift -> anahtarı at + parca = parca.replace(/,?\s*"[^"]*"\s*:\s*$/, ''); + while (yigin.length) parca += yigin.pop() === '{' ? '}' : ']'; + try { return JSON.parse(parca); } catch { cut -= 1; } + } + return null; } /** Zinciri sırayla dener; ilk başarılı sonucu döndürür. */ @@ -288,7 +320,7 @@ export async function analyzeSpec({ text, fileName, tender, chain, systemOverrid // kesilirse JSON parse edilemez ve zincir boşuna tüketilir. const userPrompt = buildUserPrompt({ text, fileName, tender, limit: derin ? 90000 : 60000 }); const sistem = systemOverride || (derin ? DERIN_SYSTEM_PROMPT : SYSTEM_PROMPT); - const maxTokens = derin ? 8000 : 4000; + const maxTokens = derin ? 12000 : 4000; const timeoutMs = derin ? 150000 : 90000; const attempts = []; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index be13e5f..e042e72 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -54,7 +54,7 @@ export const Header = () => { {/* Search Bar */} -