feat(model-server): afficher le téléchargement du modèle llamacpp au démarrage (#54)

Ajoute un handle du téléchargement des modèles lors du démarrage de
llamacpp : le domaine et l'application émettent la progression de
téléchargement du modèle, relayée en événement côté app-tauri, et l'UI
l'affiche via un badge de lancement et un overlay de cellule pendant que
le serveur de modèle démarre.

Backend (B1) : progression de téléchargement dans domain/application,
relais d'événement app-tauri, couverture de tests.
Frontend (F1) : modelServerLaunch, badge et overlay LayoutGrid, tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 23:14:05 +02:00
parent c67ec4f7bd
commit fe7ed0aa20
12 changed files with 891 additions and 29 deletions

View File

@ -36,6 +36,18 @@ pub enum ModelServerStatusDto {
Probing,
/// IdeA is starting the process.
Starting,
/// Server is downloading/preparing a remote model.
#[serde(rename_all = "camelCase")]
Downloading {
/// Downloaded bytes when known.
downloaded_bytes: Option<u64>,
/// Total bytes when known.
total_bytes: Option<u64>,
/// Completion percentage when known.
percent: Option<f32>,
/// Remote model source.
source: Option<String>,
},
/// Server is ready.
#[serde(rename_all = "camelCase")]
Ready {
@ -58,6 +70,17 @@ impl From<&ModelServerLifecycleStatus> for ModelServerStatusDto {
ModelServerLifecycleStatus::NotConfigured => Self::NotConfigured,
ModelServerLifecycleStatus::Probing => Self::Probing,
ModelServerLifecycleStatus::Starting => Self::Starting,
ModelServerLifecycleStatus::Downloading {
downloaded_bytes,
total_bytes,
percent,
source,
} => Self::Downloading {
downloaded_bytes: *downloaded_bytes,
total_bytes: *total_bytes,
percent: *percent,
source: source.clone(),
},
ModelServerLifecycleStatus::Ready { reused } => Self::Ready { reused: *reused },
ModelServerLifecycleStatus::Failed { code, message } => Self::Failed {
code: code.clone(),
@ -1168,6 +1191,31 @@ mod tests {
assert_eq!(json["status"]["reused"], true);
}
#[test]
fn model_server_status_changed_relays_downloading_to_dto_and_wire() {
let dto = DomainEventDto::from(&DomainEvent::ModelServerStatusChanged {
server_id: server(37),
status: ModelServerLifecycleStatus::Downloading {
downloaded_bytes: None,
total_bytes: None,
percent: None,
source: Some("Qwen/Qwen3-Coder-30B-A3B-Instruct-GGUF".to_owned()),
},
});
let json = serde_json::to_value(&dto).expect("serialisable");
assert_eq!(json["type"], "modelServerStatusChanged");
assert_eq!(json["serverId"], server(37).to_string());
assert_eq!(json["status"]["state"], "downloading");
assert_eq!(json["status"]["downloadedBytes"], serde_json::Value::Null);
assert_eq!(json["status"]["totalBytes"], serde_json::Value::Null);
assert_eq!(json["status"]["percent"], serde_json::Value::Null);
assert_eq!(
json["status"]["source"],
"Qwen/Qwen3-Coder-30B-A3B-Instruct-GGUF"
);
}
#[test]
fn agent_launch_failed_relays_model_server_cause() {
let dto = DomainEventDto::from(&DomainEvent::AgentLaunchFailed {