Feat : Prio order for auto stream record
All checks were successful
release / build (push) Successful in 27s
release / verify-windows (push) Successful in 1m20s

This commit is contained in:
jeanotx32
2026-08-12 16:01:18 +02:00
parent 96b364bd66
commit eae694d49a
12 changed files with 442 additions and 72 deletions

View File

@@ -1,8 +1,9 @@
import { useMemo, useState, type FormEvent } from 'react';
import type { AgentView, WatchTarget } from '@stream-control/shared';
import type { AgentView, PreemptionVerdict, WatchTarget } from '@stream-control/shared';
import { DEFAULT_PRIORITY, priorityLabel } from '@stream-control/shared';
import { api } from '../api';
import { formatDateTime, formatDuration, formatRelative } from '../format';
import { recordingContext, stateLabel, type RecordingContext } from '../streamers';
import { preemption, recordingContext, stateLabel, type RecordingContext } from '../streamers';
import { StreamerDetail } from './StreamerDetail';
interface Props {
@@ -49,9 +50,9 @@ export function StreamersPage({
}
/**
* En direct d'abord, puis show privé, puis le reste ; à statut égal, les
* favoris devant. L'ordre du statut prime : un direct en cours appelle une
* décision, un favori hors-ligne non.
* En direct d'abord, puis show privé, puis le reste ; à statut égal les
* favoris devant, puis la priorité. L'ordre du statut prime : un direct en
* cours appelle une décision, un favori hors-ligne non.
*/
const ordered = useMemo(
() =>
@@ -61,6 +62,7 @@ export function StreamersPage({
return (
rank(a) - rank(b) ||
Number(b.favorite) - Number(a.favorite) ||
b.priority - a.priority ||
a.username.localeCompare(b.username)
);
}),
@@ -174,6 +176,7 @@ function StreamerCard({
const state = stateLabel(target);
const agent = agents.find((candidate) => candidate.id === target.agentId) ?? null;
const context = recordingContext(target, agent, targets);
const verdict = preemption(target, context);
const isLive = target.state === 'public';
const liveFor = isLive && target.statusChangedAt ? Date.now() - target.statusChangedAt : null;
@@ -293,17 +296,23 @@ function StreamerCard({
) : (
<button
className="primary"
disabled={busy || !agent?.online || !isLive || Boolean(context.busyWith)}
title={recordTitle(target, agent, context)}
disabled={busy || !agent?.online || !isLive || verdict?.allowed === false}
title={recordTitle(target, agent, context, verdict)}
onClick={() => void run(() => api.recordTarget(target.id), 'Enregistrement lancé')}
>
Enregistrer
{verdict?.allowed ? '● Enregistrer ⚡' : '● Enregistrer'}
</button>
)}
<div className="spacer" />
<span className="muted small">{agent ? agent.name : 'aucun agent'}</span>
<span className="muted small">
{agent ? agent.name : 'aucun agent'}
{/* Le rang par défaut resterait du bruit : il ne dit rien de plus que
l'absence de réglage. */}
{target.priority !== DEFAULT_PRIORITY && ` · ${priorityLabel(target.priority)}`}
{target.preempt && <span title="Peut interrompre une capture de rang inférieur"> </span>}
</span>
</div>
<footer className="card-foot muted small">
@@ -326,12 +335,14 @@ function recordTitle(
target: WatchTarget,
agent: AgentView | null,
context: RecordingContext,
verdict: PreemptionVerdict | null,
): string {
if (!target.agentId) return 'Assigne un agent à ce streamer (clique la fiche)';
if (!agent?.online) return 'Agent hors-ligne';
if (context.busyWith) {
return `${agent.name} enregistre déjà ${context.busyWith} — une VM ne traite qu'un flux à la fois`;
if (verdict?.allowed) {
return `Interrompt la capture de ${context.busyWith} sur ${agent.name} — priorité supérieure`;
}
if (verdict) return `${agent.name} déjà pris : ${verdict.reason}`;
if (target.state !== 'public') return "Le streamer n'est pas en direct";
return agent.browser.enabled
? "Ouvrir la page, passer en plein écran et lancer l'enregistrement"