refactor: overhaul website with new design and improved user experience#299
refactor: overhaul website with new design and improved user experience#299alwaysmavs merged 170 commits intomainfrom
Conversation
…t/dark themes style(Navbar): enhance light mode styles and adjust footer background color config: enable color mode switch and respect user preferences
…ol-cloud, oomol-studio, open-source, and system-status
…ht and dark modes
…tency across components
… design tokens for consistency
…ign tokens and enhance hover effects
… with design tokens
…cific selectors for better clarity
…dencies as peer dependencies in package-lock.json
…proved performance and consistency
… for improved performance and consistency
… and improved theming
refactor(docusaurus.config.js): add onUntruncatedBlogPosts option for blog configuration
…n hooks for better configuration
…ate styles for improved layout
…onsistency across components
…igation and enhance styles for better layout
…ibility; update rendering logic for anchor and button elements style(Navbar): update background color to use CSS variables for consistency and improved theming style(Downloads): replace hardcoded colors with CSS variables for dark theme support style(Pricing): update background colors and text colors to use CSS variables for better theme management style(Support): change border color to use CSS variable for consistency across components
- Added logo-en-light.svg for the English light theme. - Added logo-symbol-dark.svg for the dark theme symbol. - Added logo-symbol-light.svg for the light theme symbol. - Added logo-zh-dark.svg for the Chinese dark theme. - Added logo-zh-light.svg for the Chinese light theme.
…ing; add Downloads page with download options
…ge; update Navbar to use Link component for navigation
…sponding translations and styles
…veness and clarity
…ations - Added @radix-ui/themes to package.json for enhanced theming support. - Refactored translation calls in the pricing page for better readability. - Removed unused styles related to free and standard plans in styles.module.scss. - Cleaned up the pricing table styles and adjusted responsive design.
… fusion API options
…d pro subscriptions
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
Summary by CodeRabbit发布说明
✏️ Tip: You can customize this high-level summary in your review settings. 浏览概览此次更改涉及网站基础设施的广泛重构,包括导航栏重组、设计系统实现(CSS变量)、新增Radix UI组件库、文档和国际化更新、主页组件优化、以及对齐多个页面和样式的现代化工作。 更改内容
评估代码审查工作量🎯 5 (关键) | ⏱️ ~120-150 分钟 重点关注区域:
Pre-merge checks❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying oomol-com with
|
| Latest commit: |
671eecf
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://3819558b.oomol-com.pages.dev |
| Branch Preview URL: | https://frp.oomol-com.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/contact-us/index.tsx (1)
319-356: 仓库卡片缺少键盘可访问性支持。当前仓库卡片使用
onClick处理点击事件,但div元素不支持键盘导航。建议添加tabIndex、role和onKeyDown处理,或改用<a>标签。<div className={styles.repository} key={repo.url} + role="link" + tabIndex={0} onClick={() => { window.open(`https://github.com/${repo.url}`, "_blank"); }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + window.open(`https://github.com/${repo.url}`, "_blank"); + } + }} >或者更好的方式是使用语义化的
<a>标签:<a className={styles.repository} key={repo.url} href={`https://github.com/${repo.url}`} target="_blank" rel="noopener noreferrer" >
🧹 Nitpick comments (47)
src/components/HomepageCommunityShare/index.tsx (1)
37-48: 建议:图片 alt 走翻译,并考虑启用 lazy loading当前两张大图的
alt文案是硬编码英文字符串,页面如果有多语言,建议改为使用translate提供可本地化的文案,同时可以顺便加上loading="lazy"降低首屏加载压力,类似:- <img - src={isDark ? communityHomeDark : communityHomeLight} - alt="Community Home" - className={styles.image} - /> + <img + src={isDark ? communityHomeDark : communityHomeLight} + alt={translate({ + id: "HOME.CommunityShare.imageHomeAlt", + message: "Community Home", + })} + loading="lazy" + className={styles.image} + /> @@ - <img - src={isDark ? communityDetailDark : communityDetailLight} - alt="Community Detail" - className={styles.image} - /> + <img + src={isDark ? communityDetailDark : communityDetailLight} + alt={translate({ + id: "HOME.CommunityShare.imageDetailAlt", + message: "Community Detail", + })} + loading="lazy" + className={styles.image} + />(对应的翻译 key 需要在 i18n 文件中补上。)
src/components/HomepageCommunityShare/styles.module.scss (1)
3-23: 可以合并.container的 padding 声明以减少冗余现在先写了一次
padding: 100px 0 50px;,随后又单独设置padding-left/right,最终效果是对的,但可读性稍差一点,可以直接合并成一个声明,例如:- padding: 100px 0 50px; - gap: 60px; - max-width: 1440px; - margin: 0 auto; - padding-left: 32px; - padding-right: 32px; + padding: 100px 32px 50px; + gap: 60px; + max-width: 1440px; + margin: 0 auto;下方几个 media query 里如果有类似模式,也可以按需做同类整理,仅为样式层的小优化。
src/components/Spin/Spin.tsx (1)
106-125: 整体逻辑正确!使用 RAF + 定时器的模式可以正确触发 CSS 过渡动画:
- 当 progress 有值时,通过 RAF 延迟更新状态
- 当 progress 为 undefined 时,先设为 0,然后延迟 50ms 设为 100,确保浏览器渲染初始状态后再触发过渡
- cleanup 函数正确清理了 RAF 和定时器,避免内存泄漏
可选优化:类型注解的跨平台兼容性
Line 108 使用
NodeJS.Timeout类型在纯浏览器环境中可能不可用。虽然在大多数配置中可以正常工作,但为了更好的跨平台兼容性,可以考虑使用number或ReturnType<typeof setTimeout>:- let timer: NodeJS.Timeout; + let timer: ReturnType<typeof setTimeout>;另外,Line 115 的 50ms 延迟是为了触发 CSS 过渡的关键值,建议添加注释说明其用途。
src/components/HomepageLifecycle/index.tsx (6)
16-16: 建议保持注释语言一致性。代码中混合使用了中英文注释。建议根据团队规范统一注释语言。
19-21: 列表格式检测逻辑可以更健壮。当前检测逻辑只检查是否包含
•和换行符,但不验证格式是否正确。如果描述文本中意外包含这些字符但不是列表格式,可能会导致错误的渲染。可以考虑使用更严格的检测:
const checkIsListFormat = (description: string): boolean => { - return description.includes("•") && description.includes("\n"); + const lines = description.trim().split("\n"); + return lines.length > 1 && lines.every(line => line.trim().startsWith("•")); };
103-107: 建议添加显式的 button type 属性。按钮元素应该明确指定
type="button"以防止在表单内意外触发提交行为。<button + type="button" className={`${styles.stepItem} ${ index === activeStep ? styles.active : "" }`} onClick={() => handleStepClick(index)} >
103-157: 可访问性改进:添加 ARIA 属性。步骤导航按钮应该包含适当的 ARIA 属性,以改善屏幕阅读器用户的体验。
<button type="button" className={`${styles.stepItem} ${ index === activeStep ? styles.active : "" }`} onClick={() => handleStepClick(index)} + aria-label={`${step.title}: ${step.subtitle}`} + aria-current={index === activeStep ? "step" : undefined} >
144-146: 优化渲染性能:避免在渲染中进行字符串处理。在渲染函数中执行
split()和replace()操作会在每次重新渲染时重复执行。建议将处理逻辑提取到 useMemo 中或者在 steps 数据准备阶段就完成格式化。可以创建一个辅助函数来处理描述文本:
const formatDescription = (description: string) => { if (checkIsListFormat(description)) { return description.split("\n").map(line => line.replace("• ", "")); } return description; }; // 在 useMemo 中缓存格式化后的步骤 const formattedSteps = useMemo(() => steps.map(step => ({ ...step, formattedDescription: formatDescription(step.description) })), [steps] );
164-169: 硬编码的占位符文本应该国际化。占位符文本 "Step {activeStep + 1} Image/Video Placeholder" 是硬编码的英文文本,与组件的其他部分使用 i18n 不一致。
建议使用 translate 函数:
<div className={styles.placeholderText}> - Step {activeStep + 1} Image/Video Placeholder + {translate( + { message: "HOME.Lifecycle.placeholder" }, + { step: activeStep + 1 } + )} </div>src/components/HomepageLifecycle/styles.module.scss (5)
54-66: 移除注释掉的代码。第 59 行有注释掉的 CSS 属性。如果不再需要,建议删除;如果需要保留用于未来调整,请添加说明注释。
.mainContent { display: grid; grid-template-columns: 420px 1fr; gap: 60px; - // margin-bottom: 100px; align-items: start;
69-82: 考虑固定高度的灵活性。
.stepsNavigation使用了固定高度height: 580px。如果将来步骤数量或内容长度发生变化,这可能会导致布局问题。建议考虑使用min-height或让高度自适应内容。.stepsNavigation { display: flex; flex-direction: column; gap: 0; position: sticky; top: 100px; - height: 580px; + min-height: 580px; margin-top: 20px;
100-117: 移除或更新中文注释。第 106 行的中文注释应该与代码库的注释规范保持一致。
&.active { background: rgba(var(--ifm-color-primary-rgb), 0.08); .stepNumber { background: var(--ifm-color-primary); color: white; - // 移除 scale 动画,数字大小已统一 }
242-253: 动画的 max-height 可能需要调整。
expandContent动画中的max-height: 500px可能不足以容纳较长的步骤描述内容。如果内容超过这个高度,动画效果会被截断。考虑使用更大的值或根据实际内容调整:
@keyframes expandContent { from { opacity: 0; max-height: 0; transform: translateY(-8px); } to { opacity: 1; - max-height: 500px; + max-height: 800px; transform: translateY(0); } }
297-318: 移除注释掉的代码。第 298 行有注释掉的 CSS 属性。如果不再需要,建议删除。
.stepDescription { - // margin-bottom: 8px; - p { font-size: 1rem; color: var(--ifm-color-emphasis-700);eslint.config.mjs (1)
30-74: 考虑使用globals包简化全局变量定义。当前手动定义了 40+ 个全局变量,这种方式冗长且难以维护。推荐使用
globals包来提供预定义的全局变量集合。首先安装
globals包:npm install -D globals然后重构配置:
+import globals from 'globals'; import js from '@eslint/js'; import tseslint from '@typescript-eslint/eslint-plugin'; import tsparser from '@typescript-eslint/parser'; import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks';在配置中使用:
languageOptions: { parser: tsparser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true, }, }, globals: { - // Browser globals - window: 'readonly', - document: 'readonly', - console: 'readonly', - navigator: 'readonly', - location: 'readonly', - localStorage: 'readonly', - sessionStorage: 'readonly', - setTimeout: 'readonly', - clearTimeout: 'readonly', - setInterval: 'readonly', - clearInterval: 'readonly', - fetch: 'readonly', - XMLHttpRequest: 'readonly', - MediaQueryList: 'readonly', - MediaQueryListEvent: 'readonly', - MouseEvent: 'readonly', - HTMLElement: 'readonly', - HTMLDivElement: 'readonly', - HTMLButtonElement: 'readonly', - HTMLSpanElement: 'readonly', - HTMLTableElement: 'readonly', - HTMLTableSectionElement: 'readonly', - HTMLTableRowElement: 'readonly', - HTMLTableCellElement: 'readonly', - HTMLTableCaptionElement: 'readonly', - Element: 'readonly', - AbortController: 'readonly', + ...globals.browser, + ...globals.node, gtag: 'readonly', - - // Node globals - module: 'readonly', - require: 'readonly', - process: 'readonly', - __dirname: 'readonly', - __filename: 'readonly', - - // TypeScript - NodeJS: 'readonly', - - // React/JSX - React: 'readonly', - JSX: 'readonly', }, },i18n/zh-CN/docusaurus-plugin-content-pages/terms/styles.module.scss (1)
1-11:.box固定宽度 1200px 在小屏上可能导致横向滚动当前
.box直接设为width: 1200px;,在移动端或窄屏上会溢出视口。建议改为「最大宽度 1200px + 自适应宽度」,提升响应式体验。可以参考:
-.box { - width: 1200px; -} +.box { + max-width: 1200px; + width: 100%; +}这样在大屏仍保持 1200px 内容宽度,小屏则可以自动压缩,不会产生横向滚动条。
src/components/GetStartedPrompt/index.tsx (1)
4-17: 新增 logo 加载逻辑清晰,可考虑补充懒加载属性使用
useBaseUrl("/img/logo2x.png")注入图片路径,并在文本上方渲染<img>,整体实现没问题。如果该组件不在首屏最上方,可考虑给图片增加懒加载以减少首屏外资源的开销,例如:
<img className={styles.logo} src={logoSrc} - alt="OOMOL Logo" + alt="OOMOL Logo" + loading="lazy" />属于体验级优化,可按需采纳。
src/components/HomepageStarter/index.tsx (1)
1-19: 为i18n增加可选链和默认值,避免未配置多语言时的潜在运行时错误现在通过:
const { i18n } = useDocusaurusContext() as unknown as DocusaurusContext & { i18n: { currentLocale: string }; }; const locale = i18n.currentLocale;假设
i18n一定存在且结构固定。如果未来站点关闭 i18n 或 Docusaurus 类型结构调整,这里会在运行时报错。可以稍微放宽类型并提供安全默认值,例如:
- const { i18n } = useDocusaurusContext() as unknown as DocusaurusContext & { - i18n: { currentLocale: string }; - }; - const locale = i18n.currentLocale; + const { i18n } = useDocusaurusContext() as unknown as DocusaurusContext & { + i18n?: { currentLocale?: string }; + }; + const locale = i18n?.currentLocale ?? "en";这样即便未启用 i18n 或字段缺失,也会回退到英文,不会中断渲染。
src/lib/utils.ts (1)
8-27:callback的参数类型与对undefined的判断略不一致,可统一一下现在的实现:
const callback = function (url: string) { if (typeof url != "undefined") { window.location.href = url; } }; ... export function downloadStable( event: null | React.MouseEvent<HTMLElement, MouseEvent>, url: string ) { ... }TS 类型已经保证
url为string,正常调用时不会是undefined,但运行时代码仍在做typeof url != "undefined"判断,两者语义不完全一致。可以考虑两种方向(二选一即可):
- 保留判断,让类型也接受可选参数:
-const callback = function (url: string) { - if (typeof url != "undefined") { +const callback = function (url?: string) { + if (typeof url !== "undefined") { window.location.href = url; } }; export function downloadStable( event: null | React.MouseEvent<HTMLElement, MouseEvent>, - url: string + url?: string ) {
- 如果业务上保证一定有 URL,则去掉运行时判断,仅保留更严格的类型:
-const callback = function (url: string) { - if (typeof url != "undefined") { - window.location.href = url; - } -}; +const callback = function (url: string) { + window.location.href = url; +};看你期望的调用约束强度来选,当前实现虽能正常工作,但略显多余。
src/pages/cloud/index.tsx (2)
96-117: Hero 区域部分文案硬编码,建议也接入 i18n目前:
<h1 className={styles.heroTitle}>OOMOL Cloud</h1> ... <span className={styles.statValue}>自动扩缩容</span> <span className={styles.statValue}>RESTful API</span> <span className={styles.statValue}>MCP 协议</span>这些内容在英文站点上也会原样展示,且无法通过 i18n JSON 做本地化配置。既然 use cases / features 已经用
translate,建议 hero 标题和指标主文案也走同一套机制,例如:- <h1 className={styles.heroTitle}>OOMOL Cloud</h1> + <h1 className={styles.heroTitle}> + {translate({ message: "CLOUD.hero.title" })} + </h1> ... - <span className={styles.statValue}>自动扩缩容</span> + <span className={styles.statValue}> + {translate({ message: "CLOUD.hero.stat1.value" })} + </span>对应再在
i18n/*/code.json里补充这些 key,即可在不同语言中自由调整文案。
140-158: 列表渲染建议避免使用数组索引作为 Reactkey当前:
{useCases.map((useCase, index) => ( <UseCaseCard key={index} useCase={useCase} /> ))} ... {coreFeatures.map((feature, index) => ( <FeatureCard key={index} feature={feature} /> ))}虽然
useCases/coreFeatures目前是静态数组,但用index作为key在未来需要插入、删除或重排时容易触发不必要的重渲染甚至状态错乱。这里每项都有稳定的title,可以直接用它做 key:- {useCases.map((useCase, index) => ( - <UseCaseCard key={index} useCase={useCase} /> + {useCases.map((useCase) => ( + <UseCaseCard key={useCase.title} useCase={useCase} /> ))} ... - {coreFeatures.map((feature, index) => ( - <FeatureCard key={index} feature={feature} /> + {coreFeatures.map((feature) => ( + <FeatureCard key={feature.title} feature={feature} /> ))}这样在未来调整卡片顺序时更安全。
src/components/HomepagePdfCraftShowcase/index.tsx (1)
34-35: 考虑动态获取 GitHub star 数量硬编码的 "3000+ Stars" 会随时间变得过时。建议使用 GitHub API 在构建时获取实际的 star 数量,或使用 GitHub 的动态徽章。
可以考虑在构建时通过 GitHub API 获取:
// 示例:在构建时获取 star 数 const response = await fetch('https://api.github.com/repos/oomol-lab/pdf-craft'); const data = await response.json(); const starCount = data.stargazers_count;或使用 GitHub shields.io 徽章实现动态更新。
src/css/design-tokens.scss (1)
441-469: 考虑过渡动画的性能影响虽然代码注释提到避免全局通配符的性能问题,但
[class*="oomol-"]等选择器仍可能匹配大量元素。在复杂页面中可能影响渲染性能。如果遇到性能问题,可以考虑:
- 进一步缩小选择器范围,只针对确实需要过渡的组件类
- 使用
will-change属性提示浏览器优化- 监控实际性能指标,按需优化
当前实现对于大多数场景应该是可接受的。
src/components/HomepageFirstScreen/GridBackground.tsx (2)
216-298: 监控高斯模糊滤镜的性能影响代码中使用了多个高斯模糊滤镜,其中
stdDeviation="240"的模糊强度特别高,在低端设备上可能影响渲染性能。虽然组件已使用 memo 优化,但 SVG 滤镜本身的计算成本仍然存在。建议:
- 在不同设备上测试渲染性能
- 考虑在移动设备上降低模糊强度或简化效果
- 可以根据设备性能动态调整滤镜复杂度
当前实现对于大多数现代设备应该可以接受。
8-11: 考虑为 memo 组件添加 displayName虽然函数名称作为参数传递给
memo(),但显式设置displayName可以提高在 React DevTools 中的调试体验。export const GridBackground = memo(function GridBackground({ opacity = 0.6, className, }: GridBackgroundProps) { + // ... +}); + +GridBackground.displayName = "GridBackground";src/css/custom.scss (2)
107-119: 浅色和深色模式的 navbar 样式完全相同,可以合并简化。两个选择器
[data-theme="light"] .navbar和[data-theme="dark"] .navbar的样式规则完全一致,建议合并以减少代码重复。-/* Light mode navbar styles */ -[data-theme="light"] .navbar { - background: var(--oomol-bg-base); - opacity: 0.9; - border-bottom: 1px solid var(--oomol-border-subtle); -} - -/* Dark mode navbar styles */ -[data-theme="dark"] .navbar { +/* Navbar theme styles */ +[data-theme="light"] .navbar, +[data-theme="dark"] .navbar { background: var(--oomol-bg-base); opacity: 0.9; border-bottom: 1px solid var(--oomol-border-subtle); }
40-40:!important的使用需要注意。在
--ifm-background-color上使用!important可能是为了覆盖 Docusaurus 的默认样式。如果确实需要,建议添加注释说明原因,以便后续维护。src/pages/chat/index.tsx (1)
83-86: 使用数组索引作为 key。在静态数据渲染中使用
idx作为 key 是可以接受的,因为feature.features数组在运行时不会重新排序或修改。但如果后续数据变为动态,需要考虑使用更稳定的 key。i18n/zh-CN/docusaurus-plugin-content-pages/support/styles.module.scss (2)
20-25: 建议添加响应式设计
.supportCellBox使用固定宽度1080px,在小屏幕设备上可能导致水平滚动。建议添加响应式断点或使用max-width配合百分比宽度。.supportCellBox { - width: 1080px; + width: 100%; + max-width: 1080px; display: flex; align-items: center; justify-content: space-between; + flex-wrap: wrap; + gap: 24px; } + +@media (max-width: 768px) { + .supportCellBox { + flex-direction: column; + } + + .supportCell { + width: 100%; + max-width: 320px; + } +}
36-38: 缺少 border-width 属性设置了
border-style: solid但未指定border-width,浏览器会使用默认值(通常为medium约 3px)。建议显式指定宽度以确保一致性。border-style: solid; + border-width: 1px; border-color: var(--oomol-border-default);i18n/zh-CN/docusaurus-plugin-content-pages/privacy/styles.module.scss (1)
9-11: 建议添加响应式样式以支持小屏幕设备。
.box使用固定宽度1200px,在小于 1200px 的屏幕上会导致水平溢出。建议使用
max-width配合width: 100%实现响应式布局:.box { - width: 1200px; + width: 100%; + max-width: 1200px; + padding: 0 16px; }src/components/HomepageCoreFeatures/styles.module.scss (1)
125-138: 建议使用 CSS 变量替代硬编码的颜色值。在
featureHighlight和深色模式适配中使用了硬编码的颜色值:
- Line 135:
#667eea- Line 189:
#8b9bff为了与项目的设计令牌系统保持一致,建议将这些颜色值替换为相应的 CSS 自定义属性(如
var(--oomol-primary)等)。考虑应用类似的修改:
.featureHighlight { display: flex; align-items: flex-start; gap: 12px; padding: 20px 24px; background: linear-gradient( 135deg, - rgba(102, 126, 234, 0.1) 0%, - rgba(118, 75, 162, 0.1) 100% + rgba(var(--oomol-primary-rgb), 0.1) 0%, + rgba(var(--oomol-secondary-rgb), 0.1) 100% ); - border-left: 4px solid #667eea; + border-left: 4px solid var(--oomol-primary); border-radius: 8px; margin-top: 8px; }Also applies to: 182-191
src/components/HomepageDownloads/styles.module.scss (1)
1-61: 补充按钮的可访问状态并收紧 transition 范围
.download-btn使用设计 token 和 hover/active 动效整体不错,不过可以考虑两点优化:
- 为
.download-btn:focus-visible(或至少:focus)增加与:hover一致的样式,方便键盘用户识别当前焦点。- 将
transition: all 0.3s ease;收紧到实际需要的属性(如background-color, color, border-color, transform),避免未来新增属性被意外动画、也更利于性能。src/components/HomepageProductComparison/index.tsx (1)
4-88: 为产品数据声明类型并避免使用 index 作为 key实现整体清晰,不过这里可以稍微加强类型安全和 React 最佳实践:
- 为
products声明类型(包含color的联合类型),这样styles[product.color]出错时能在编译期暴露。map中建议使用稳定字段(如product.name或一个显式的id)作为key,而不是index,便于未来重排或动态增删项。示例:
type ProductColor = "primary" | "secondary" | "tertiary"; interface Product { name: string; stage: string; capability: string; scenario: string; tech: string; icon: string; color: ProductColor; } const products: Product[] = [/* ... */]; // ... {products.map(product => ( <div key={product.name} className={`${styles.productCard} ${styles[product.color]}`}> {/* ... */} </div> ))}i18n/zh-CN/docusaurus-plugin-content-pages/terms/_terms.mdx (1)
178-188: 12.3 条“可分割性”的中文表述可略作润色当前句子“如果本服务条款的任何条款被认定为无效或不可执行,该条款应在必要范围内进行修改以使其有效,或在无法修改的情况下予以删除,其余条款继续有效。”语义是清楚的,不过可以考虑稍作拆分或调整,使法律文本更顺口一些,例如:
如果本服务条款的任何条款被认定为无效或不可执行,该条款可以在必要范围内进行修改以使其有效;若无法修改,则应予以删除,其余条款仍继续有效。
纯属文风上的小优化,不影响现有含义。
src/components/HomepageProductComparison/styles.module.scss (1)
89-113: 建议将硬编码的颜色值替换为 CSS 变量secondary 和 tertiary 主题使用了硬编码的颜色值(#5d63a3、#3d3c60),以及 box-shadow 中的硬编码 RGBA 值。为了提高主题一致性和可维护性,建议将这些颜色定义为 CSS 变量。
考虑在 design tokens 文件中定义这些颜色:
// 在 design-tokens.scss 中添加 --oomol-secondary: #5d63a3; --oomol-tertiary: #3d3c60;然后在此文件中使用:
&.secondary { - border-color: #5d63a3; + border-color: var(--oomol-secondary); .cardHeader { - background: #5d63a3; + background: var(--oomol-secondary); } &:hover { - border-color: #5d63a3; + border-color: var(--oomol-secondary); box-shadow: 0 12px 40px rgba(0, 119, 182, 0.25); } } &.tertiary { - border-color: #3d3c60; + border-color: var(--oomol-tertiary); .cardHeader { - background: #3d3c60; + background: var(--oomol-tertiary); } &:hover { - border-color: #3d3c60; + border-color: var(--oomol-tertiary); box-shadow: 0 12px 40px rgba(72, 202, 228, 0.25); } }src/components/HomePagePricing/index.tsx (1)
66-95: 建议使用 clsx 优化 className 拼接当前使用字符串拼接处理 className,可以使用 clsx 库(项目中已经使用)来更优雅地处理条件类名。
+import clsx from "clsx"; {pricingData.map((item, index) => { - const cardClassName = item.className ? item.className : ""; return ( - <Card key={index} className={styles.card + " " + cardClassName}> + <Card key={index} className={clsx(styles.card, item.className)}> <CardHeader>同样适用于 Line 98 的 top-up 卡片:
- <Card key={index} className={styles.card + " " + styles["top-up"]}> + <Card key={index} className={clsx(styles.card, styles["top-up"])}>src/components/StudioPdfCraftCase/index.tsx (1)
137-154: 考虑将统计数据改为动态获取或添加更新提醒统计数据(特别是 "3000+ GitHub Stars")是硬编码的,可能会随时间过时。
建议考虑以下方案之一:
- 使用 GitHub API 动态获取 star 数量
- 将这些数值移到配置文件或 i18n 文件中,方便定期更新
- 在代码中添加注释,提醒定期检查和更新这些数值
示例(动态获取 GitHub stars):
const [githubStars, setGithubStars] = useState("3000+"); useEffect(() => { fetch("https://api.github.com/repos/oomol/oomol") .then(res => res.json()) .then(data => setGithubStars(`${Math.floor(data.stargazers_count / 1000)}k+`)) .catch(() => setGithubStars("3000+")); // fallback }, []);src/components/HomepageFirstScreen/index.tsx (1)
58-78: 可以简化重复的代码中文和其他语言版本的 AuroraText 实现完全相同,可以简化为单一实现。
- {i18n.currentLocale === "zh-CN" ? ( - <AuroraText className={styles["aurora-slogan"]}> - {translate({ - message: "HOME.FirstScreen.slogan.line1", - })} - <br /> - {translate({ - message: "HOME.FirstScreen.slogan.line2", - })} - </AuroraText> - ) : ( - <AuroraText className={styles["aurora-slogan"]}> - {translate({ - message: "HOME.FirstScreen.slogan.line1", - })} - <br /> - {translate({ - message: "HOME.FirstScreen.slogan.line2", - })} - </AuroraText> - )} + <AuroraText className={styles["aurora-slogan"]}> + {translate({ + message: "HOME.FirstScreen.slogan.line1", + })} + <br /> + {translate({ + message: "HOME.FirstScreen.slogan.line2", + })} + </AuroraText>如果将来需要针对不同语言做差异化处理,可以保留条件判断。但目前两个分支完全相同。
src/components/HomePageBuiltInLLM/styles.module.scss (1)
163-165: 考虑减少!important的使用。虽然在覆盖第三方库样式时
!important可能是必要的,但建议在可能的情况下通过增加选择器特异性来避免使用,以提高样式的可维护性。- :global(.margin-top--md) { - margin-top: 0 !important; - } + // 如果可以通过更高特异性的选择器实现,考虑替换 + :global(.tabs + .margin-top--md) { + margin-top: 0; + }src/components/HomepageCoreFeatures/index.tsx (1)
110-117: 描述文本拆分存在重复计算。
feature.description.split("\n")在渲染时被调用了两次,建议提取为变量以避免重复计算。+ const lines = feature.description.split("\n"); <div className={styles.featureDescription}> - {feature.description.split("\n").map((line, i) => ( + {lines.map((line, i) => ( <React.Fragment key={i}> {line} - {i < feature.description.split("\n").length - 1 && <br />} + {i < lines.length - 1 && <br />} </React.Fragment> ))} </div>i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/executor.mdx (1)
5-6: 存在未使用的导入。
Image和useBaseUrl被导入但未在文档中使用。如果不需要这些导入,建议移除。--- sidebar_position: 4 --- -import Image from "@theme/ThemedImage"; -import useBaseUrl from "@docusaurus/useBaseUrl"; - # 执行器 (Executor)src/components/HomepageFirstScreen/styles.module.scss (3)
31-53: GPU 优化和 SVG 性能优化实现合理
will-change、transform: translateZ(0)和backface-visibility: hidden用于启用 GPU 加速。但需要确认背景 SVG 的复杂度——如果 SVG 较简单,这些优化可能不必要。同时,建议验证shape-rendering: optimizeSpeed在不同浏览器中的表现。考虑添加注释说明为什么需要这些 GPU 优化(例如:SVG 动画性能原因)。
37-37: 中文注释需要考虑本地化第 37 行的注释
// 临时背景色用于测试表明这是临时代码。建议:
- 将背景色值提取为 CSS 变量
- 移除"临时"注释或用 TODO 替代
- 确保此颜色值是最终设计
这是临时颜色还是最终设计色?如果是临时的,建议创建一个 issue 追踪。
Also applies to: 37-37
55-84: CSS 变量系统迁移完整且一致所有颜色值已系统性地替换为 CSS 变量(如
--oomol-primary、--oomol-text-primary、--oomol-bg-elevated等)。这支持了设计系统的令牌化方案。几点验证建议:
- 确保
custom.scss中定义了所有使用的 CSS 变量- 检查色彩对比度是否满足 WCAG 可访问性标准(特别是文本颜色)
.go-to-hub类使用了!important标志(行 427-429),这可能表明存在样式冲突建议移除
.go-to-hub中的!important标志,改为调整选择器特异性或 CSS 顺序来解决冲突。Also applies to: 401-436
i18n/zh-CN/code.json (2)
744-749: 代码示例翻译中的多行字符串处理第 744-748 行包含 Python 和 TypeScript 代码示例。代码块被转义为 JSON 字符串,包含实际的代码注释(中英文混合)。
几点观察:
- Python 示例的注释是中文的("融合 API 基础地址"等),这可能导致编码问题
- 代码中的变量名和 API 路由保持了英文,这是正确的
- 需要确保这些代码示例在前端渲染时正确处理换行符和缩进
建议验证代码块在 UI 中的显示效果,特别是缩进和行数。
考虑将长代码块分解为多个行,或使用专门的代码块组件来处理格式化。当前的 JSON 转义方式可能在渲染时造成可读性问题。
1-1: 整体国际化结构和最佳实践此文件已扩展为包含完整产品生态的国际化翻译,结构化程度高。建议:
- 建立翻译键命名规范文档,确保新增键遵循一致的命名模式
- 实施自动化检查:检测重复键、缺失键、未使用的键
- 为翻译贡献者建立审核流程,确保术语一致性
- 定期同步与英文版本 (i18n/en/code.json) 的新增内容
建议创建一个 i18n 键映射文档或前端组件映射表,追踪每个键的使用位置。这有助于防止重复和孤立的翻译键。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (63)
package-lock.jsonis excluded by!**/package-lock.jsonstatic/img/cases-1.svgis excluded by!**/*.svgstatic/img/cases-2.svgis excluded by!**/*.svgstatic/img/community-discord.svgis excluded by!**/*.svgstatic/img/community-discussion.svgis excluded by!**/*.svgstatic/img/community-github.svgis excluded by!**/*.svgstatic/img/community-we-com.svgis excluded by!**/*.svgstatic/img/community-x.svgis excluded by!**/*.svgstatic/img/community-youtube.svgis excluded by!**/*.svgstatic/img/create-workflow.svgis excluded by!**/*.svgstatic/img/discord_line.svgis excluded by!**/*.svgstatic/img/feature_1.pngis excluded by!**/*.pngstatic/img/feature_3.jpgis excluded by!**/*.jpgstatic/img/interactive.svgis excluded by!**/*.svgstatic/img/logo-en-dark.svgis excluded by!**/*.svgstatic/img/logo-en-light.svgis excluded by!**/*.svgstatic/img/logo-symbol.svgis excluded by!**/*.svgstatic/img/logo-zh-dark.svgis excluded by!**/*.svgstatic/img/logo-zh-light.svgis excluded by!**/*.svgstatic/img/logo.svgis excluded by!**/*.svgstatic/img/logo_black.svgis excluded by!**/*.svgstatic/img/macos-apple-silicon.svgis excluded by!**/*.svgstatic/img/macos-intel.svgis excluded by!**/*.svgstatic/img/pages/features/feature-1.svgis excluded by!**/*.svgstatic/img/pages/features/feature-2.svgis excluded by!**/*.svgstatic/img/pages/features/feature-3.svgis excluded by!**/*.svgstatic/img/pages/features/feature-4.svgis excluded by!**/*.svgstatic/img/pages/features/feature-5.svgis excluded by!**/*.svgstatic/img/pages/features/feature-6.svgis excluded by!**/*.svgstatic/img/pages/features/feature-7.svgis excluded by!**/*.svgstatic/img/pages/headless/docker-deploy.svgis excluded by!**/*.svgstatic/img/pages/headless/package-management.svgis excluded by!**/*.svgstatic/img/pages/headless/remote-access.svgis excluded by!**/*.svgstatic/img/pages/home/code-block-dark.pngis excluded by!**/*.pngstatic/img/pages/home/code-block-light.pngis excluded by!**/*.pngstatic/img/pages/home/community-detail-dark.pngis excluded by!**/*.pngstatic/img/pages/home/community-detail-light.pngis excluded by!**/*.pngstatic/img/pages/home/community-home-dark.pngis excluded by!**/*.pngstatic/img/pages/home/community-home-light.pngis excluded by!**/*.pngstatic/img/pages/home/connect-dark.pngis excluded by!**/*.pngstatic/img/pages/home/connect-light.pngis excluded by!**/*.pngstatic/img/pages/home/copilot-highlight.svgis excluded by!**/*.svgstatic/img/pages/home/copilot.svgis excluded by!**/*.svgstatic/img/pages/home/grok-dark-highlight.svgis excluded by!**/*.svgstatic/img/pages/home/hero-dark.pngis excluded by!**/*.pngstatic/img/pages/home/hero-light.pngis excluded by!**/*.pngstatic/img/pages/home/openai-dark-highlight.svgis excluded by!**/*.svgstatic/img/pages/home/ovm-dark.pngis excluded by!**/*.pngstatic/img/pages/home/ovm-light.pngis excluded by!**/*.pngstatic/img/pages/home/publish-dark.pngis excluded by!**/*.pngstatic/img/pages/home/publish-light.pngis excluded by!**/*.pngstatic/img/pages/home/share.pngis excluded by!**/*.pngstatic/img/pages/home/siliconcloud-highlight.svgis excluded by!**/*.svgstatic/img/pages/home/siliconcloud.svgis excluded by!**/*.svgstatic/img/pricing.svgis excluded by!**/*.svgstatic/img/scenes/chat.svgis excluded by!**/*.svgstatic/img/scenes/data.svgis excluded by!**/*.svgstatic/img/scenes/epub.svgis excluded by!**/*.svgstatic/img/scenes/magic.svgis excluded by!**/*.svgstatic/img/scenes/pdf.svgis excluded by!**/*.svgstatic/img/scenes/zip.svgis excluded by!**/*.svgstatic/img/screen1.svgis excluded by!**/*.svgstatic/img/tech-background.svgis excluded by!**/*.svg
📒 Files selected for processing (107)
.claude/settings.local.json(1 hunks)babel.config.js(0 hunks)blog/2025-07-05-Export-Workflow-as-Image/index.mdx(1 hunks)docs/advanced-guide/universal-block-settings.mdx(2 hunks)docs/concepts/executor.mdx(1 hunks)docs/faq.mdx(1 hunks)docs/workflow-engine/principle.mdx(1 hunks)docusaurus.config.js(7 hunks)eslint.config.mjs(1 hunks)i18n/en/code.json(11 hunks)i18n/en/docusaurus-theme-classic/footer.json(1 hunks)i18n/en/docusaurus-theme-classic/navbar.json(2 hunks)i18n/zh-CN/code.json(11 hunks)i18n/zh-CN/docusaurus-plugin-content-blog/2025-07-05-Export-Workflow-as-Image/index.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-blog/2025-07-14-Convert-scanned-PDF-books-to-EPUB/index.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced-guide/universal-block-settings.mdx(2 hunks)i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/executor.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-docs/current/faq.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-docs/current/workflow-engine/principle.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/downloads/index.tsx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/privacy/_privacy.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/privacy/index.tsx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/privacy/styles.module.scss(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/support/index.tsx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/support/styles.module.scss(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/terms/_terms.mdx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/terms/index.tsx(1 hunks)i18n/zh-CN/docusaurus-plugin-content-pages/terms/styles.module.scss(1 hunks)i18n/zh-CN/docusaurus-theme-classic/footer.json(1 hunks)i18n/zh-CN/docusaurus-theme-classic/navbar.json(1 hunks)package.json(2 hunks)src/components/AssetBlock/styles.module.scss(4 hunks)src/components/Button/LinkBtn.module.scss(0 hunks)src/components/Button/LinkBtn.tsx(0 hunks)src/components/Button/index.tsx(0 hunks)src/components/Button/styles.module.scss(0 hunks)src/components/ComingSoon/index.tsx(0 hunks)src/components/ComingSoon/styles.module.scss(0 hunks)src/components/CyclicTypewriterText/index.tsx(0 hunks)src/components/CyclicTypewriterText/styles.module.scss(0 hunks)src/components/DownloadButton/index.tsx(4 hunks)src/components/DownloadButton/styles.module.scss(3 hunks)src/components/FeatureBlockList/FeatureBlockList.module.scss(1 hunks)src/components/GetStartedPrompt/index.tsx(1 hunks)src/components/GetStartedPrompt/styles.module.scss(2 hunks)src/components/HomePageBuiltInLLM/CodeExample.tsx(1 hunks)src/components/HomePageBuiltInLLM/index.tsx(4 hunks)src/components/HomePageBuiltInLLM/styles.module.scss(2 hunks)src/components/HomePagePricing/index.tsx(2 hunks)src/components/HomePagePricing/style.module.scss(7 hunks)src/components/HomepageCommunityShare/index.tsx(1 hunks)src/components/HomepageCommunityShare/styles.module.scss(1 hunks)src/components/HomepageCoreFeatures/index.tsx(1 hunks)src/components/HomepageCoreFeatures/styles.module.scss(1 hunks)src/components/HomepageCreate/index.tsx(0 hunks)src/components/HomepageCreate/styles.module.scss(0 hunks)src/components/HomepageCreateScenes/index.tsx(0 hunks)src/components/HomepageCreateScenes/styles.module.scss(0 hunks)src/components/HomepageDownloads/index.tsx(2 hunks)src/components/HomepageDownloads/styles.module.scss(3 hunks)src/components/HomepageFeatures/index.tsx(0 hunks)src/components/HomepageFeatures/styles.module.scss(0 hunks)src/components/HomepageFirstScreen/GridBackground.tsx(1 hunks)src/components/HomepageFirstScreen/index.tsx(2 hunks)src/components/HomepageFirstScreen/styles.module.scss(8 hunks)src/components/HomepageGuide/index.tsx(0 hunks)src/components/HomepageGuide/styles.module.scss(0 hunks)src/components/HomepageLifecycle/index.tsx(1 hunks)src/components/HomepageLifecycle/styles.module.scss(1 hunks)src/components/HomepagePdfCraftShowcase/index.tsx(1 hunks)src/components/HomepagePdfCraftShowcase/styles.module.scss(1 hunks)src/components/HomepageProductComparison/index.tsx(1 hunks)src/components/HomepageProductComparison/styles.module.scss(1 hunks)src/components/HomepageProducts/index.tsx(0 hunks)src/components/HomepageProducts/styles.module.scss(0 hunks)src/components/HomepageScenes/FirstSceneCard.tsx(0 hunks)src/components/HomepageScenes/index.tsx(0 hunks)src/components/HomepageScenes/styles.module.scss(0 hunks)src/components/HomepageStarter/index.tsx(1 hunks)src/components/HomepageVideo/index.tsx(0 hunks)src/components/HomepageVideo/styles.module.scss(0 hunks)src/components/Popover/index.tsx(2 hunks)src/components/ResponsiveImage/index.tsx(0 hunks)src/components/Spin/Spin.tsx(4 hunks)src/components/StudioPdfCraftCase/index.tsx(1 hunks)src/components/StudioPdfCraftCase/styles.module.scss(1 hunks)src/components/TypewriterText/index.tsx(0 hunks)src/components/TypewriterText/styles.module.scss(0 hunks)src/components/magic-ui/AuroraText.tsx(2 hunks)src/components/magic-ui/GradualSpacing.tsx(0 hunks)src/components/ui/button.tsx(1 hunks)src/components/ui/card.tsx(1 hunks)src/components/ui/dropdown-menu.tsx(1 hunks)src/components/ui/popover.tsx(1 hunks)src/css/custom.scss(9 hunks)src/css/design-tokens.scss(1 hunks)src/lib/utils.ts(1 hunks)src/pages/about-us/index.tsx(0 hunks)src/pages/about-us/styles.module.scss(0 hunks)src/pages/brand-assets/index.tsx(3 hunks)src/pages/brand-assets/styles.module.scss(9 hunks)src/pages/chat/index.tsx(1 hunks)src/pages/chat/styles.module.scss(1 hunks)src/pages/cloud/index.tsx(1 hunks)src/pages/cloud/styles.module.scss(1 hunks)src/pages/contact-us/index.tsx(6 hunks)src/pages/contact-us/styles.module.scss(11 hunks)
⛔ Files not processed due to max files limit (30)
- src/pages/downloads/index.tsx
- src/pages/downloads/styles.module.scss
- src/pages/headless/index.tsx
- src/pages/headless/styles.module.scss
- src/pages/hub/index.tsx
- src/pages/hub/styles.module.scss
- src/pages/index.tsx
- src/pages/oomol-api/index.tsx
- src/pages/oomol-api/styles.module.scss
- src/pages/oomol-cloud/index.tsx
- src/pages/oomol-cloud/styles.module.scss
- src/pages/oomol-studio/index.tsx
- src/pages/oomol-studio/styles.module.scss
- src/pages/open-source/index.tsx
- src/pages/open-source/styles.module.scss
- src/pages/pricing/index.tsx
- src/pages/pricing/styles.module.scss
- src/pages/privacy/_privacy.mdx
- src/pages/privacy/index.tsx
- src/pages/privacy/privacy.mdx
- src/pages/studio/index.tsx
- src/pages/studio/styles.module.scss
- src/pages/support/index.tsx
- src/pages/support/styles.module.scss
- src/pages/system-status/index.tsx
- src/pages/system-status/styles.module.scss
- src/pages/terms/_terms.mdx
- src/pages/terms/index.tsx
- src/pages/terms/terms.mdx
- src/theme/Footer/index.tsx
💤 Files with no reviewable changes (30)
- babel.config.js
- src/components/Button/index.tsx
- src/components/HomepageVideo/index.tsx
- src/components/ResponsiveImage/index.tsx
- src/components/HomepageGuide/index.tsx
- src/components/Button/styles.module.scss
- src/components/HomepageCreateScenes/index.tsx
- src/components/HomepageCreate/styles.module.scss
- src/components/HomepageFeatures/index.tsx
- src/pages/about-us/styles.module.scss
- src/components/TypewriterText/index.tsx
- src/components/CyclicTypewriterText/index.tsx
- src/pages/about-us/index.tsx
- src/components/HomepageProducts/styles.module.scss
- src/components/magic-ui/GradualSpacing.tsx
- src/components/HomepageScenes/FirstSceneCard.tsx
- src/components/ComingSoon/styles.module.scss
- src/components/ComingSoon/index.tsx
- src/components/CyclicTypewriterText/styles.module.scss
- src/components/HomepageScenes/styles.module.scss
- src/components/HomepageCreateScenes/styles.module.scss
- src/components/HomepageGuide/styles.module.scss
- src/components/HomepageProducts/index.tsx
- src/components/Button/LinkBtn.tsx
- src/components/TypewriterText/styles.module.scss
- src/components/Button/LinkBtn.module.scss
- src/components/HomepageCreate/index.tsx
- src/components/HomepageScenes/index.tsx
- src/components/HomepageVideo/styles.module.scss
- src/components/HomepageFeatures/styles.module.scss
🧰 Additional context used
🧬 Code graph analysis (15)
i18n/zh-CN/docusaurus-plugin-content-pages/downloads/index.tsx (3)
src/download_url.ts (1)
DownloadUrl(1-29)src/pages/downloads/index.tsx (1)
Downloads(77-143)src/lib/utils.ts (1)
downloadStable(14-28)
i18n/zh-CN/docusaurus-plugin-content-pages/support/index.tsx (1)
src/components/ui/button.tsx (1)
Button(57-57)
i18n/zh-CN/docusaurus-plugin-content-pages/terms/index.tsx (1)
i18n/zh-CN/docusaurus-plugin-content-pages/privacy/index.tsx (1)
Index(13-25)
i18n/zh-CN/docusaurus-plugin-content-pages/privacy/index.tsx (1)
i18n/zh-CN/docusaurus-plugin-content-pages/terms/index.tsx (1)
Index(12-24)
src/components/ui/button.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/pages/contact-us/index.tsx (2)
src/components/ui/card.tsx (3)
Card(76-76)CardHeader(76-76)CardTitle(76-76)src/components/Popover/index.tsx (1)
Popover(16-60)
src/components/ui/card.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/DownloadButton/index.tsx (3)
src/components/ui/button.tsx (1)
Button(57-57)src/lib/utils.ts (1)
downloadStable(14-28)src/download_url.ts (1)
DownloadUrl(1-29)
src/components/ui/popover.tsx (2)
src/components/Popover/index.tsx (1)
Popover(16-60)src/lib/utils.ts (1)
cn(4-6)
src/pages/cloud/index.tsx (1)
src/components/GetStartedPrompt/index.tsx (1)
GetStartedPrompt(6-29)
src/components/HomepageFirstScreen/index.tsx (3)
src/components/HomepageFirstScreen/GridBackground.tsx (1)
GridBackground(8-370)src/components/magic-ui/AuroraText.tsx (1)
AuroraText(59-59)src/components/DownloadButton/index.tsx (1)
DownloadButton(57-127)
src/pages/chat/index.tsx (1)
src/components/GetStartedPrompt/index.tsx (1)
GetStartedPrompt(6-29)
src/components/HomePageBuiltInLLM/index.tsx (1)
src/components/HomePageBuiltInLLM/CodeExample.tsx (1)
CodeExample(91-107)
src/components/ui/dropdown-menu.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/Popover/index.tsx (1)
src/components/ui/popover.tsx (2)
PopoverTrigger(31-31)PopoverContent(31-31)
🪛 Biome (2.1.2)
i18n/en/code.json
[error] 1633-1633: The key STUDIO.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
[error] 2047-2047: The key HEADLESS.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
i18n/zh-CN/code.json
[error] 1552-1553: The key STUDIO.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
🪛 LanguageTool
i18n/zh-CN/docusaurus-plugin-content-blog/2025-07-14-Convert-scanned-PDF-books-to-EPUB/index.mdx
[uncategorized] ~37-~37: "间"不能与“模式”搭配,请换量词
Context: ...在自己喜欢的 EPUB 阅读器上,以最适合自己的方式浏览这些内容:调整字号、切换夜间模式,甚至可以让 AI 朗读。 [pdf-craft](https://git...
(wa5)
[uncategorized] ~39-~39: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:正确"地"阅读
Context: ...DF 文件中的文本内容、页眉页脚、参考注释等。能够保持跨页内容的连贯性,还原正确的阅读顺序。此外,它还会使用 LLM 来构建完整的 EPUB 目录结构。 在 o...
(wb4)
i18n/zh-CN/docusaurus-plugin-content-pages/privacy/_privacy.mdx
[uncategorized] ~154-~154: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:合理"地"预防
Context: ...控系统 - 建立安全事件响应计划 ### 4.3. 安全限制 虽然我们采取合理的预防措施,但没有任何通过互联网传输数据或电子存储的方法是 100% 安全的。我们...
(wb4)
[uncategorized] ~251-~251: 数量词修饰并列短语,可能产生歧义
Context: .... 第三方链接和服务 ### 8.1. 第三方网站 Oomol 可能包含指向第三方网站、插件或服务的链接。我们不对这些第三方的隐私做法或内容负责。 ### 8.2. ...
(s5)
i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/executor.mdx
[uncategorized] ~164-~164: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:正确"地"执行
Context: ...码启用独立进程执行** 4. 遵循 OOMOL 提供的代码模板以确保正确的执行器集成: - [Node.js 模板](/zh-CN/docs/wor...
(wb4)
i18n/zh-CN/docusaurus-plugin-content-pages/terms/_terms.mdx
[uncategorized] ~182-~182: 能愿动词不能成为‘把’字句、‘被’字句的谓语动词。应该是:"可被……认定"。
Context: ...于使用服务的完整协议。 12.3. 可分割性:如果本服务条款的任何条款被认定为无效或不可执行,该条款应在必要范围内进行修改以使其有效,或在无法修改的情况下予以删除,其余...
(wa3)
i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced-guide/universal-block-settings.mdx
[grammar] ~200-~200: Ensure spelling is correct
Context: ...执行器由 oomol 团队内部实现,可支持 Python 以及 Node.js 运行环境,因此有对应的两个可选项。 Python 执行器: - 在 Python 运行时环境中执行 Python...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cloudflare Pages
.claude/settings.local.json
Outdated
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(npm run build:*)", | ||
| "Bash(npm run start:*)", | ||
| "Bash(lsof:*)", | ||
| "Bash(xargs kill:*)", | ||
| "Bash(npm run start:en:*)", | ||
| "Bash(npm run)", | ||
| "Bash(npm run lint:*)", | ||
| "Bash(npm run type-check:*)", | ||
| "Bash(npx tsc:*)", | ||
| "Bash(npm run typecheck:*)", | ||
| "Bash(cat:*)", | ||
| "Bash(i18n/zh-CN/code.json)", | ||
| "Bash(node -e:*)", | ||
| "Bash(find:*)", | ||
| "Bash(rm /Users/wushuang/code/oomol.com/src/components/ui/accordion.tsx /Users/wushuang/code/oomol.com/src/components/ui/popover.tsx /Users/wushuang/code/oomol.com/src/components/ui/tooltip.tsx /Users/wushuang/code/oomol.com/src/components/ui/dialog.tsx /Users/wushuang/code/oomol.com/src/components/ui/table.tsx)", | ||
| "Bash(rm /Users/wushuang/code/oomol.com/src/components/magic-ui/GradualSpacing.tsx)", | ||
| "Bash(rm -rf /Users/wushuang/code/oomol.com/src/components/HomepageChatAgent /Users/wushuang/code/oomol.com/src/components/HomepageCommunityShare /Users/wushuang/code/oomol.com/src/components/HomepageCoreTech /Users/wushuang/code/oomol.com/src/components/HomepageEcosystem /Users/wushuang/code/oomol.com/src/components/HomepageGuide /Users/wushuang/code/oomol.com/src/components/HomepageValueProps /Users/wushuang/code/oomol.com/src/components/HomepageVideo /Users/wushuang/code/oomol.com/src/components/HomepageWhyOomol /Users/wushuang/code/oomol.com/src/components/ResponsiveImage /Users/wushuang/code/oomol.com/src/components/TypewriterText)", | ||
| "Bash(git checkout HEAD -- /Users/wushuang/code/oomol.com/src/components/ui/popover.tsx)", | ||
| "Bash(rm -rf /Users/wushuang/code/oomol.com/.docusaurus /Users/wushuang/code/oomol.com/build /Users/wushuang/code/oomol.com/node_modules/.cache)", | ||
| "Bash(rm -rf /Users/wushuang/code/oomol.com/src/components/HomepageFeatures)", | ||
| "Bash(done)", | ||
| "Bash(xargs -I {} bash -c 'echo \"\"\"\"=== $(basename {}) ===\"\"\"\" && find {} -type f \\( -name \"\"\"\"*.tsx\"\"\"\" -o -name \"\"\"\"*.ts\"\"\"\" \\) 2>/dev/null')", | ||
| "Bash(mkdir -p /Users/wushuang/code/oomol.com/src/components/HomepageCoreFeatures)", | ||
| "Bash(npm run build:zh-CN)", | ||
| "Bash(npm run start:zh-cn)", | ||
| "Bash(kill 69546 96927)", | ||
| "Bash(node --version)", | ||
| "Bash(npm --version)", | ||
| "Bash(npm run clear)", | ||
| "Bash(kill 74240 78577)", | ||
| "Bash(rm -rf .docusaurus build node_modules/.cache)", | ||
| "Bash(kill 7420 86602)", | ||
| "Bash(tree -L 3 /Users/wushuang/code/oomol.com/src/pages/terms /Users/wushuang/code/oomol.com/i18n/zh-CN/docusaurus-plugin-content-pages/terms)", | ||
| "Bash(mkdir -p /Users/wushuang/code/oomol.com/src/components/HomepageCommunityShare)", | ||
| "Bash(npm run build-unocss)", | ||
| "Bash(kill 88270 88442)", | ||
| "Bash(npm install @radix-ui/themes)", | ||
| "Bash(mkdir -p /Users/wushuang/code/oomol.com/i18n/zh-CN/docusaurus-plugin-content-pages/support)", | ||
| "Bash(cp /Users/wushuang/code/oomol.com/src/pages/support/styles.module.scss /Users/wushuang/code/oomol.com/i18n/zh-CN/docusaurus-plugin-content-pages/support/styles.module.scss)", | ||
| "Bash(kill 25847 26430)" | ||
| ], | ||
| "deny": [], | ||
| "ask": [] | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if .gitignore exists and its contents
if [ -f .gitignore ]; then
echo "=== .gitignore contents ==="
cat .gitignore | grep -i "claude\|\.local"
else
echo "No .gitignore found"
fiRepository: oomol/oomol.com
Length of output: 158
🏁 Script executed:
# Check .claude directory structure and what exists
echo "=== .claude directory contents ==="
ls -la .claude/ 2>/dev/null || echo ".claude directory does not exist"Repository: oomol/oomol.com
Length of output: 276
🏁 Script executed:
# Check git status to see if this file is tracked
echo "=== Git status for .claude/settings.local.json ==="
git status .claude/settings.local.json 2>/dev/null || echo "File not in git"
# Check if file is staged
echo "=== Check if file is staged in this commit ==="
git diff --cached --name-only 2>/dev/null | grep "\.claude"Repository: oomol/oomol.com
Length of output: 222
🏁 Script executed:
# Check if there's a .claude/settings.json (team-shared version)
echo "=== Checking for .claude/settings.json ==="
ls -la .claude/settings.json 2>/dev/null || echo "No team settings.json found"Repository: oomol/oomol.com
Length of output: 129
🌐 Web query:
Claude Code settings.local.json version control gitignore
💡 Result:
Short answer: Claude Code creates a project-local settings file at .claude/settings.local.json and will configure git to ignore it. If you need to add/verify this yourself, add this line to your .gitignore:
.claude/settings.local.json
If the file was already committed, remove it from the repo while keeping it locally:
git rm --cached .claude/settings.local.json
git commit -m "Remove local Claude settings from repo"
Sources: Claude Code docs (settings files). [1][2]
.claude/settings.local.json should not be committed to version control and must be removed from git history.
Per Claude Code documentation, .claude/settings.local.json is a personal local settings file that should be automatically ignored by git. This file is currently committed and not excluded by .gitignore.
Issues:
- Missing
.gitignoreentry: Add.claude/settings.local.jsonto.gitignore. - Machine-specific absolute paths: The file contains hardcoded paths like
/Users/wushuang/code/oomol.com/src/...(lines 18–22, 26, 36–37, 41–42) that do not work on other developers' machines. - Temporary development commands: Includes process kill commands with specific PIDs (lines 29, 33, 35, 39, 43) and file deletions that have no place in a shared repository.
Remove the file from git history using git rm --cached .claude/settings.local.json and update .gitignore before merging this PR.
🤖 Prompt for AI Agents
In .claude/settings.local.json (lines 1–48) the file is a personal local
settings file containing machine-specific absolute paths, ephemeral kill/PID
entries, and other temporary commands and must be removed from version control:
add .claude/settings.local.json to .gitignore, remove the tracked file with git
rm --cached .claude/settings.local.json and commit the removal, replace the
committed file in the repo (if you need a repo-safe example) with a sanitized
template that omits absolute paths and PIDs (or move the real file to each
developer’s local .claude/), and if you need to purge it from history run a
history-rewrite tool (git filter-repo or BFG) to remove the file from previous
commits before force-pushing the cleaned branch.
eslint.config.mjs
Outdated
| '*.config.js', | ||
| '*.config.mjs', |
There was a problem hiding this comment.
配置文件自我排除可能导致配置错误未被检测。
第 14-15 行排除了所有 *.config.js 和 *.config.mjs 文件,这意味着当前的 eslint.config.mjs 本身不会被 lint。如果配置文件中存在代码质量问题,将无法被检测到。
如果希望对配置文件也进行 lint,可以更精确地指定忽略模式:
'static/**',
- '*.config.js',
- '*.config.mjs',
+ 'webpack.config.js',
+ 'postcss.config.js',
+ 'tailwind.config.js',
'src/styles/uno.css',🤖 Prompt for AI Agents
In eslint.config.mjs around lines 14-15, the patterns '*.config.js' and
'*.config.mjs' globally exclude config files (including eslint.config.mjs) from
linting; update the ignore patterns to stop excluding your ESLint config itself
— either remove these two patterns or narrow them to specific directories/files
you truly want ignored (e.g., only third-party or legacy config folders), and
ensure eslint.config.mjs is included for linting so configuration issues are
detected.
| ...js.configs.recommended.rules, | ||
| ...tseslint.configs.recommended.rules, | ||
| ...react.configs.recommended.rules, | ||
| ...reactHooks.configs.recommended.rules, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if eslint.config.mjs exists and read the relevant section
cat -n eslint.config.mjs | head -100Repository: oomol/oomol.com
Length of output: 3555
🏁 Script executed:
# Check the imports at the top of the file
head -50 eslint.config.mjsRepository: oomol/oomol.com
Length of output: 1406
🏁 Script executed:
# Verify that the config objects have the expected properties
rg "js\.configs|tseslint\.configs|react\.configs|reactHooks\.configs" -A 2 -B 2 eslint.config.mjsRepository: oomol/oomol.com
Length of output: 270
🏁 Script executed:
# Check package.json for lint script
cat package.json | grep -A 5 '"scripts"'Repository: oomol/oomol.com
Length of output: 309
🏁 Script executed:
# Check the actual TypeScript ESLint plugin export structure
# by looking at node_modules if available
ls -la node_modules/@typescript-eslint/ 2>/dev/null | head -20 || echo "node_modules not available, checking package docs"Repository: oomol/oomol.com
Length of output: 41
🏁 Script executed:
# Search for how tseslint is actually used elsewhere in the config
rg "tseslint\." eslint.config.mjsRepository: oomol/oomol.com
Length of output: 103
🌐 Web query:
@typescript-eslint/eslint-plugin flat config recommended rules exports
💡 Result:
Use the plugin's shared flat configs. Example eslint.config.mjs enabling the recommended rules (and the typed rules variant if you want type-aware rules):
eslint.config.mjs
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended, // basic recommended rules
// tseslint.configs.recommendedTypeChecked // add this for rules that require type information
);
Docs: typescript-eslint shared configs and package usage. [1][2]
Sources
[1] typescript-eslint package / usage (flat config).
[2] Shared configs (recommended, recommended-type-checked) — typescript-eslint.
The recommended rules configuration contains critical errors that will cause runtime failures.
Line 83 has two issues:
- Import path:
@typescript-eslint/eslint-pluginshould betypescript-eslint - Property access:
tseslint.configs.recommended.rulesis incorrect; it should betseslint.configs.recommendedwithout.rules
The flat config pattern for TypeScript ESLint is:
import tseslint from 'typescript-eslint';
// Then use directly:
tseslint.configs.recommendedThe same issue applies to js.configs.recommended.rules and react.configs.recommended.rules on lines 82 and 84—remove the .rules suffix. The config objects themselves are the rule sets, not nested under a .rules property.
🤖 Prompt for AI Agents
In eslint.config.mjs around lines 82 to 85, the config spreads are using the
wrong property and an incorrect import path for TypeScript ESLint: remove the
trailing ".rules" from js.configs.recommended, tseslint.configs.recommended,
react.configs.recommended and reactHooks.configs.recommended so you spread the
config objects themselves (e.g., ...js.configs.recommended), and ensure the
TypeScript ESLint import uses 'typescript-eslint' (not
'@typescript-eslint/eslint-plugin') so you reference
tseslint.configs.recommended rather than tseslint.configs.recommended.rules.
…proved functionality and styling
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (8)
i18n/en/code.json (2)
1632-1634: 重复的 STUDIO.hero.description 键定义需要删除键
STUDIO.hero.description在第 1632-1634 行和第 2349-2351 行中定义了两次。在 JSON 中,重复的键定义会导致第一个定义被忽略,只有最后一个定义生效。两个定义的内容不同:
- 第 1632 行:
"Visual Workflow + Full Code with VSCode, Build and Share AI Tools Fast"- 第 2349 行:
"Local development environment for developers, VSCode-based visual programming tool with FRP protocol for P2P computing power sharing"第二个定义更加详细,应该保留第二个定义,删除第一个重复的定义。
删除以下重复定义:
- "STUDIO.hero.description": { - "message": "Visual Workflow + Full Code with VSCode, Build and Share AI Tools Fast" - },Also applies to: 2349-2351
2049-2051: 重复的 HEADLESS.hero.description 键定义需要删除键
HEADLESS.hero.description在第 2049-2051 行和第 2352-2354 行中定义了两次。只有最后一个定义会生效,前面的定义被忽略。
- 第 2049 行:
"Docker containerized deployment to easily run OOMOL applications on servers and NAS"- 第 2352 行:
"Containerized deployment solution, packaging workflows as standalone Docker images for cloud servers, private clouds and K8s clusters"第二个定义更加完整,应该保留第二个定义,删除第一个重复的。
删除以下重复定义:
- "HEADLESS.hero.description": { - "message": "Docker containerized deployment to easily run OOMOL applications on servers and NAS" - },Also applies to: 2352-2354
i18n/zh-CN/code.json (1)
1184-1186: 重复的 STUDIO.hero.description 键定义需要删除键
STUDIO.hero.description在第 1184-1186 行和第 2352-2354 行中定义了两次。JSON 规范中,重复的键只有最后一个定义会生效,前面的定义会被忽略。
- 第 1184 行:
"可视化工作流 + VSCode 全代码,快速构建并分享 AI 工具"- 第 2352 行:
"面向开发者的本地开发环境,基于 VSCode 的可视化编程工具,结合 FRP 协议实现 P2P 算力共享"第二个定义更详细完整,应保留第二个定义,删除第一个重复的定义。
删除以下重复定义:
- "STUDIO.hero.description": { - "message": "可视化工作流 + VSCode 全代码,快速构建并分享 AI 工具" - },Also applies to: 2352-2354
eslint.config.mjs (3)
2-2: TypeScript ESLint 的导入路径不正确。当前导入路径
@typescript-eslint/eslint-plugin在 flat config 模式下不正确。应该从typescript-eslint包导入。这会导致后续使用tseslint.configs.recommended时出现运行时错误。应用此修复:
-import tseslint from "@typescript-eslint/eslint-plugin"; +import tseslint from "typescript-eslint";
14-15: 配置文件排除模式过于宽泛。当前的排除模式会导致
eslint.config.mjs自身不被检查。如果配置文件中存在代码质量问题,将无法被检测到。建议更精确地指定需要忽略的配置文件:
- '*.config.js', - '*.config.mjs', + 'webpack.config.js', + 'postcss.config.js', + 'tailwind.config.js',
82-85: 规则配置的属性访问错误。第 82-85 行错误地使用了
.rules后缀。在 ESLint flat config 中,配置对象本身就是规则集,不应该访问嵌套的.rules属性。这会导致运行时错误,因为这些配置对象没有.rules属性。应用此修复:
- ...js.configs.recommended.rules, - ...tseslint.configs.recommended.rules, - ...react.configs.recommended.rules, - ...reactHooks.configs.recommended.rules, + ...js.configs.recommended, + ...tseslint.configs.recommended, + ...react.configs.recommended, + ...reactHooks.configs.recommended,src/css/design-tokens.scss (2)
26-32: 文本颜色对比度注释与实际值不符。代码注释声称
--oomol-text-quaternary和--oomol-text-disabled(均为rgba(0, 0, 0, 0.45))符合 WCAG AA 标准(4.5:1),但实际上:
rgba(0, 0, 0, 0.45)在白色背景上的对比度约为 3.0:1- WCAG AA 要求正常文本的对比度至少为 4.5:1
- 大文本(≥18pt 或 ≥14pt 粗体)的对比度至少为 3:1
建议:
- 如果这些令牌仅用于大文本或装饰性元素,请更新注释说明其用途限制
- 如果用于正常文本,需要将 alpha 值提高到至少 0.65 以满足 WCAG AA 标准
- 或者添加单独的令牌用于装饰性用途
- --oomol-text-quaternary: rgba( - 0, - 0, - 0, - 0.45 - ); /* WCAG AA: 4.5:1 minimum for text */ - --oomol-text-disabled: rgba(0, 0, 0, 0.45); /* WCAG AA compliant */ + --oomol-text-quaternary: rgba(0, 0, 0, 0.45); /* For large text or decorative elements only (3:1 contrast) */ + --oomol-text-disabled: rgba(0, 0, 0, 0.45); /* For large text or decorative elements only (3:1 contrast) */或者提高对比度以支持正常文本:
- --oomol-text-quaternary: rgba( - 0, - 0, - 0, - 0.45 - ); /* WCAG AA: 4.5:1 minimum for text */ - --oomol-text-disabled: rgba(0, 0, 0, 0.45); /* WCAG AA compliant */ + --oomol-text-quaternary: rgba(0, 0, 0, 0.65); /* WCAG AA compliant for normal text (4.5:1) */ + --oomol-text-disabled: rgba(0, 0, 0, 0.65); /* WCAG AA compliant for normal text (4.5:1) */
218-225: 暗色模式文本对比度仍未达到 WCAG AA 标准。虽然代码注释表明对比度已从之前的值改进(tertiary 从 0.50 到 0.55,quaternary 从 0.25 到 0.45),但
--oomol-text-quaternary和--oomol-text-disabled(均为rgba(255, 255, 255, 0.45))在暗色背景(#141414)上的对比度约为 3.3:1,仍低于 WCAG AA 要求的 4.5:1。建议与浅色模式相同:
- 在注释中明确这些令牌仅用于大文本或装饰性元素
- 或将 alpha 值提高到至少 0.65 以满足正常文本的 WCAG AA 标准
- --oomol-text-quaternary: rgba( - 255, - 255, - 255, - 0.45 - ); /* WCAG AA: improved from 0.25 */ - --oomol-text-disabled: rgba(255, 255, 255, 0.45); /* WCAG AA compliant */ + --oomol-text-quaternary: rgba(255, 255, 255, 0.45); /* For large text or decorative elements only (~3.3:1 contrast) */ + --oomol-text-disabled: rgba(255, 255, 255, 0.45); /* For large text or decorative elements only (~3.3:1 contrast) */或者提高对比度:
- --oomol-text-quaternary: rgba( - 255, - 255, - 255, - 0.45 - ); /* WCAG AA: improved from 0.25 */ - --oomol-text-disabled: rgba(255, 255, 255, 0.45); /* WCAG AA compliant */ + --oomol-text-quaternary: rgba(255, 255, 255, 0.65); /* WCAG AA compliant for normal text (~5:1 contrast) */ + --oomol-text-disabled: rgba(255, 255, 255, 0.65); /* WCAG AA compliant for normal text (~5:1 contrast) */
🧹 Nitpick comments (7)
src/pages/chat/index.tsx (2)
69-94: 优化类型声明与列表 key,可读性与健壮性更好这里的实现是正确的,不过有两点可以作为小优化考虑:
- 显式类型别名
目前 props 使用({ useCase }: { useCase: (typeof useCases)[0] })/({ feature }: { feature: (typeof coreFeatures)[0] }),类型推断没问题,但可读性稍差。可以考虑提取类型别名,让结构更直观,例如:type UseCase = { icon: string; title: string; description: string; }; type CoreFeature = { icon: string; title: string; description: string; features: string[]; }; const useCases: UseCase[] = [/* ... */]; const coreFeatures: CoreFeature[] = [/* ... */]; const UseCaseCard = ({ useCase }: { useCase: UseCase }) => { /* ... */ }; const FeatureCard = ({ feature }: { feature: CoreFeature }) => { /* ... */ };
- features 列表的 key 使用文案本身而非索引
feature.features.map((item, idx) => <li key={idx}>在当前静态数组场景下能工作,但从 React 最佳实践看,更推荐使用稳定标识以避免未来改动时出现不必要的重渲染。这里文案本身是唯一字符串,可直接作为 key:- <ul className={styles.featureList}> - {feature.features.map((item, idx) => ( - <li key={idx}>{item}</li> - ))} - </ul> + <ul className={styles.featureList}> + {feature.features.map((item) => ( + <li key={item}>{item}</li> + ))} + </ul>这两点都属于可选优化,不影响当前功能。
96-178: 内部导航与国际化文案可以进一步统一整体页面结构(Hero + Use Cases + Features + CTA)清晰,DOM 语义也合理;有几点可以考虑优化:
- 内部链接建议使用 Docusaurus 的
Link而非原生<a>
href="/downloads"和href="/docs"是站内路径,在 Docusaurus 中一般推荐用@docusaurus/Link的to属性,这样可以自动处理baseUrl、语言前缀以及避免整页刷新。例如:-import Layout from "../../theme/Layout"; +import Layout from "../../theme/Layout"; +import Link from "@docusaurus/Link"; - <div className={styles.heroCTA}> - <a href="/downloads" className={styles.primaryButton}> - {translate({ message: "CHAT.hero.cta.download" })} - </a> - <a href="/docs" className={styles.secondaryButton}> - {translate({ message: "CHAT.hero.cta.docs" })} - </a> - </div> + <div className={styles.heroCTA}> + <Link to="/downloads" className={styles.primaryButton}> + {translate({ message: "CHAT.hero.cta.download" })} + </Link> + <Link to="/docs" className={styles.secondaryButton}> + {translate({ message: "CHAT.hero.cta.docs" })} + </Link> + </div>
- Hero 标题与部分 stat 文案是否需要国际化(取决于产品定位)
OOMOL Chat一般可以视为产品名,保持英文常见且可以不翻译;MCP通常是缩写/品牌词,也可以不翻译;- 但
AI Agent更偏概念性用语,如果你们希望在中文页面里也完全本地化文案,可以考虑也包一层translate(),例如新增 key:CHAT.hero.statValue3。这些都属于体验与本地化层面的改进点,不影响当前功能正确性。
请在你们项目使用的 Docusaurus 版本文档中再确认一次
@docusaurus/Link的推荐用法及to属性的路径写法是否与上述示例一致。src/components/HomepageFirstScreen/index.tsx (5)
16-30: parseHighlightText 实现合理,之前的 key 问题也已妥善修复现在普通文本部分通过带 key 的
React.Fragment返回,React 列表 key 告警已经不会再出现;按**...**拆分并包一层高亮样式也比较直观、易维护。如果后续文案里可能出现把**当普通字符使用的场景,再考虑调整正则或约定转义即可,当前实现完全可以先稳定使用。
32-38: 可以考虑去掉对 DocusaurusContext 的双重类型断言
useDocusaurusContext()这里用了as unknown as DocusaurusContext & { i18n: { currentLocale: string } },有点重,会掩盖类型变化带来的问题。Docusaurus 的上下文本身就带i18n信息的话,其实可以直接:const { i18n } = useDocusaurusContext();或者只做一次更窄的断言即可,让类型系统多帮一点忙。
39-41: 脚本文案高亮逻辑 OK,如需微优化可用useMemo缓存解析结果
scriptText每次 render 都会重新走translate+parseHighlightText,在当前复杂度和调用频率下没什么问题。如果后面脚本文案变长、解析规则变复杂,可以考虑:const parsedScript = useMemo( () => parseHighlightText(scriptText), [scriptText], ); ... {parsedScript}在不改变行为的前提下,避免重复拆分。
Also applies to: 82-85
50-78: 中英文 slogan 结构目前完全一致,可以视情况去掉 JSX 分支重复
i18n.currentLocale === "zh-CN"已经在className上做了区分,而AuroraText两个分支里 JSX 结构是相同的,只是依赖同一组翻译 key 输出不同语言。若短期内不打算让中英文在结构上分叉,其实可以收敛成一份 JSX,locale 仅通过translate处理文案:<AuroraText className={styles["aurora-slogan"]}> {translate({ message: "HOME.FirstScreen.slogan.line1" })} <br /> {translate({ message: "HOME.FirstScreen.slogan.line2" })} </AuroraText>这样后面改样式或结构时维护成本更低。
93-105: 暗/亮模式截图切换实现正确,可考虑本地化 alt 文案并评估双图加载手动用
colorMode切换hero-dark.png/hero-light.png很直观,配合loading="eager"与fetchPriority="high"对首屏也比较友好。两个小建议供参考:
alt="OOMOL Product Screenshot"可以通过translate本地化一下,和整页 i18n 策略保持一致。- 当前切换主题会在两张图之间来回请求,如果图片体积较大,可以评估是否需要使用 Docusaurus 提供的
ThemedImage或 CSS 方案来减少额外网络开销。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.gitignore(1 hunks)eslint.config.mjs(1 hunks)i18n/en/code.json(11 hunks)i18n/zh-CN/code.json(11 hunks)src/components/HomepageFirstScreen/index.tsx(2 hunks)src/components/HomepageLifecycle/index.tsx(1 hunks)src/components/HomepagePdfCraftShowcase/index.tsx(1 hunks)src/css/design-tokens.scss(1 hunks)src/pages/chat/index.tsx(1 hunks)src/pages/contact-us/index.tsx(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/HomepageLifecycle/index.tsx
- src/components/HomepagePdfCraftShowcase/index.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/HomepageFirstScreen/index.tsx (3)
src/components/HomepageFirstScreen/GridBackground.tsx (1)
GridBackground(8-370)src/components/magic-ui/AuroraText.tsx (1)
AuroraText(59-59)src/components/DownloadButton/index.tsx (1)
DownloadButton(57-127)
src/pages/chat/index.tsx (1)
src/components/GetStartedPrompt/index.tsx (1)
GetStartedPrompt(6-29)
🪛 Biome (2.1.2)
i18n/en/code.json
[error] 1633-1633: The key STUDIO.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
[error] 2050-2050: The key HEADLESS.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
i18n/zh-CN/code.json
[error] 1550-1550: The key STUDIO.hero.description was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
.gitignore (1)
18-19: 新增条目简洁合理。将 Claude 本地设置文件添加到 .gitignore 是一个很好的做法,可以防止机器特定的配置被提交。条目放置位置恰当,与其他本地/环境特定文件(如 .env.local)逻辑分组,注释清晰易懂。
src/pages/chat/index.tsx (1)
7-67: 使用场景与核心功能数据结构整体合理
useCases和coreFeatures用数组 + translate 的方式组织文案和图标,结构清晰、易于扩展,也方便后续在同一位置维护多语言文案,当前实现没有明显问题。src/pages/contact-us/index.tsx (6)
6-6: 导入语句正确。新增的 Card 组件和 useBaseUrl hook 导入正确,且在文件中被正确使用。
Also applies to: 9-9
129-130: 正确使用 useBaseUrl 处理静态资源。使用
useBaseUrlhook 来构建图片路径是 Docusaurus 的最佳实践,能够正确处理不同的部署环境和 base URL 配置。
257-276: 卡片式联系方式列表实现正确。新的实现使用了语义化的锚点标签包裹卡片,并正确应用了
target="_blank"和rel="noopener noreferrer"来确保安全性,防止反向标签劫持攻击。卡片结构清晰,符合最佳实践。
277-300: Popover 实现符合新的设计系统。将触发器改为卡片式布局,并使用右侧定位(
position="right"),提升了用户体验。二维码图片正确使用了useBaseUrl。
317-358: 仓库列表实现正确,具有良好的回退机制。实现包括:
- 正确的外部链接安全属性
- GitHub 统计数据的条件渲染,当 API 数据不可用时回退到默认值
- 统一的仓库图标使用
useBaseUrl处理代码逻辑清晰,错误处理得当。
66-66: Icon classes are auto-generated by UnoCSS and will be properly available.The icon classes (
i-bi-discord,i-bi-youtube,i-bi-twitter-x,i-bi-github,i-octicon-comment-discussion-16) are not manually defined in CSS. They are generated by UnoCSS during the build process via thebuild-unocsscommand. The project'suno.config.tsproperly configures thepresetIconspreset with@iconify/jsoncollections for Bootstrap Icons (bi) and Octicons (octicon). These icons will be bundled intosrc/styles/uno.cssat build time, so they will display correctly.src/css/design-tokens.scss (3)
387-456: 排版和间距系统设计合理。设计令牌系统实现了完整的排版(字体大小、字重、行高)和基于 8pt 网格的间距系统,符合现代设计系统的最佳实践。令牌命名清晰,数值设置合理且一致。
462-491: 过渡动画实现考虑周全,避免了性能问题。实现亮点:
- 选择性地仅对特定元素应用过渡,避免使用全局通配符导致的性能问题
- 明确排除不需要过渡的媒体元素(img、video、canvas、svg、iframe)
- 使用合适的缓动函数和持续时间(0.3s)
这是处理主题切换动画的最佳实践。
263-265: The primary text color provides excellent contrast for dark backgrounds, exceeding WCAG AAA standards.The
--oomol-primary-text: #a8a9f0color choice on a#141414dark background achieves an actual contrast ratio of 8.42:1, which exceeds both WCAG AA (4.5:1) and WCAG AAA (7:1) standards for normal text. The code is correct and requires no changes.
No description provided.