Radix Switch'in kendi gizli-input form entegrasyonu bu ortamda Server Action'ın FormData'sına güvenilir şekilde ulaşmıyordu — switch'i "off" yapıp Kaydet'e basınca eski değer kalıcı oluyordu (STT hep otomatik çalışmaya devam ediyordu). Artık her switch kendi değerini doğrudan kontrol eden bir hidden input'la (SettingSwitch) submit ediliyor, Radix'in iç mekanizmasına bağımlı değil. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
145 lines
6.9 KiB
TypeScript
145 lines
6.9 KiB
TypeScript
import { prisma } from "@streamclipper/db";
|
||
import { updateYtdlpCookies, updateSystemSettings, updateNotificationPrefs, updateSttEnabled } from "../actions";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Label } from "@/components/ui/label";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
import { SettingSwitch } from "../components/SettingSwitch";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
const STALE_COOKIE_HOURS = 4;
|
||
|
||
export default async function SettingsPage() {
|
||
const [setting, pollSetting, ttlSetting, notifyStart, notifyRender, sttSetting] = await Promise.all([
|
||
prisma.appSetting.findUnique({ where: { key: "ytdlp_cookies" } }),
|
||
prisma.appSetting.findUnique({ where: { key: "poll_interval_ms" } }),
|
||
prisma.appSetting.findUnique({ where: { key: "raw_segment_ttl_hours" } }),
|
||
prisma.appSetting.findUnique({ where: { key: "notify_stream_start" } }),
|
||
prisma.appSetting.findUnique({ where: { key: "notify_render_done" } }),
|
||
prisma.appSetting.findUnique({ where: { key: "stt_enabled" } }),
|
||
]);
|
||
|
||
const cookieAgeHours = setting ? (Date.now() - setting.updatedAt.getTime()) / 3_600_000 : null;
|
||
const cookieStale = cookieAgeHours !== null && cookieAgeHours > STALE_COOKIE_HOURS;
|
||
const pollIntervalSec = pollSetting ? Math.round(Number(pollSetting.value) / 1000) : 60;
|
||
const ttlHours = ttlSetting ? Number(ttlSetting.value) : 24;
|
||
const notifyStreamStart = notifyStart?.value !== "false";
|
||
const notifyRenderDone = notifyRender?.value !== "false";
|
||
const sttEnabled = sttSetting?.value !== "false";
|
||
|
||
return (
|
||
<div className="flex flex-col gap-6">
|
||
<h1 className="text-2xl font-semibold tracking-tight">Ayarlar</h1>
|
||
|
||
<Card>
|
||
<CardHeader className="flex-row items-center justify-between">
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">YouTube Cookie'leri</CardTitle>
|
||
<span className="font-mono-num text-xs text-muted-foreground">
|
||
{setting ? `son güncelleme: ${setting.updatedAt.toLocaleString("tr-TR")}` : "hiç ayarlanmadı"}
|
||
</span>
|
||
</CardHeader>
|
||
<CardContent className="flex flex-col gap-3">
|
||
{cookieStale && (
|
||
<Badge variant="warn" className="w-fit">
|
||
⚠️ {Math.round(cookieAgeHours!)} saattir güncellenmedi — büyük/popüler kanallarda bot-check hatası
|
||
görülebilir
|
||
</Badge>
|
||
)}
|
||
<CardDescription>
|
||
YouTube canlı yayın kontrolü ve kayıt için kullanılıyor. Google, oturum çerezlerinin bir kısmını birkaç
|
||
saatte bir yeniliyor — burada eskidiğini fark edersen (kanal detay sayfasında "son poll hatası"
|
||
olarak görürsün) taze bir cookies.txt ile güncelle. Ana hesabın yerine ayrı, önemsiz bir Google hesabı
|
||
kullanman önerilir.
|
||
</CardDescription>
|
||
<form action={updateYtdlpCookies} className="flex flex-col gap-3">
|
||
<Textarea
|
||
name="cookies"
|
||
required
|
||
placeholder="Netscape formatlı cookies.txt içeriğini buraya yapıştır"
|
||
rows={10}
|
||
className="font-mono-num text-xs"
|
||
/>
|
||
<Button type="submit" className="self-start">
|
||
Kaydet
|
||
</Button>
|
||
</form>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">Sistem Parametreleri</CardTitle>
|
||
<CardDescription>Bir sonraki döngüden itibaren geçerli olur, redeploy gerekmez.</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<form action={updateSystemSettings} className="flex flex-col gap-4">
|
||
<div className="flex flex-col gap-1.5">
|
||
<Label htmlFor="pollIntervalSec">Poll aralığı (saniye)</Label>
|
||
<Input id="pollIntervalSec" name="pollIntervalSec" type="number" min={5} defaultValue={pollIntervalSec} className="max-w-40" />
|
||
</div>
|
||
<div className="flex flex-col gap-1.5">
|
||
<Label htmlFor="ttlHours">Ham segment TTL (saat)</Label>
|
||
<Input id="ttlHours" name="ttlHours" type="number" min={1} defaultValue={ttlHours} className="max-w-40" />
|
||
</div>
|
||
<p className="max-w-prose text-xs text-muted-foreground">
|
||
Eşzamanlı capture limiti ve render eşzamanlılığı BullMQ worker'ları başlatılırken sabitleniyor —
|
||
bunları değiştirmek için Coolify'deki <code className="font-mono-num">MAX_CONCURRENT_CAPTURES</code>{" "}
|
||
/ <code className="font-mono-num">VIDEO_RENDER_CONCURRENCY</code> env var'larını güncelleyip
|
||
redeploy etmek gerekiyor.
|
||
</p>
|
||
<Button type="submit" className="self-start">
|
||
Kaydet
|
||
</Button>
|
||
</form>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">STT (Transkripsiyon)</CardTitle>
|
||
<CardDescription>
|
||
Kapatırsan yeni aday klipler transkribe edilmez (PENDING_STT'de bekler) — OpenAI Whisper çağrısı
|
||
yapılmaz, render de tetiklenmez. Boş/konuşmasız yayınlarda (ör. sadece ambiyans müzik) gereksiz API
|
||
maliyetini önlemek için kapatabilirsin.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<form action={updateSttEnabled}>
|
||
<CardContent>
|
||
<Label className="flex items-center justify-between gap-4 text-sm font-normal">
|
||
Otomatik transkripsiyon aktif
|
||
<SettingSwitch name="sttEnabled" defaultChecked={sttEnabled} />
|
||
</Label>
|
||
</CardContent>
|
||
<CardFooter>
|
||
<Button type="submit">Kaydet</Button>
|
||
</CardFooter>
|
||
</form>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-sm font-medium text-muted-foreground">Bildirimler</CardTitle>
|
||
</CardHeader>
|
||
<form action={updateNotificationPrefs}>
|
||
<CardContent className="flex flex-col gap-4">
|
||
<Label className="flex items-center justify-between gap-4 text-sm font-normal">
|
||
Yayın başladığında Telegram bildirimi
|
||
<SettingSwitch name="notifyStreamStart" defaultChecked={notifyStreamStart} />
|
||
</Label>
|
||
<Label className="flex items-center justify-between gap-4 text-sm font-normal">
|
||
9:16 render tamamlandığında Telegram bildirimi
|
||
<SettingSwitch name="notifyRenderDone" defaultChecked={notifyRenderDone} />
|
||
</Label>
|
||
</CardContent>
|
||
<CardFooter>
|
||
<Button type="submit">Kaydet</Button>
|
||
</CardFooter>
|
||
</form>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|