Skip to main content

systemprompt_content/
homepage_prerenderer.rs

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