greentic-designer 0.6.0

Greentic Designer — orchestrator that powers Adaptive Card design via the adaptive-card-mcp toolkit
Documentation
//! Greentic Designer web UI — Axum server.
//!
//! This module boots an HTTP server that serves the embedded HTML/JS/CSS
//! bundle and exposes a JSON API around the LLM orchestrator (`/api/chat`,
//! `/api/validate`, `/api/examples`, ...). The real handler code lives in
//! `routes/`.

mod assets;
pub mod image;
pub mod llm;
pub mod prompt_builder;
pub mod routes;
pub mod state;
pub mod storage;
pub mod tool_bridge;

use std::sync::Arc;

use anyhow::Result;

use crate::cli::UiArgs;
use state::AppState;

/// Start the Greentic Designer HTTP server and block until it exits.
pub async fn launch(args: UiArgs) -> Result<()> {
    use crate::cli::Provider;

    let knowledge_base = Arc::new(crate::knowledge::Knowledge::embedded());
    let system_prompt = prompt_builder::build_system_prompt(&knowledge_base, None);

    let llm = match args.provider {
        Provider::Openai => {
            let api_key = args.openai_api_key.ok_or_else(|| {
                anyhow::anyhow!(
                    "OpenAI API key required: set --openai-api-key or OPENAI_API_KEY env var"
                )
            })?;
            let model = args.model.unwrap_or_else(|| "gpt-4o".to_string());
            llm::LlmBackend::OpenAi(llm::openai::OpenAiProvider { api_key, model })
        }
        Provider::Anthropic => {
            let api_key = args.anthropic_api_key.ok_or_else(|| {
                anyhow::anyhow!(
                    "Anthropic API key required: set --anthropic-api-key or ANTHROPIC_API_KEY env var"
                )
            })?;
            let model = args
                .model
                .unwrap_or_else(|| "claude-sonnet-4-20250514".to_string());
            llm::LlmBackend::Anthropic(llm::anthropic::AnthropicProvider { api_key, model })
        }
    };

    // Initialize persistent storage.
    std::fs::create_dir_all(&args.storage_dir)?;
    let image_store = storage::local::LocalImageStore::new(&args.storage_dir)?;

    let state = Arc::new(AppState {
        llm,
        system_prompt,
        knowledge_base,
        image_store,
        pack_jobs: tokio::sync::Mutex::new(std::collections::HashMap::new()),
        wizard_jobs: tokio::sync::Mutex::new(std::collections::HashMap::new()),
    });

    let router = routes::build(state);

    let addr = format!("127.0.0.1:{}", args.port.unwrap_or(0));
    let listener = tokio::net::TcpListener::bind(&addr).await?;
    let port = listener.local_addr()?.port();
    let url = format!("http://127.0.0.1:{port}");

    eprintln!("Greentic Designer UI started at: {url}");
    let _ = open::that(&url);

    axum::serve(listener, router).await?;

    Ok(())
}