feat(model-server): progression fine du téléchargement du modèle llamacpp (#54)
Stretch B2/F2 de #54, par-dessus le MVP déjà mergé (B1/F1). Backend : le port de téléchargement HF publie une progression débouncée (bytes reçus / total, pourcentage) via le stream de statut du serveur modèle, avec gestion du total inconnu (pas de faux %), du cache hit, de l'annulation et du timeout. Frontend : l'overlay plein-cellule de préparation du serveur affiche la progression réelle (barre, %, octets, source) en mappant le fil de statut, avec la règle « pas de faux % » quand le total est inconnu. Tests : application + infrastructure (téléchargement débouncé, cancel, timeout, cache hit, total inconnu) et vitest (overlay + formatage pur). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -40,6 +40,7 @@ import {
|
||||
} from "@/features/announcements";
|
||||
import {
|
||||
modelServerOverlayText,
|
||||
describeModelServerDownload,
|
||||
useModelServerLaunchState,
|
||||
} from "@/features/agents";
|
||||
import { leaves, normalizeWeights, resizeAdjacent } from "./layout";
|
||||
@ -382,7 +383,12 @@ function LeafView({
|
||||
const pinnedAgent = agentId
|
||||
? agents.find((a) => a.id === agentId)
|
||||
: undefined;
|
||||
const modelServerOverlay = modelServerOverlayText(statusForAgent(pinnedAgent));
|
||||
const modelServerStatus = statusForAgent(pinnedAgent);
|
||||
const modelServerOverlay = modelServerOverlayText(modelServerStatus);
|
||||
// F2 — download progress (bar/%/bytes/source) when the status carries it; null
|
||||
// for every non-`downloading` state, so `starting`/`probing` show just the
|
||||
// title. An indeterminate download (unknown total) yields `percent: null`.
|
||||
const modelServerDownload = describeModelServerDownload(modelServerStatus);
|
||||
const agentWork = agentId
|
||||
? workState?.agents.find((row) => row.agentId === agentId)
|
||||
: undefined;
|
||||
@ -976,16 +982,101 @@ function LeafView({
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 6,
|
||||
minWidth: 0,
|
||||
maxWidth: "80%",
|
||||
background: "var(--color-surface, #1e1e1e)",
|
||||
border: "1px solid var(--color-border, #3a3a3a)",
|
||||
borderRadius: 4,
|
||||
padding: "6px 12px",
|
||||
padding: "8px 14px",
|
||||
}}
|
||||
>
|
||||
{modelServerOverlay}
|
||||
</span>
|
||||
<span>{modelServerOverlay}</span>
|
||||
{modelServerDownload && (
|
||||
<>
|
||||
{/* Determinate bar when the total is known; an indeterminate
|
||||
track (no aria-valuenow → ARIA indeterminate) otherwise. */}
|
||||
<div
|
||||
data-testid="model-server-progress"
|
||||
role="progressbar"
|
||||
aria-label="progression du téléchargement"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={
|
||||
modelServerDownload.percent != null
|
||||
? Math.round(modelServerDownload.percent)
|
||||
: undefined
|
||||
}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: 220,
|
||||
maxWidth: "100%",
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
overflow: "hidden",
|
||||
background: "var(--color-raised, #2a2a2a)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
modelServerDownload.percent != null
|
||||
? {
|
||||
height: "100%",
|
||||
width: `${modelServerDownload.percent}%`,
|
||||
background: "var(--color-primary, #5b9bd5)",
|
||||
transition: "width 120ms linear",
|
||||
}
|
||||
: {
|
||||
// Indeterminate: a sliding sliver.
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "40%",
|
||||
background: "var(--color-primary, #5b9bd5)",
|
||||
animation:
|
||||
"model-server-indeterminate 1.1s ease-in-out infinite",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{(modelServerDownload.percent != null ||
|
||||
modelServerDownload.bytesLabel) && (
|
||||
<span
|
||||
data-testid="model-server-progress-label"
|
||||
style={{ fontSize: 12, color: "var(--color-content-muted, #9a9a9a)" }}
|
||||
>
|
||||
{modelServerDownload.percent != null
|
||||
? `${Math.round(modelServerDownload.percent)} %`
|
||||
: ""}
|
||||
{modelServerDownload.percent != null &&
|
||||
modelServerDownload.bytesLabel
|
||||
? " · "
|
||||
: ""}
|
||||
{modelServerDownload.bytesLabel ?? ""}
|
||||
</span>
|
||||
)}
|
||||
{modelServerDownload.source && (
|
||||
<span
|
||||
data-testid="model-server-source"
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: "var(--color-content-muted, #9a9a9a)",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
title={modelServerDownload.source}
|
||||
>
|
||||
{modelServerDownload.source}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user