AI katmanını OpenRouter tek kapısına geçir ve rapor üretimini hızlandır
All checks were successful
Deploy / deploy (push) Successful in 9m30s
All checks were successful
Deploy / deploy (push) Successful in 9m30s
- Tüm AI akışı OpenRouter üzerinden (openai SDK, varsayılan model deepseek/deepseek-v4-flash, OPENROUTER_MODEL ile değiştirilebilir); Anthropic/Gemini/lokal sağlayıcı seçimi ve SDK'ları kaldırıldı. - Rapor: aday havuzu dilim başına 20 (60 program, ~8k token prompt); AI şeması sabit uzunluklu üç liste (hayal 5 / dengeli 13 / garanti 6 — dağılım şema düzeyinde zorlanır), sıra + dilim + riskNotu + trendOzeti gerçek sıra geçmişinden deterministik üretilir; 184 sn → ~12 sn. - Sohbet: akış ortası zaman aşımı artık sessizce yutulmaz (kesik cevap tam cevap gibi kaydedilmiyordu, kredi iadesi çalışmıyordu); istemci hata anında durumu sunucudan senkronlar, boş balon bırakmaz. - aiHataMesaji, openai SDK'nın .name'i "Error" kalan abort/timeout hatalarını da yakalar; meraklisina şeması yeni akışla senkronlandı. - CI: OPENROUTER_API_KEY / OPENROUTER_MODEL secret'ları; eski AI secret'ları kaldırıldı. next.config serverExternalPackages sadeleşti. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,10 +15,12 @@ import { ListemGovde } from "./listem-govde";
|
||||
import { ListemBosCta } from "./listem-bos-cta";
|
||||
import { ListeUretici } from "./liste-uretici";
|
||||
|
||||
// listeRevize aksiyonu 2 AI denemesi yapabiliyor (deneme başına 50 sn abort
|
||||
// sınırı, bkz. lib/ai/cagri.ts); platform limiti AI zaman aşımından SONRA
|
||||
// dolmalı ki kredi iadesi her zaman çalışabilsin.
|
||||
export const maxDuration = 120;
|
||||
// listeRevize aksiyonu 2 AI denemesi yapabiliyor (deneme başına 60 sn abort
|
||||
// sınırı, bkz. lib/ai/cagri.ts) → en kötü ~120 sn. Platform limiti bundan
|
||||
// SONRA dolmalı ki kredi iadesi her zaman çalışabilsin. Self-hosted docker'da
|
||||
// bu değer etkisiz ama invaryant belgeli kalsın; öndeki reverse proxy'nin
|
||||
// istek süresi limiti de >= 200 sn olmalı.
|
||||
export const maxDuration = 300;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Listem",
|
||||
|
||||
@@ -121,6 +121,7 @@ export function SohbetClient({
|
||||
{ id: asistanId, role: "assistant", content: "" },
|
||||
]);
|
||||
|
||||
let birikmis = "";
|
||||
try {
|
||||
const res = await fetch("/api/soru", {
|
||||
method: "POST",
|
||||
@@ -151,7 +152,6 @@ export function SohbetClient({
|
||||
});
|
||||
const reader = res.body!.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let birikmis = "";
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
@@ -165,7 +165,26 @@ export function SohbetClient({
|
||||
// harcamasını görmez; bakiye orada da güncellensin.
|
||||
router.refresh();
|
||||
} catch {
|
||||
toast.error("Bağlantı koptu. Cevabı sayfayı yenileyerek görebilirsin.");
|
||||
// Sunucu hata anında krediyi iade edip yarım cevabı kaydetmiş olabilir;
|
||||
// tahmin etme: kayıtlı geçmişi ve bakiyeyi sunucudan çek (boş balon da
|
||||
// bu senkronla temizlenir). Senkron da düşerse boş balonu elle kaldır.
|
||||
try {
|
||||
const res = await fetch("/api/soru");
|
||||
const data = res.ok ? await res.json() : null;
|
||||
if (data?.mesajlar) setMesajlar(data.mesajlar);
|
||||
if (typeof data?.kredi === "number") setKredi(data.kredi);
|
||||
} catch {}
|
||||
if (!birikmis) {
|
||||
setMesajlar((m) => m.filter((x) => x.id !== asistanId));
|
||||
// Cevap hiç gelmedi; kullanıcının yazdığı soru kaybolmasın
|
||||
setGirdi(mesaj);
|
||||
}
|
||||
router.refresh();
|
||||
toast.error(
|
||||
birikmis
|
||||
? "Bağlantı koptu — cevabın buraya kadar üretilen kısmı kaydedildi."
|
||||
: "Bağlantı koptu, cevap alınamadı. Kredin harcandıysa otomatik iade edildi.",
|
||||
);
|
||||
} finally {
|
||||
setBekliyor(false);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ flowchart TD
|
||||
H --> S{"Havuzda 24'ten az program mı kaldı?"}
|
||||
S -- "evet" --> G("Filtreler adım adım gevşetilir<br/>önce il, sonra alan — listede açıkça belirtilir")
|
||||
G --> H
|
||||
S -- "hayır" --> Y("Yapay zekâ havuzdan tam 24 tercih seçer<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe + risk notu ekler")
|
||||
Y --> L("24 tercihlik liste")
|
||||
S -- "hayır" --> Y("Yapay zekâ havuzdan tam 24 tercih seçer<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar")
|
||||
Y --> R("Risk notu ve trend özeti<br/>gerçek sıralama geçmişinden hesaplanır, yapay zekâ yorumu değildir")
|
||||
R --> L("24 tercihlik liste")
|
||||
L --> L1("üstte 4–6 hayal")
|
||||
L --> L2("ortada 12–14 dengeli")
|
||||
L --> L3("sonda 5–7 garanti")
|
||||
@@ -39,7 +40,7 @@ export function HuniSemasi() {
|
||||
return (
|
||||
<ElCizimiSema
|
||||
tanim={SEMA}
|
||||
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla aday havuzuna daraltılır; havuz 24'ün altına düşerse filtreler gevşetilir, sonra yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda garanti olacak şekilde 24 tercih seçer."
|
||||
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla aday havuzuna daraltılır; havuz 24'ün altına düşerse filtreler gevşetilir, sonra yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda garanti olacak şekilde 24 tercih seçip her satıra gerekçe yazar; risk notu ve trend özeti gerçek sıralama geçmişinden hesaplanır."
|
||||
figcaption={
|
||||
<>
|
||||
Her adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "}
|
||||
|
||||
@@ -43,8 +43,15 @@ function siraTurGecerli(sira: number, tur: string): tur is PuanTuruKey {
|
||||
function aiHataMesaji(err: unknown): string {
|
||||
if (err instanceof RaporUretimHatasi) return err.message;
|
||||
// AbortSignal.timeout: DOMException "TimeoutError"; SDK'lar abort'u kendi
|
||||
// hata sınıflarıyla sarabiliyor (ör. APIUserAbortError) — ada göre yakala.
|
||||
if (err instanceof Error && /abort|timeout/i.test(err.name)) {
|
||||
// hata sınıflarıyla sarabiliyor (ör. APIUserAbortError) ve openai/anthropic
|
||||
// bu sınıflarda .name'i "Error" bırakıyor — ada ek olarak sınıf adına ve
|
||||
// mesaja da bak ("Request was aborted." / "Request timed out.").
|
||||
if (
|
||||
err instanceof Error &&
|
||||
/abort|timeout|timed out/i.test(
|
||||
`${err.name} ${err.constructor.name} ${err.message}`,
|
||||
)
|
||||
) {
|
||||
return "Liste üretimi bu sefer çok uzun sürdü ve durduruldu; harcanan kredin otomatik iade edildi. Tekrar dener misin?";
|
||||
}
|
||||
if (err instanceof Error && err.message === "AI_KEY_MISSING") {
|
||||
|
||||
Reference in New Issue
Block a user