diff --git a/docs/en/DEPLOY_OPTION.md b/docs/en/DEPLOY_OPTION.md index ea41a63dc..7d2fd9c39 100644 --- a/docs/en/DEPLOY_OPTION.md +++ b/docs/en/DEPLOY_OPTION.md @@ -1505,6 +1505,38 @@ const envs: Record> = { } ``` +## Branding Customization + +You can customize the logo and title displayed on the landing page by creating a branding configuration file. + +### Configuration + +1. Create `packages/cdk/branding.json` with your custom settings: + +```json +{ + "logoPath": "your-logo.svg", + "title": "Your Custom Title" +} +``` + +2. Place your custom SVG logo file in `packages/web/src/assets/`: + +``` +packages/web/src/assets/your-logo.svg +``` + +### Parameters + +- `logoPath` (optional): Filename of the SVG logo in `packages/web/src/assets/` +- `title` (optional): Custom title text to display + +### Notes + +- If `branding.json` doesn't exist, default AWS logo and title are used +- Only SVG format is supported for custom logos +- The logo will be displayed at 80x80 pixels (size-20 class) + ## Security-Related Settings ### Disable Self-Signup diff --git a/docs/ja/DEPLOY_OPTION.md b/docs/ja/DEPLOY_OPTION.md index b62d90206..497f33755 100644 --- a/docs/ja/DEPLOY_OPTION.md +++ b/docs/ja/DEPLOY_OPTION.md @@ -1512,6 +1512,38 @@ const envs: Record> = { } ``` +## ブランディングカスタマイズ + +ランディングページに表示されるロゴとタイトルをカスタマイズできます。 + +### 設定方法 + +1. `packages/cdk/branding.json` にカスタム設定を作成: + +```json +{ + "logoPath": "your-logo.svg", + "title": "カスタムタイトル" +} +``` + +2. カスタムSVGロゴファイルを `packages/web/src/assets/` に配置: + +``` +packages/web/src/assets/your-logo.svg +``` + +### パラメータ + +- `logoPath` (オプション): `packages/web/src/assets/` 内のSVGロゴファイル名 +- `title` (オプション): 表示するカスタムタイトルテキスト + +### 注意事項 + +- `branding.json` が存在しない場合、デフォルトのAWSロゴとタイトルが使用されます +- カスタムロゴはSVG形式のみサポートされています +- ロゴは80x80ピクセル(size-20クラス)で表示されます + ## セキュリティ関連設定 ### セルフサインアップを無効化する diff --git a/packages/cdk/branding.ts b/packages/cdk/branding.ts new file mode 100644 index 000000000..99a5eb6e1 --- /dev/null +++ b/packages/cdk/branding.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +// Branding configuration interface +interface BrandingConfig { + logoPath?: string; + title?: string; +} + +// Load branding configuration from JSON file +export const loadBrandingConfig = (): BrandingConfig => { + const brandingPath = path.join(__dirname, 'branding.json'); + try { + if (fs.existsSync(brandingPath)) { + const brandingData = fs.readFileSync(brandingPath, 'utf8'); + return JSON.parse(brandingData); + } + } catch (error) { + console.warn('Failed to load branding.json, using defaults:', error); + } + return {}; +}; diff --git a/packages/cdk/lib/construct/web.ts b/packages/cdk/lib/construct/web.ts index b7bcc1344..f7d4a5d24 100644 --- a/packages/cdk/lib/construct/web.ts +++ b/packages/cdk/lib/construct/web.ts @@ -70,6 +70,10 @@ export interface WebProps { readonly agentCoreAgentBuilderRuntime?: AgentCoreConfiguration; readonly agentCoreExternalRuntimes: AgentCoreConfiguration[]; readonly agentCoreRegion?: string; + readonly brandingConfig?: { + logoPath?: string; + title?: string; + }; } export class Web extends Construct { @@ -307,6 +311,8 @@ export class Web extends Construct { VITE_APP_AGENT_CORE_EXTERNAL_RUNTIMES: JSON.stringify( props.agentCoreExternalRuntimes ), + VITE_APP_BRANDING_LOGO_PATH: props.brandingConfig?.logoPath ?? '', + VITE_APP_BRANDING_TITLE: props.brandingConfig?.title ?? '', }, }); // Enhance computing resources diff --git a/packages/cdk/lib/generative-ai-use-cases-stack.ts b/packages/cdk/lib/generative-ai-use-cases-stack.ts index 7d3f4e53e..e17973846 100644 --- a/packages/cdk/lib/generative-ai-use-cases-stack.ts +++ b/packages/cdk/lib/generative-ai-use-cases-stack.ts @@ -293,6 +293,8 @@ export class GenerativeAiUseCasesStack extends Stack { webBucket: props.webBucket, cognitoUserPoolProxyEndpoint: props.cognitoUserPoolProxyEndpoint, cognitoIdentityPoolProxyEndpoint: props.cognitoIdentityPoolProxyEndpoint, + // Branding + brandingConfig: params.brandingConfig, }); // RAG diff --git a/packages/cdk/lib/stack-input.ts b/packages/cdk/lib/stack-input.ts index ee8964323..3c066d1eb 100644 --- a/packages/cdk/lib/stack-input.ts +++ b/packages/cdk/lib/stack-input.ts @@ -254,6 +254,13 @@ export const processedStackInputSchema = baseStackInputSchema.extend({ ), // Processed agentCoreRegion (null -> modelRegion) agentCoreRegion: z.string(), + // Branding configuration + brandingConfig: z + .object({ + logoPath: z.string().optional(), + title: z.string().optional(), + }) + .optional(), }); export type StackInput = z.infer; diff --git a/packages/cdk/parameter.ts b/packages/cdk/parameter.ts index 1b417e06a..8000e662a 100644 --- a/packages/cdk/parameter.ts +++ b/packages/cdk/parameter.ts @@ -5,6 +5,7 @@ import { ProcessedStackInput, } from './lib/stack-input'; import { ModelConfiguration } from 'generative-ai-use-cases'; +import { loadBrandingConfig } from './branding'; // Get parameters from CDK Context const getContext = (app: cdk.App): StackInput => { @@ -77,5 +78,7 @@ export const getParams = (app: cdk.App): ProcessedStackInput => { ), // Process agentCoreRegion: null -> modelRegion agentCoreRegion: params.agentCoreRegion || params.modelRegion, + // Load branding configuration + brandingConfig: loadBrandingConfig(), }; }; diff --git a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap index f2588e9e3..a1328b65f 100644 --- a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap +++ b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap @@ -19017,6 +19017,8 @@ exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 6`] = ` ], ], }, + "VITE_APP_BRANDING_LOGO_PATH": "", + "VITE_APP_BRANDING_TITLE": "", "VITE_APP_COGNITO_IDENTITY_POOL_PROXY_ENDPOINT": { "Fn::Join": [ "", @@ -39957,6 +39959,8 @@ exports[`GenerativeAiUseCases matches the snapshot 6`] = ` ], ], }, + "VITE_APP_BRANDING_LOGO_PATH": "", + "VITE_APP_BRANDING_TITLE": "", "VITE_APP_COGNITO_IDENTITY_POOL_PROXY_ENDPOINT": "", "VITE_APP_COGNITO_USER_POOL_PROXY_ENDPOINT": "", "VITE_APP_ENDPOINT_NAMES": "[]", diff --git a/packages/web/src/hooks/useBranding.ts b/packages/web/src/hooks/useBranding.ts new file mode 100644 index 000000000..bd1a8345b --- /dev/null +++ b/packages/web/src/hooks/useBranding.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react'; + +interface BrandingConfig { + logoPath: string; + title: string; +} + +const useBranding = (): BrandingConfig => { + const brandingConfig = useMemo(() => { + const logoPath = import.meta.env.VITE_APP_BRANDING_LOGO_PATH; + const title = import.meta.env.VITE_APP_BRANDING_TITLE; + + return { + logoPath: logoPath || '', + title: title || '', + }; + }, []); + + return brandingConfig; +}; + +export default useBranding; diff --git a/packages/web/src/pages/LandingPage.tsx b/packages/web/src/pages/LandingPage.tsx index 15473a0b8..2d14345ef 100644 --- a/packages/web/src/pages/LandingPage.tsx +++ b/packages/web/src/pages/LandingPage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import CardDemo from '../components/CardDemo'; import Button from '../components/Button'; @@ -23,6 +23,7 @@ import { } from 'react-icons/pi'; import AwsIcon from '../assets/aws.svg?react'; import useInterUseCases from '../hooks/useInterUseCases'; + import { AgentPageQueryParams, ChatPageQueryParams, @@ -51,6 +52,8 @@ const agentCoreEnabled: boolean = import.meta.env.VITE_APP_AGENT_CORE_ENABLED === 'true'; const inlineAgents: boolean = import.meta.env.VITE_APP_INLINE_AGENTS === 'true'; const mcpEnabled: boolean = import.meta.env.VITE_APP_MCP_ENABLED === 'true'; +const logoPath: string = import.meta.env.VITE_APP_BRANDING_LOGO_PATH || ''; +const brandingTitle: string = import.meta.env.VITE_APP_BRANDING_TITLE || ''; const { imageGenModelIds, videoGenModelIds, @@ -66,6 +69,22 @@ const LandingPage: React.FC = () => { const { setIsShow, init } = useInterUseCases(); const { t } = useTranslation(); + const displayLogo = useMemo(() => { + if (logoPath) { + const logoUrl = new URL(`../assets/${logoPath}`, import.meta.url).href; + return ( + {brandingTitle + ); + } + return ; + }, []); + + const displayTitle = brandingTitle || t('landing.title'); + const demoChat = () => { const params: ChatPageQueryParams = { content: t('landing.demo.chat.content'), @@ -282,8 +301,8 @@ const LandingPage: React.FC = () => { return (
- - {t('landing.title')} + {displayLogo} + {displayTitle}
diff --git a/packages/web/src/vite-env.d.ts b/packages/web/src/vite-env.d.ts index 36077167f..7addf8ab1 100644 --- a/packages/web/src/vite-env.d.ts +++ b/packages/web/src/vite-env.d.ts @@ -39,6 +39,8 @@ interface ImportMetaEnv { readonly VITE_APP_AGENT_CORE_AGENT_BUILDER_ENABLED: string; readonly VITE_APP_AGENT_CORE_AGENT_BUILDER_RUNTIME: string; readonly VITE_APP_AGENT_CORE_EXTERNAL_RUNTIMES: string; + readonly VITE_APP_BRANDING_LOGO_PATH: string; + readonly VITE_APP_BRANDING_TITLE: string; readonly VITE_APP_MCP_SERVERS_CONFIG: string; } diff --git a/setup-env.sh b/setup-env.sh index 21c185843..a58aa1558 100755 --- a/setup-env.sh +++ b/setup-env.sh @@ -60,3 +60,10 @@ export VITE_APP_AGENT_CORE_AGENT_BUILDER_ENABLED=$(extract_value "$stack_output" export VITE_APP_AGENT_CORE_AGENT_BUILDER_RUNTIME=$(extract_value "$stack_output" AgentCoreAgentBuilderRuntime) export VITE_APP_AGENT_CORE_EXTERNAL_RUNTIMES=$(extract_value "$stack_output" AgentCoreExternalRuntimes) export VITE_APP_MCP_SERVERS_CONFIG=$(extract_value "$stack_output" McpServersConfig) +if [ -f "packages/cdk/branding.json" ]; then + export VITE_APP_BRANDING_LOGO_PATH=$(cat packages/cdk/branding.json | jq -r '.logoPath // ""') + export VITE_APP_BRANDING_TITLE=$(cat packages/cdk/branding.json | jq -r '.title // ""') +else + export VITE_APP_BRANDING_LOGO_PATH="" + export VITE_APP_BRANDING_TITLE="" +fi