Implement live analysis achievements and admin console

这个提交包含在:
cryptocommuniums-afk
2026-03-15 01:39:34 +08:00
父节点 d1b6603061
当前提交 edc66ea5bc
修改 23 个文件,包含 4033 行新增1022 行删除

查看文件

@@ -17,6 +17,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Progress } from "@/components/ui/progress";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { useBackgroundTask } from "@/hooks/useBackgroundTask";
@@ -169,6 +170,7 @@ export default function Recorder() {
const [cameraActive, setCameraActive] = useState(false);
const [hasMultipleCameras, setHasMultipleCameras] = useState(false);
const [durationMs, setDurationMs] = useState(0);
const [sessionMode, setSessionMode] = useState<"practice" | "pk">("practice");
const [isOnline, setIsOnline] = useState(() => navigator.onLine);
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const [queuedSegments, setQueuedSegments] = useState(0);
@@ -637,13 +639,15 @@ export default function Recorder() {
sessionId: session.id,
title: title.trim() || session.title,
exerciseType: "recording",
sessionMode,
durationMinutes: Math.max(1, Math.round((Date.now() - recordingStartedAtRef.current) / 60000)),
});
toast.success("录制已提交,后台正在整理回放文件");
} catch (error: any) {
toast.error(`结束录制失败: ${error?.message || "未知错误"}`);
setMode("recording");
}
}, [closePeer, finalizeTaskMutation, flushPendingSegments, stopCamera, stopRecorder, syncSessionState, title]);
}, [closePeer, finalizeTaskMutation, flushPendingSegments, sessionMode, stopCamera, stopRecorder, syncSessionState, title]);
const resetRecorder = useCallback(async () => {
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
@@ -948,6 +952,10 @@ export default function Recorder() {
<MonitorUp className="h-3.5 w-3.5" />
WebRTC
</Badge>
<Badge variant="outline" className="gap-1.5 border-white/15 bg-white/5 text-white/80">
<Sparkles className="h-3.5 w-3.5" />
{sessionMode === "practice" ? "练习会话" : "训练 PK"}
</Badge>
</div>
<div>
@@ -1045,13 +1053,22 @@ export default function Recorder() {
</div>
<div className="border-t border-border/60 bg-card/80 p-4">
<div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto]">
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_180px_auto]">
<Input
value={title}
onChange={(event) => setTitle(event.target.value)}
placeholder="本次训练录制标题"
className="h-12 rounded-2xl border-border/60"
/>
<Select value={sessionMode} onValueChange={(value) => setSessionMode(value as "practice" | "pk")} disabled={mode !== "idle" && mode !== "archived"}>
<SelectTrigger className="h-12 rounded-2xl border-border/60">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="practice"></SelectItem>
<SelectItem value="pk"> PK</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-wrap gap-2">
{renderPrimaryActions()}
</div>