fix(sohbet): iç içe markdown bağlantısı düzleştiriliyor ([[x](y)](y) → [x](y))
Model paketsiz cevapta "[[/paket](/paket)](/paket)" yazıyor, ekranda köşeli parantezler ve "Paket (Paket)" kartı çıkıyordu (QA B3). Rehber eşlemesinden önce aynı adresi iki kez saran bağlantı her adres için düzleştirilir; akış tamponu `)`'den sonra `](` gelip gelmediğine bakar (bir karakterlik bekleme), parça parça çıktı tek seferlikle aynı kalır. baglanti-denetim'e iki senaryo: 56/56. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,9 @@
|
||||
*
|
||||
* lib/ai/baglanti-denetimi.ts gerçek content/rehber listesiyle sınanır:
|
||||
* kayıtlı slug dokunulmaz, yakın slug yeniden yazılır (başlık kayıttan),
|
||||
* uydurma slug düz metne iner; akış tamponunun parça parça çıktısı tek
|
||||
* seferlik sonuçla birebir aynı olmalı. Beklenti tutmazsa çıkış kodu 1.
|
||||
* uydurma slug düz metne iner; iç içe `[[x](y)](y)` bağlantısı (her adres)
|
||||
* `[x](y)` olur; akış tamponunun parça parça çıktısı tek seferlik sonuçla
|
||||
* birebir aynı olmalı. Beklenti tutmazsa çıkış kodu 1.
|
||||
*/
|
||||
import { BaglantiTamponu, rehberBaglantilariniDuzelt, type RehberKaydi } from "@/lib/ai/baglanti-denetimi";
|
||||
import { getRehberAramaKayitlari } from "@/lib/rehber";
|
||||
@@ -71,6 +72,19 @@ function senaryolar(rehberler: readonly RehberKaydi[]): Senaryo[] {
|
||||
bekle: `Oku: [${obp.baslik}](/rehber/${obp.slug}).`,
|
||||
duzeltme: 1,
|
||||
},
|
||||
{
|
||||
// QA 8 Eki 2026: paketsiz cevapta modelin ürettiği biçim
|
||||
ad: "ic-ice-paket-baglantisi",
|
||||
metin: "Tercih Dönemi Paketi (299 TL, [[/paket](/paket)](/paket)) ile açılır; [[a](/x)](/y) kalır.",
|
||||
bekle: "Tercih Dönemi Paketi (299 TL, [/paket](/paket)) ile açılır; [[a](/x)](/y) kalır.",
|
||||
duzeltme: 0,
|
||||
},
|
||||
{
|
||||
ad: "ic-ice-rehber-baglantisi-sonra-eslesir",
|
||||
metin: "Oku: [[Yatay geçiş](/rehber/yatay-gecis-rehberi)](/rehber/yatay-gecis-rehberi) ve [[[OBP](/rehber/obp-nedir)](/rehber/obp-nedir)](/rehber/obp-nedir).",
|
||||
bekle: `Oku: [${yatay.baslik}](/rehber/${yatay.slug}) ve [${obp.baslik}](/rehber/${obp.slug}).`,
|
||||
duzeltme: 2,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
*
|
||||
* `/universite/…` ve `/bolum/…` bu turda doğrulanmaz (backlog).
|
||||
*
|
||||
* İç içe bağlantı (8 Eki 2026, QA): model bazen `[[/paket](/paket)](/paket)`
|
||||
* gibi aynı adresi iki kez saran bozuk markdown yazıyor; ekranda köşeli
|
||||
* parantezler ve "Paket (Paket)" kartı görünüyordu. Adres ne olursa olsun
|
||||
* (`/rehber/` ile sınırlı değil) `[[x](y)](y)` → `[x](y)` düzleştirilir; bu
|
||||
* düzeltme rehber eşlemesinden ÖNCE yapılır ki içteki rehber adresi de
|
||||
* doğrulansın. Tampon bunun için bir bağlantı adayının `)`'sinden sonra
|
||||
* `](` gelip gelmediğine bakar (bir karakterlik bekleme).
|
||||
*
|
||||
* Saf modül: DB/ağ/Next yok, `server-only` import etmez; akış modülü gibi
|
||||
* betiklerden de koşar.
|
||||
*/
|
||||
@@ -59,6 +67,25 @@ const BAGLANTI = new RegExp(
|
||||
"g",
|
||||
);
|
||||
|
||||
/**
|
||||
* Aynı adresi iki kez saran iç içe markdown bağlantısı: `[[x](y)](y)`.
|
||||
* Etiket köşeli parantez, adres yuvarlak parantez içermez; adres iki
|
||||
* katmanda aynıysa eşleşir (farklı adresler düzleştirilmez — hangisi
|
||||
* doğru bilinemez).
|
||||
*/
|
||||
const IC_ICE_BAGLANTI = /\[\[([^[\]]*)\]\(([^()]*)\)\]\(\2\)/g;
|
||||
|
||||
/** `[[x](y)](y)` → `[x](y)`; daha derin sarmalar için kalmayana dek yinelenir. */
|
||||
export function icIceBaglantiyiDuzlestir(metin: string): string {
|
||||
let onceki: string;
|
||||
let simdiki = metin;
|
||||
do {
|
||||
onceki = simdiki;
|
||||
simdiki = onceki.replace(IC_ICE_BAGLANTI, "[$1]($2)");
|
||||
} while (simdiki !== onceki);
|
||||
return simdiki;
|
||||
}
|
||||
|
||||
function parcalar(slug: string): Set<string> {
|
||||
return new Set(slug.split("-").filter(Boolean));
|
||||
}
|
||||
@@ -95,8 +122,10 @@ export function enYakinRehber(slug: string, rehberler: readonly RehberKaydi[]):
|
||||
}
|
||||
|
||||
/**
|
||||
* Metindeki `/rehber/<slug>` adreslerini (markdown bağlantısı, ters tırnak ve
|
||||
* çıplak adres) gerçek kayıtlarla eşler. Kayıtlı slug dokunulmaz; yakın
|
||||
* Önce iç içe bağlantılar düzleştirilir (`[[x](y)](y)` → `[x](y)`, her adres;
|
||||
* `duzeltmeler`e yazılmaz — rehber kaydı değil biçim hatası), sonra
|
||||
* metindeki `/rehber/<slug>` adresleri (markdown bağlantısı, ters tırnak ve
|
||||
* çıplak adres) gerçek kayıtlarla eşlenir. Kayıtlı slug dokunulmaz; yakın
|
||||
* kayıt varsa `[başlık](/rehber/yeni-slug)` olur (başlık kayıttan); yoksa
|
||||
* markdown bağlantısı etiketine iner, ters tırnak içi ve çıplak adres
|
||||
* silinir ("bkz." gibi öncesindeki bağlaç kalır).
|
||||
@@ -107,7 +136,7 @@ export function rehberBaglantilariniDuzelt(
|
||||
): { metin: string; duzeltmeler: BaglantiDuzeltme[] } {
|
||||
const duzeltmeler: BaglantiDuzeltme[] = [];
|
||||
const kayitli = new Set(rehberler.map((r) => r.slug));
|
||||
const yeni = metin.replace(BAGLANTI, (eslesme, etiket: string | undefined, mdSlug?: string, tirnakSlug?: string, ciplakSlug?: string) => {
|
||||
const yeni = icIceBaglantiyiDuzlestir(metin).replace(BAGLANTI, (eslesme, etiket: string | undefined, mdSlug?: string, tirnakSlug?: string, ciplakSlug?: string) => {
|
||||
const slug = mdSlug ?? tirnakSlug ?? ciplakSlug ?? "";
|
||||
if (kayitli.has(slug)) return eslesme;
|
||||
const yakin = enYakinRehber(slug, rehberler);
|
||||
@@ -203,9 +232,20 @@ export class BaglantiTamponu {
|
||||
if (kapanis === -1 || kapanis > ETIKET_UST + 1) return m.length > ETIKET_UST + 1 ? "degil" : "bekle";
|
||||
if (kapanis + 1 >= m.length) return "bekle";
|
||||
if (m[kapanis + 1] !== "(") return "degil";
|
||||
const parantez = m.indexOf(")", kapanis + 2);
|
||||
let parantez = m.indexOf(")", kapanis + 2);
|
||||
if (parantez === -1) return m.length - (kapanis + 2) > ADRES_UST ? "degil" : "bekle";
|
||||
return parantez + 1;
|
||||
// Kapanan bağlantıyı bir dış bağlantı sarıyor olabilir (`[[x](y)](y)`,
|
||||
// icIceBaglantiyiDuzlestir): `)`'den sonra `](` geliyorsa dış adresin
|
||||
// kapanışına dek tutulur; sonraki karakter henüz gelmediyse beklenir.
|
||||
for (;;) {
|
||||
if (parantez + 1 >= m.length) return "bekle";
|
||||
if (m[parantez + 1] !== "]") return parantez + 1;
|
||||
if (parantez + 2 >= m.length) return "bekle";
|
||||
if (m[parantez + 2] !== "(") return parantez + 1;
|
||||
const dis = m.indexOf(")", parantez + 3);
|
||||
if (dis === -1) return m.length - (parantez + 3) > ADRES_UST ? parantez + 1 : "bekle";
|
||||
parantez = dis;
|
||||
}
|
||||
}
|
||||
if (m[0] === "`") {
|
||||
const kapanis = m.indexOf("`", 1);
|
||||
|
||||
Reference in New Issue
Block a user