diff --git a/web-pages/product-site/assets/css/site.css b/web-pages/product-site/assets/css/site.css index 20e5d4d49..3e47f8039 100644 --- a/web-pages/product-site/assets/css/site.css +++ b/web-pages/product-site/assets/css/site.css @@ -610,6 +610,49 @@ td a { color: var(--muted); } +.ecosystem-repositories { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 44px; +} + +.ecosystem-repositories article { + display: flex; + min-height: 250px; + flex-direction: column; + padding-top: 20px; + border-top: 3px solid var(--blue); +} + +.ecosystem-repositories article:nth-child(2) { + border-color: var(--green); +} + +.ecosystem-repositories article:nth-child(3) { + border-color: #d08a21; +} + +.repository-role { + margin: 0 0 8px; + color: var(--muted); + font-size: 13px; + font-weight: 750; +} + +.ecosystem-repositories h3 { + margin: 0 0 8px; + font-size: 24px; +} + +.ecosystem-repositories article > p:not(.repository-role) { + margin: 0 0 24px; + color: var(--muted); +} + +.ecosystem-repositories .text-link { + margin-top: auto; +} + .final-cta { display: flex; align-items: end; @@ -1192,7 +1235,8 @@ td a { grid-template-columns: 1fr; } - .responsibility-grid { + .responsibility-grid, + .ecosystem-repositories { gap: 24px; } @@ -1394,10 +1438,15 @@ td a { } .responsibility-grid, + .ecosystem-repositories, .deploy-grid { grid-template-columns: 1fr; } + .ecosystem-repositories article { + min-height: 0; + } + .final-cta, .site-footer { align-items: flex-start; diff --git a/web-pages/product-site/templates/home.html b/web-pages/product-site/templates/home.html index 67ae4c52f..61994a81e 100644 --- a/web-pages/product-site/templates/home.html +++ b/web-pages/product-site/templates/home.html @@ -155,6 +155,34 @@

{{ '上线证据' if language == 'zh' else 'Release evidence' }}

+
+
+

{{ '生态仓库' if language == 'zh' else 'Ecosystem repositories' }}

+

{{ '从模型到视频工作流' if language == 'zh' else 'From models to video workflows' }}

+

{{ '按目标进入专用仓库,核对模型、部署边界与可复现示例。' if language == 'zh' else 'Open the focused repository for its models, deployment boundaries, and reproducible examples.' }}

+
+
+
+

{{ '端到端语音识别' if language == 'zh' else 'End-to-end speech recognition' }}

+

Fun-ASR

+

{{ '端到端语音识别模型家族,覆盖旗舰与 31 语言 MLT 检查点。' if language == 'zh' else 'A family of end-to-end speech recognition models spanning flagship and 31-language MLT checkpoints.' }}

+ {{ '查看 Fun-ASR' if language == 'zh' else 'Open Fun-ASR on GitHub' }} +
+
+

{{ '多语言语音理解' if language == 'zh' else 'Multilingual speech understanding' }}

+

SenseVoice

+

{{ '覆盖语音识别、语言、情感与音频事件的多语言理解模型。' if language == 'zh' else 'Multilingual understanding across speech recognition, language, emotion, and audio events.' }}

+ {{ '查看 SenseVoice' if language == 'zh' else 'Open SenseVoice on GitHub' }} +
+
+

{{ '视频理解与剪辑' if language == 'zh' else 'Video understanding and clipping' }}

+

FunClip

+

{{ '以语音与文本定位为核心的视频理解与智能剪辑工作流。' if language == 'zh' else 'Video understanding and intelligent clipping workflows driven by speech and text localization.' }}

+ {{ '查看 FunClip' if language == 'zh' else 'Open FunClip on GitHub' }} +
+
+
+

{{ '下一步' if language == 'zh' else 'Next step' }}

diff --git a/web-pages/product-site/tests/test_build.py b/web-pages/product-site/tests/test_build.py index 05b4fffa1..4402eaa83 100644 --- a/web-pages/product-site/tests/test_build.py +++ b/web-pages/product-site/tests/test_build.py @@ -95,6 +95,27 @@ def test_visible_repository_links_use_fixed_conversion_routes(tmp_path): assert json_ld['codeRepository'] == 'https://github.com/modelscope/FunASR' +def test_home_surfaces_attributed_ecosystem_repositories(tmp_path): + build(tmp_path) + + for relative, expected_heading in ( + ('index.html', '从模型到视频工作流'), + ('en/index.html', 'From models to video workflows'), + ): + soup = read_soup(tmp_path / relative) + section = soup.select_one('[data-section="ecosystem-adoption"]') + + assert section + assert section.h2.get_text(strip=True) == expected_heading + assert {link.get('href') for link in section.select('a[href]')} == { + '/go/fun-asr', + '/go/sensevoice', + '/go/funclip', + } + text = section.get_text(' ', strip=True) + assert all(name in text for name in ('Fun-ASR', 'SenseVoice', 'FunClip')) + + def test_build_manifest_records_asset_hashes(tmp_path): manifest = build(tmp_path)