mission canvas: add Refine button + markdown-rendered description
Adds a Refine button to the left of Refresh + Launch on the mission
detail toolbar (draft-only). Clicking it POSTs to a new endpoint that
calls Gemini 2.5 Flash to rewrite the user's freeform description into
a coherent, sectioned Markdown brief (Objective / Context / Scope /
Constraints / Acceptance Criteria / Open Questions) ready for the
research + coding agents to ingest cleanly.
Backend:
- crates/cm-api/src/mission_refiner.rs — Gemini call with a
system prompt that preserves user-provided facts, avoids
invention, and emits raw markdown (not JSON).
- POST /api/missions/{id}/refine — draft-only, 400 on empty
description or non-draft state.
- cm-db::repo::missions::set_description helper.
Frontend:
- MarkdownBlock — tiny zero-dep renderer for h1/h2/h3, bullet +
numbered lists, **bold**, `code`, paragraphs. Deliberately
small; the refiner emits a bounded subset.
- MissionCanvas — Refine button (Sparkles icon, secondary style)
to the left of Refresh; description now renders through
MarkdownBlock instead of a single <p>. Disabled while
description is empty or a refine is in flight.
- lib/api/missions — refineMission client.
This commit is contained in:
@@ -221,6 +221,32 @@ pub async fn trigger_security_scan(
|
||||
Ok(Json(SecurityScanResponse { findings, tasks }))
|
||||
}
|
||||
|
||||
/// POST /api/missions/{id}/refine — rewrite the description into a
|
||||
/// coherent, sectioned Markdown brief ready for downstream agent
|
||||
/// ingestion. Draft-only.
|
||||
pub async fn refine(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<Mission>, ApiError> {
|
||||
crate::mission_refiner::refine(&state.pool, user.workspace_id, id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("mission {id}: refine failed: {e}");
|
||||
if e.contains("not found") {
|
||||
ApiError::NotFound
|
||||
} else if e.contains("empty") || e.contains("only allowed on draft") {
|
||||
ApiError::BadRequest
|
||||
} else {
|
||||
ApiError::Internal
|
||||
}
|
||||
})?;
|
||||
let mission = cm_db::repo::missions::get(&state.pool, id, user.workspace_id.as_uuid())
|
||||
.await?
|
||||
.ok_or(ApiError::NotFound)?;
|
||||
Ok(Json(mission))
|
||||
}
|
||||
|
||||
pub async fn set_status(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
|
||||
Reference in New Issue
Block a user