Implement live analysis achievements and admin console
这个提交包含在:
@@ -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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户