Skip to main content

systemprompt_content/
homepage_prerenderer.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::Result;
5use async_trait::async_trait;
6use systemprompt_cloud::constants::storage;
7use systemprompt_models::ContentConfigRaw;
8use systemprompt_models::services::ServicesConfig;
9use systemprompt_provider_contracts::{
10    PagePrepareContext, PagePrerenderer, PageRenderSpec, WebConfig,
11};
12
13fn resolve_content_raw<'a>(ctx: &'a PagePrepareContext<'_>) -> Result<&'a ContentConfigRaw> {
14    if let Some(services) = ctx.content_config::<ServicesConfig>() {
15        return Ok(&services.content.raw);
16    }
17    ctx.content_config::<ContentConfigRaw>()
18        .ok_or_else(|| anyhow::anyhow!("ContentConfig not available in context"))
19}
20
21const PAGE_TYPE: &str = "homepage";
22const TEMPLATE_NAME: &str = "homepage";
23const OUTPUT_FILE: &str = "index.html";
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct DefaultHomepagePrerenderer;
27
28impl DefaultHomepagePrerenderer {
29    #[must_use]
30    pub const fn new() -> Self {
31        Self
32    }
33
34    fn extract_branding(
35        web_config: &WebConfig,
36        content_config: &ContentConfigRaw,
37    ) -> HomepageBranding {
38        let org = &content_config.metadata.structured_data.organization;
39        let branding = &web_config.branding;
40
41        HomepageBranding {
42            org_name: org.name.clone(),
43            org_url: org.url.clone(),
44            org_logo: org.logo.clone(),
45            logo_path: branding
46                .logo
47                .primary
48                .svg
49                .clone()
50                .unwrap_or_else(String::new),
51            favicon_path: branding.favicon.clone(),
52            twitter_handle: branding.twitter_handle.clone(),
53            display_sitename: branding.display_sitename,
54        }
55    }
56}
57
58struct HomepageBranding {
59    org_name: String,
60    org_url: String,
61    org_logo: String,
62    logo_path: String,
63    favicon_path: String,
64    twitter_handle: String,
65    display_sitename: bool,
66}
67
68#[async_trait]
69impl PagePrerenderer for DefaultHomepagePrerenderer {
70    fn page_type(&self) -> &str {
71        PAGE_TYPE
72    }
73
74    fn priority(&self) -> u32 {
75        100
76    }
77
78    async fn prepare(&self, ctx: &PagePrepareContext<'_>) -> Result<Option<PageRenderSpec>> {
79        let content_config = resolve_content_raw(ctx)?;
80
81        let branding = Self::extract_branding(ctx.web_config, content_config);
82
83        let base_data = serde_json::json!({
84            "site": ctx.web_config,
85            "ORG_NAME": branding.org_name,
86            "ORG_URL": branding.org_url,
87            "ORG_LOGO": branding.org_logo,
88            "LOGO_PATH": branding.logo_path,
89            "FAVICON_PATH": branding.favicon_path,
90            "TWITTER_HANDLE": branding.twitter_handle,
91            "DISPLAY_SITENAME": branding.display_sitename,
92            "HEADER_CTA_URL": "/",
93            "JS_BASE_PATH": format!("/{}", storage::JS),
94            "CSS_BASE_PATH": format!("/{}", storage::CSS)
95        });
96
97        Ok(Some(PageRenderSpec::new(
98            TEMPLATE_NAME,
99            base_data,
100            PathBuf::from(OUTPUT_FILE),
101        )))
102    }
103}
104
105pub fn default_homepage_prerenderer() -> Arc<dyn PagePrerenderer> {
106    Arc::new(DefaultHomepagePrerenderer::new())
107}