greentic-flow-builder 0.1.0

AI-powered Adaptive Card flow builder with visual graph editor and demo runner
Documentation
//! `POST /api/render-flow` — render a list of cards that form a flow.
//!
//! Each card is rendered with its preset + optional per-card theme (falls back
//! to the flow-level theme). Flow-level actions are injected as Action.Submit
//! buttons carrying a `goto` payload, replacing any template-defined actions.

use crate::render;
use crate::ui::state::AppState;
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde::Deserialize;
use serde_json::{Value, json};
use std::sync::Arc;

#[derive(Deserialize)]
pub struct FlowAction {
    pub title: String,
    pub goto: String,
}

#[derive(Deserialize)]
pub struct FlowCard {
    pub id: String,
    #[serde(alias = "preset")]
    pub template: String,
    #[serde(default)]
    pub theme: Option<String>,
    pub data: Value,
    #[serde(default)]
    pub actions: Vec<FlowAction>,
}

#[derive(Deserialize)]
pub struct FlowBody {
    pub cards: Vec<FlowCard>,
    #[serde(default)]
    pub theme: Option<String>,
}

pub async fn post_render_flow(
    State(state): State<Arc<AppState>>,
    Json(body): Json<FlowBody>,
) -> impl IntoResponse {
    let mut rendered_cards = Vec::new();
    for fc in &body.cards {
        let theme = fc.theme.as_deref().or(body.theme.as_deref());
        let response = match render::render_preset(&state.registry, &fc.template, theme, &fc.data) {
            Ok(r) => r,
            Err(e) => {
                return (
                    StatusCode::BAD_REQUEST,
                    Json(json!({
                        "error": format!("card '{}': {}", fc.id, e)
                    })),
                )
                    .into_response();
            }
        };

        let mut card = response.card;
        if !fc.actions.is_empty() {
            let action_values: Vec<Value> = fc
                .actions
                .iter()
                .map(|a| {
                    json!({
                        "type": "Action.Submit",
                        "title": a.title,
                        "style": "positive",
                        "data": { "goto": a.goto }
                    })
                })
                .collect();
            card["actions"] = Value::Array(action_values);
        }

        rendered_cards.push(json!({
            "id": fc.id,
            "rendered_card": card,
        }));
    }
    Json(json!({ "cards": rendered_cards })).into_response()
}