feat(danisman): araç kullanımı — program_ara/program_detay/universite_profili sorgu araçları + kapalı listeli profil (profil_guncelle)
- src/lib/ai/araclar.ts: OpenAI uyumlu 4 araç; sorgu araçları yalnız okur (yokatlas.db + program_ozellik + YÖK İstatistik + URAP), sayı uydurma yok. profil_guncelle yalnız 7 tanımlı alanı yazar (evIl, maxKm, ucretUst, burs, dil, ogretimTuru, kampus); sağlık/din/gelir/cinsiyet şemada yok (KVKK §8). Profil süzgeci sonucu sıfırlarsa profilsiz tekrar denenip modele söylenir. - src/lib/ai/cagri.ts: sohbetAkisiAraclarla — stream'de tool_calls biriktirme, en fazla 4 tur, son tur araç kapalı; sağlayıcı reddederse düz sohbete düşer. - src/lib/danisman-profil.ts (saf şema + metinler), danisman-profil-db.ts, drizzle 0005 danisman_profil tablosu, /api/soru/profil GET+DELETE. - /api/soru: profil prompt'a girer, araç kuralları eklendi. - Sohbet başlığı altında 'Kaydettiklerim' çipleri (tek tıkla sil); listem sayfası profili sunucudan indirir. Analitiğe yalnız alan adı gider. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
56
src/lib/danisman-profil-db.ts
Normal file
56
src/lib/danisman-profil-db.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import "server-only";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { appDb, schema } from "@/lib/appdb";
|
||||
import {
|
||||
PROFIL_SEMASI,
|
||||
profilTemizle,
|
||||
type DanismanProfili,
|
||||
type ProfilAlani,
|
||||
} from "@/lib/danisman-profil";
|
||||
|
||||
/** Kullanıcının danışman profili; satır yoksa boş nesne. Bozuk alanlar düşer. */
|
||||
export async function profilGetir(userId: string): Promise<DanismanProfili> {
|
||||
const satir = await appDb.query.danismanProfil.findFirst({
|
||||
where: eq(schema.danismanProfil.userId, userId),
|
||||
columns: { alanlar: true },
|
||||
});
|
||||
return profilTemizle(satir?.alanlar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tek alanı yazar (deger null → alan silinir). Değer şemadan geçmezse hata
|
||||
* fırlatır; çağıran taraf (araç) bunu modele "kaydedilmedi" diye döner.
|
||||
*/
|
||||
export async function profilAlanYaz(
|
||||
userId: string,
|
||||
alan: ProfilAlani,
|
||||
deger: unknown,
|
||||
): Promise<DanismanProfili> {
|
||||
const mevcut = await profilGetir(userId);
|
||||
const yeni: Record<string, unknown> = { ...mevcut };
|
||||
if (deger === null || deger === undefined) {
|
||||
delete yeni[alan];
|
||||
} else {
|
||||
const dogrulama = PROFIL_SEMASI.shape[alan].safeParse(deger);
|
||||
if (!dogrulama.success) {
|
||||
throw new Error(`Geçersiz değer: ${alan}`);
|
||||
}
|
||||
yeni[alan] = dogrulama.data;
|
||||
}
|
||||
const alanlar = PROFIL_SEMASI.parse(yeni);
|
||||
const now = new Date();
|
||||
await appDb
|
||||
.insert(schema.danismanProfil)
|
||||
.values({ userId, alanlar, updatedAt: now })
|
||||
.onConflictDoUpdate({
|
||||
target: schema.danismanProfil.userId,
|
||||
set: { alanlar, updatedAt: now },
|
||||
});
|
||||
return alanlar;
|
||||
}
|
||||
|
||||
export async function profilSil(userId: string): Promise<void> {
|
||||
await appDb
|
||||
.delete(schema.danismanProfil)
|
||||
.where(eq(schema.danismanProfil.userId, userId));
|
||||
}
|
||||
Reference in New Issue
Block a user