greentic-flow-builder 0.1.0

AI-powered Adaptive Card flow builder with visual graph editor and demo runner
Documentation
//! Compose mode: dynamic section rendering via primitive partials.

use crate::template::{ComposeSection, Theme};
use anyhow::{Context, bail};
use handlebars::Handlebars;
use serde_json::{Map, Value};

pub fn render_sections(
    hbs: &Handlebars<'static>,
    sections: &[ComposeSection],
    theme: &Theme,
) -> anyhow::Result<Value> {
    let mut body = Vec::new();
    for (idx, section) in sections.iter().enumerate() {
        if !hbs.has_template(&section.primitive) {
            bail!(
                "primitive '{}' not found in section {}",
                section.primitive,
                idx
            );
        }
        let context = build_section_context(&section.data, theme);
        let rendered = hbs
            .render(&section.primitive, &context)
            .with_context(|| format!("rendering section {} ({})", idx, section.primitive))?;
        let fixed = super::post_process::fix_trailing_commas(&rendered);
        let fragment: Value = serde_json::from_str(&fixed)
            .with_context(|| format!("parsing section {} output", idx))?;
        body.push(fragment);
    }
    Ok(serde_json::json!({
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": body
    }))
}

fn build_section_context(data: &Value, theme: &Theme) -> Value {
    let mut ctx = match data {
        Value::Object(map) => map.clone(),
        _ => Map::new(),
    };
    let mut theme_obj = Map::new();
    let mut tokens = Map::new();
    for (key, value) in &theme.tokens {
        tokens.insert(key.clone(), value.clone());
    }
    theme_obj.insert("tokens".to_string(), Value::Object(tokens));
    ctx.insert("theme".to_string(), Value::Object(theme_obj));
    Value::Object(ctx)
}