fix: poll hatasına zaman damgası ekle + STT'yi panelden kapatılabilir yap

- lastPollErrors artık {message, at} tutuyor — panelde hangi hatanın ne
  zaman oluştuğu görünmüyordu, eski/güncel ayrımı yapılamıyordu.
- "This live event will begin in N minutes" gibi zararsız mesajlar da
  hata rozetine düşüyordu, "is not currently live" ile aynı gruba alındı.
- Yeni stt_enabled ayarı (Ayarlar sayfası): kapatılınca stt_scoring.py
  OpenAI Whisper çağrısı yapmadan adayı PENDING_STT'de bırakıp çıkıyor —
  konuşmasız/ambiyans yayınlarda (bkz. NASA testi) boşa API maliyeti
  önleniyor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 14:52:15 +03:00
co-authored by Claude Sonnet 5
parent 7d504d1938
commit cbb16697ca
7 changed files with 74 additions and 16 deletions
+10
View File
@@ -136,6 +136,16 @@ export async function updateSystemSettings(formData: FormData) {
revalidatePath("/settings");
}
export async function updateSttEnabled(formData: FormData) {
const enabled = formData.get("sttEnabled") === "on";
await prisma.appSetting.upsert({
where: { key: "stt_enabled" },
create: { key: "stt_enabled", value: String(enabled) },
update: { value: String(enabled) },
});
revalidatePath("/settings");
}
export async function updateNotificationPrefs(formData: FormData) {
const streamStart = formData.get("notifyStreamStart") === "on";
const renderDone = formData.get("notifyRenderDone") === "on";
+9 -4
View File
@@ -122,11 +122,16 @@ export default async function ChannelDetailPage({ params }: { params: Promise<{
{status?.lastPollError && (
<div className="flex flex-col gap-1.5">
<Badge variant="err" className="w-fit">
son poll hatası
</Badge>
<div className="flex items-center gap-2">
<Badge variant="err" className="w-fit">
son poll hatası
</Badge>
<span className="font-mono-num text-xs text-muted-foreground">
{new Date(status.lastPollError.at).toLocaleString("tr-TR")}
</span>
</div>
<pre className="font-mono-num whitespace-pre-wrap rounded-md border border-border bg-background/60 p-3 text-[0.7rem] text-muted-foreground">
{status.lastPollError}
{status.lastPollError.message}
</pre>
</div>
)}
+8 -1
View File
@@ -91,7 +91,14 @@ export default async function DashboardPage() {
<TableCell>
<div className="flex items-center gap-1.5">
<Badge variant={recording ? "live" : "muted"}>{recording ? "🔴 Kayıtta" : "—"}</Badge>
{status?.lastPollError && <Badge variant="err">hata</Badge>}
{status?.lastPollError && (
<Badge
variant="err"
title={`${new Date(status.lastPollError.at).toLocaleString("tr-TR")}${status.lastPollError.message}`}
>
hata
</Badge>
)}
</div>
</TableCell>
<TableCell className="font-mono-num text-xs text-muted-foreground">
+26 -2
View File
@@ -1,5 +1,5 @@
import { prisma } from "@streamclipper/db";
import { updateYtdlpCookies, updateSystemSettings, updateNotificationPrefs } from "../actions";
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";
@@ -13,12 +13,13 @@ export const dynamic = "force-dynamic";
const STALE_COOKIE_HOURS = 4;
export default async function SettingsPage() {
const [setting, pollSetting, ttlSetting, notifyStart, notifyRender] = await Promise.all([
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;
@@ -27,6 +28,7 @@ export default async function SettingsPage() {
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">
@@ -95,6 +97,28 @@ export default async function SettingsPage() {
</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&apos;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
<Switch 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>
+1 -1
View File
@@ -5,7 +5,7 @@ export interface ChannelStatus {
lastCheckedAt: string | null;
recording: boolean;
activeSessionId: string | null;
lastPollError: string | null;
lastPollError: { message: string; at: string } | null;
}
export const API_DAEMON_URL = process.env.API_DAEMON_URL ?? "http://localhost:4001";