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