Files
screenclipper/apps/frontend/app/page.tsx
T
ayrisdevandClaude Sonnet 5 cbb16697ca 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>
2026-09-02 14:52:15 +03:00

132 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { prisma } from "@streamclipper/db";
import { addChannel, toggleChannelActive, toggleAllChannels, checkChannelNow } from "./actions";
import { fetchChannelStatuses } from "../lib/apiDaemon";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const [channels, statuses] = await Promise.all([
prisma.channel.findMany({
orderBy: { name: "asc" },
include: {
sessions: {
where: { endedAt: null },
orderBy: { startedAt: "desc" },
take: 1,
},
},
}),
fetchChannelStatuses(),
]);
return (
<div className="flex flex-col gap-6">
<h1 className="text-2xl font-semibold tracking-tight">Kanal Durumu</h1>
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium text-muted-foreground">Yeni Kanal Ekle</CardTitle>
</CardHeader>
<CardContent>
<form action={addChannel} className="flex flex-wrap gap-2">
<Input name="name" placeholder="Kanal adı" required className="flex-1 min-w-[140px]" />
<Input name="youtubeHandle" placeholder="@handle" required className="flex-1 min-w-[120px]" />
<Input name="channelId" placeholder="channel_id (UC...)" required className="flex-1 min-w-[180px]" />
<Button type="submit">Kanal Ekle</Button>
</form>
</CardContent>
</Card>
{channels.length === 0 ? (
<p className="text-sm text-muted-foreground">Henüz kanal eklenmedi.</p>
) : (
<Card>
<CardHeader className="flex-row items-center justify-between">
<CardTitle className="text-sm font-medium text-muted-foreground">{channels.length} kanal</CardTitle>
<div className="flex gap-2">
<form action={toggleAllChannels.bind(null, true)}>
<Button type="submit" variant="outline" size="sm">
Tümünü Devam Ettir
</Button>
</form>
<form action={toggleAllChannels.bind(null, false)}>
<Button type="submit" variant="outline" size="sm">
Tümünü Duraklat
</Button>
</form>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Kanal</TableHead>
<TableHead>Durum</TableHead>
<TableHead>Kayıt</TableHead>
<TableHead>Son Kontrol</TableHead>
<TableHead className="text-right">Aksiyon</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{channels.map((c) => {
const recording = c.sessions.length > 0;
const status = statuses.get(c.id);
return (
<TableRow key={c.id}>
<TableCell>
<Link href={`/channels/${c.id}`} className="font-medium hover:text-primary hover:underline">
{c.name}
</Link>{" "}
<span className="font-mono-num text-xs text-muted-foreground">{c.youtubeHandle}</span>
</TableCell>
<TableCell>
<Badge variant={c.isActive ? "ok" : "muted"}>{c.isActive ? "Aktif" : "Pasif"}</Badge>
</TableCell>
<TableCell>
<div className="flex items-center gap-1.5">
<Badge variant={recording ? "live" : "muted"}>{recording ? "🔴 Kayıtta" : "—"}</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">
{c.lastCheckedAt ? new Date(c.lastCheckedAt).toLocaleString("tr-TR") : "—"}
</TableCell>
<TableCell>
<div className="flex justify-end gap-1.5">
<form action={toggleChannelActive.bind(null, c.id, !c.isActive)}>
<Button type="submit" variant="outline" size="sm">
{c.isActive ? "Duraklat" : "Devam Ettir"}
</Button>
</form>
<form action={checkChannelNow.bind(null, c.id)}>
<Button type="submit" variant="ghost" size="sm">
Şimdi Kontrol Et
</Button>
</form>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</CardContent>
</Card>
)}
</div>
);
}