feat(danisman): profil onayı kredi harcamaz; yapay zekâ sağlayıcı adresi .env'den (AI_BASE_URL/AI_API_KEY)
- Bilal kararı (26 Eyl 2026): 'kaydedeyim mi?' sorusuna kısa evet/hayır cevabı bedelsiz. Sunucu son danışman mesajında onay ibaresini arar, mesaj ≤40 karakter ve evet/hayır kalıbıyla başlıyorsa kredi düşmez, ledger'a yazılmaz, iade yolu atlanır; X-Kredi-Dusuldu başlığıyla istemci bakiyeyi buna göre günceller. Gerçek soru içeren 'evet ama …' bedava geçmez. - Bilal kararı: model Türkiye'de barındırılacak. client.ts baseURL ve anahtar AI_BASE_URL/AI_API_KEY'den okunur (OPENROUTER_* geriye uyumlu); sağlayıcı değişince kod değil .env değişir, /gizlilik aynı commit'te. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { getSession } from "@/lib/session";
|
||||
import { appDb, schema } from "@/lib/appdb";
|
||||
import {
|
||||
spendCreditForMessage,
|
||||
bedelsizMesajKaydet,
|
||||
grantCredits,
|
||||
krediBittiIsaretle,
|
||||
} from "@/lib/credits";
|
||||
@@ -28,6 +29,22 @@ const MAX_GECMIS = 12;
|
||||
* C.3/10). Bedeli: "evet" cevabı bir kredi. Kapatılırsa model doğrudan kaydeder.
|
||||
*/
|
||||
const PROFIL_ONAY_SOR = true;
|
||||
|
||||
/** Danışmanın onay sorusunda geçen sabit ibare; istemci de bunu görmez, yalnız sunucu arar. */
|
||||
const ONAY_SORUSU = "profiline kaydedeyim mi";
|
||||
|
||||
/**
|
||||
* Onay mesajı mı: son danışman mesajı "kaydedeyim mi?" diye sormuş VE aday
|
||||
* kısa bir evet/hayır yazmış. Bu mesaj kredi harcamaz (Bilal, 26 Eyl 2026).
|
||||
* Kısa tutulur ki "evet ama İzmir'de hukuk kaç sırayla alıyor" gibi gerçek
|
||||
* sorular bedava geçmesin.
|
||||
*/
|
||||
function onayMesajiMi(mesaj: string, sonAsistan: string | undefined): boolean {
|
||||
if (!sonAsistan || !sonAsistan.toLocaleLowerCase("tr-TR").includes(ONAY_SORUSU)) return false;
|
||||
const m = mesaj.trim().toLocaleLowerCase("tr-TR");
|
||||
if (m.length > 40) return false;
|
||||
return /^(evet|olur|tamam|kaydet|kaydedebilirsin|kaydedelim|hayır|hayir|kaydetme|gerek yok|istemiyorum|şimdilik hayır)\b/.test(m);
|
||||
}
|
||||
const MAX_MESAJ_UZUNLUK = 2000;
|
||||
|
||||
function sistemPromptu(opts: {
|
||||
@@ -71,10 +88,11 @@ KURALLAR:
|
||||
- PROFİL: aday bir tercih söylerse (yaşadığı il, evden en fazla kaç km,
|
||||
vakıfta yıllık ücret sınırı, burs beklentisi, öğretim dili, uzaktan/açık
|
||||
kabul mü, kampüs büyüklüğü) ${PROFIL_ONAY_SOR
|
||||
? `HEMEN KAYDETME: cevabının sonunda tek cümleyle sor: "Bunu profiline
|
||||
kaydedeyim mi? (Listen ve aramalar buna göre süzülür; istediğin zaman
|
||||
silebilirsin.)" Aday evet/kaydet derse o zaman profil_guncelle ile kaydet
|
||||
ve "Kaydettim: …" de. Aday açıkça "kaydet" diye yazdıysa sormadan kaydet.`
|
||||
? `HEMEN KAYDETME: cevabının sonunda tek cümleyle, tam bu ibareyle sor:
|
||||
"Bunu profiline kaydedeyim mi? (Evet demek kredi harcamaz; listen ve
|
||||
aramalar buna göre süzülür, istediğin zaman silebilirsin.)" Aday
|
||||
evet/kaydet derse o zaman profil_guncelle ile kaydet ve "Kaydettim: …" de.
|
||||
Aday açıkça "kaydet" diye yazdıysa sormadan kaydet.`
|
||||
: `profil_guncelle ile kaydet ve cevabının sonunda tek cümleyle söyle:
|
||||
"Şunu kaydettim: …". Emin değilsen önce sor, tahminle kaydetme.`}
|
||||
Bunların DIŞINDA hiçbir şeyi kaydetme ve sorma:
|
||||
@@ -217,12 +235,18 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
const gecmis = gecmisHam.reverse();
|
||||
|
||||
// Profil onayı ("kaydedeyim mi?" → "evet") kredi harcamaz; iade de yok.
|
||||
const sonAsistan = [...gecmis].reverse().find((m) => m.role === "assistant")?.content;
|
||||
const bedelsiz = onayMesajiMi(mesaj, sonAsistan);
|
||||
|
||||
// Kredi düşümü stream'den önce — yetersizse hiç başlama
|
||||
const harcama = await spendCreditForMessage({
|
||||
userId: session.user.id,
|
||||
content: mesaj,
|
||||
clientMessageId,
|
||||
});
|
||||
const harcama = bedelsiz
|
||||
? await bedelsizMesajKaydet({ userId: session.user.id, content: mesaj, clientMessageId })
|
||||
: await spendCreditForMessage({
|
||||
userId: session.user.id,
|
||||
content: mesaj,
|
||||
clientMessageId,
|
||||
});
|
||||
if (!harcama.ok) {
|
||||
if (harcama.error === "INSUFFICIENT") {
|
||||
await krediBittiIsaretle(session.user.id);
|
||||
@@ -302,12 +326,14 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!anahtarVar()) {
|
||||
await grantCredits({
|
||||
userId: session.user.id,
|
||||
delta: 1,
|
||||
reason: "refund",
|
||||
refId: kullaniciMesajId,
|
||||
});
|
||||
if (!bedelsiz) {
|
||||
await grantCredits({
|
||||
userId: session.user.id,
|
||||
delta: 1,
|
||||
reason: "refund",
|
||||
refId: kullaniciMesajId,
|
||||
});
|
||||
}
|
||||
console.error("[soru] Yapay Zeka anahtarı tanımlı değil");
|
||||
return Response.json(
|
||||
{
|
||||
@@ -371,12 +397,14 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
await grantCredits({
|
||||
userId,
|
||||
delta: 1,
|
||||
reason: "refund",
|
||||
refId: kullaniciMesajId,
|
||||
}).catch(() => {});
|
||||
if (!bedelsiz) {
|
||||
await grantCredits({
|
||||
userId,
|
||||
delta: 1,
|
||||
reason: "refund",
|
||||
refId: kullaniciMesajId,
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
console.error("[soru] stream hatası:", err);
|
||||
controller.error(err);
|
||||
@@ -389,6 +417,8 @@ export async function POST(request: NextRequest) {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Cache-Control": "no-store",
|
||||
// İstemci bakiyeyi buna göre düşürür (onay mesajı bedelsiz)
|
||||
"X-Kredi-Dusuldu": bedelsiz ? "0" : "1",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user