use crate::services::ui_renderer::active_theme;
use systemprompt_models::mcp::ui_method_js_constants;
#[derive(Debug)]
pub struct HtmlBuilder {
title: String,
styles: Vec<String>,
scripts: Vec<String>,
body: String,
}
impl HtmlBuilder {
pub fn new(title: &str) -> Self {
Self {
title: title.to_owned(),
styles: Vec::new(),
scripts: Vec::new(),
body: String::new(),
}
}
pub fn add_style(mut self, css: &str) -> Self {
self.styles.push(css.to_owned());
self
}
pub fn add_script(mut self, js: &str) -> Self {
self.scripts.push(js.to_owned());
self
}
pub fn body(mut self, html: &str) -> Self {
html.clone_into(&mut self.body);
self
}
pub fn build(self) -> String {
let theme = active_theme();
let mut sheets = vec![design_tokens().to_owned()];
if let Some(theme) = theme {
sheets.push(format!(":root {{\n{}\n}}", theme.tokens));
}
sheets.extend(self.styles);
if let Some(theme) = theme
&& !theme.extra_css.is_empty()
{
sheets.push(theme.extra_css.to_owned());
}
let styles = format!("<style>\n{}\n</style>", sheets.join("\n"));
let scripts = {
let mut all = vec![ui_method_js_constants()];
all.extend(self.scripts);
all.push(frame_script().to_owned());
format!("<script>\n{}\n</script>", all.join("\n"))
};
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
{styles}
</head>
<body>
{body}
{scripts}
</body>
</html>"#,
title = html_escape(&self.title),
styles = styles,
body = self.body,
scripts = scripts,
)
}
}
pub fn html_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
pub fn json_to_js_literal(value: &serde_json::Value) -> String {
serde_json::to_string(value).unwrap_or_else(|_| "null".to_owned())
}
pub const fn base_styles() -> &'static str {
include_str!("assets/css/base.css")
}
pub const fn design_tokens() -> &'static str {
include_str!("assets/css/tokens.css")
}
pub const fn mcp_app_bridge_script() -> &'static str {
include_str!("assets/js/bridge.js")
}
pub const fn frame_script() -> &'static str {
include_str!("assets/js/frame.js")
}
pub fn artifact_shell_template() -> String {
include_str!("assets/html/artifact-shell.html")
.replace(MCP_UI_CONSTANTS_PLACEHOLDER, &ui_method_js_constants())
}
const MCP_UI_CONSTANTS_PLACEHOLDER: &str = "/*MCP_UI_CONSTANTS*/";