-
Notifications
You must be signed in to change notification settings - Fork 11.7k
fix(web): stabilize homepage loading and build pipeline #4210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,20 +9,28 @@ import { withTranslation } from 'react-i18next'; | |
| class ErrorBoundary extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = { hasError: false }; | ||
| this.state = { hasError: false, errorMessage: '', componentStack: '' }; | ||
| } | ||
|
|
||
| static getDerivedStateFromError() { | ||
| return { hasError: true }; | ||
| static getDerivedStateFromError(error) { | ||
| return { | ||
| hasError: true, | ||
| errorMessage: error?.message || String(error || ''), | ||
| }; | ||
| } | ||
|
|
||
| componentDidCatch(error, errorInfo) { | ||
| console.error('[ErrorBoundary]', error, errorInfo); | ||
| this.setState({ | ||
| errorMessage: error?.message || String(error || ''), | ||
| componentStack: errorInfo?.componentStack || '', | ||
| }); | ||
|
Comment on lines
+24
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't expose raw exception details to every user. Rendering Also applies to: 53-70 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| render() { | ||
| if (this.state.hasError) { | ||
| const { t } = this.props; | ||
| const { errorMessage, componentStack } = this.state; | ||
| return ( | ||
| <div className='flex flex-col justify-center items-center h-screen p-8'> | ||
| <Empty | ||
|
|
@@ -42,6 +50,25 @@ class ErrorBoundary extends React.Component { | |
| > | ||
| {t('刷新页面')} | ||
| </Button> | ||
| {errorMessage && ( | ||
| <div | ||
| className='mt-6 w-full max-w-3xl rounded border border-semi-color-border bg-semi-color-bg-1 p-4 text-left' | ||
| style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }} | ||
| > | ||
| <div className='text-sm font-semibold mb-2'>Error</div> | ||
| <div className='text-xs text-semi-color-text-1'>{errorMessage}</div> | ||
| {componentStack && ( | ||
| <> | ||
| <div className='text-sm font-semibold mt-4 mb-2'> | ||
| Component Stack | ||
| </div> | ||
|
Comment on lines
+58
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Localize the new diagnostics headings. If this panel stays, As per coding guidelines: 🤖 Prompt for AI Agents |
||
| <div className='text-xs text-semi-color-text-1'> | ||
| {componentStack} | ||
| </div> | ||
| </> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,14 +22,11 @@ import { | |
| Button, | ||
| Typography, | ||
| Input, | ||
| ScrollList, | ||
| ScrollItem, | ||
| } from '@douyinfe/semi-ui'; | ||
| import { API, showError, copy, showSuccess } from '../../helpers'; | ||
| import { useIsMobile } from '../../hooks/common/useIsMobile'; | ||
| import { API_ENDPOINTS } from '../../constants/common.constant'; | ||
| import { StatusContext } from '../../context/Status'; | ||
| import { useActualTheme } from '../../context/Theme'; | ||
| import { marked } from 'marked'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { | ||
|
|
@@ -39,67 +36,49 @@ import { | |
| IconCopy, | ||
| } from '@douyinfe/semi-icons'; | ||
| import { Link } from 'react-router-dom'; | ||
| import NoticeModal from '../../components/layout/NoticeModal'; | ||
| import { | ||
| Moonshot, | ||
| OpenAI, | ||
| XAI, | ||
| Zhipu, | ||
| Volcengine, | ||
| Cohere, | ||
| Claude, | ||
| Gemini, | ||
| Suno, | ||
| Minimax, | ||
| Wenxin, | ||
| Spark, | ||
| Qingyan, | ||
| DeepSeek, | ||
| Qwen, | ||
| Midjourney, | ||
| Grok, | ||
| AzureAI, | ||
| Hunyuan, | ||
| Xinference, | ||
| } from '@lobehub/icons'; | ||
|
|
||
| const { Text } = Typography; | ||
|
|
||
| const Home = () => { | ||
| const { t, i18n } = useTranslation(); | ||
| const [statusState] = useContext(StatusContext); | ||
| const actualTheme = useActualTheme(); | ||
| const [homePageContentLoaded, setHomePageContentLoaded] = useState(false); | ||
| const [homePageContent, setHomePageContent] = useState(''); | ||
| const [noticeVisible, setNoticeVisible] = useState(false); | ||
| const isMobile = useIsMobile(); | ||
| const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; | ||
| const docsLink = statusState?.status?.docs_link || ''; | ||
| const serverAddress = | ||
| statusState?.status?.server_address || `${window.location.origin}`; | ||
| const endpointItems = API_ENDPOINTS.map((e) => ({ value: e })); | ||
| const [endpointIndex, setEndpointIndex] = useState(0); | ||
| const isChinese = i18n.language.startsWith('zh'); | ||
| const currentLanguage = | ||
| typeof i18n.language === 'string' && i18n.language | ||
| ? i18n.language | ||
| : 'zh-CN'; | ||
| const isChinese = currentLanguage.startsWith('zh'); | ||
| const isExternalHomePage = | ||
| typeof homePageContent === 'string' && homePageContent.startsWith('https://'); | ||
|
|
||
| const displayHomePageContent = async () => { | ||
| setHomePageContent(localStorage.getItem('home_page_content') || ''); | ||
| const res = await API.get('/api/home_page_content'); | ||
| const { success, message, data } = res.data; | ||
| if (success) { | ||
| let content = data; | ||
| if (!data.startsWith('https://')) { | ||
| content = marked.parse(data); | ||
| const rawContent = typeof data === 'string' ? data : ''; | ||
| let content = rawContent; | ||
| if (!rawContent.startsWith('https://')) { | ||
| content = marked.parse(rawContent); | ||
| } | ||
| setHomePageContent(content); | ||
| localStorage.setItem('home_page_content', content); | ||
|
|
||
| // 如果内容是 URL,则发送主题模式 | ||
| if (data.startsWith('https://')) { | ||
| if (rawContent.startsWith('https://')) { | ||
| const iframe = document.querySelector('iframe'); | ||
| if (iframe) { | ||
| iframe.onload = () => { | ||
| iframe.contentWindow.postMessage({ themeMode: actualTheme }, '*'); | ||
| iframe.contentWindow.postMessage({ lang: i18n.language }, '*'); | ||
| iframe.contentWindow.postMessage({ themeMode: 'light' }, '*'); | ||
| iframe.contentWindow.postMessage({ lang: currentLanguage }, '*'); | ||
|
Comment on lines
+80
to
+81
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't hardcode the iframe theme to This regresses dark/auto mode for external homepages. 🤖 Prompt for AI Agents |
||
| }; | ||
|
Comment on lines
72
to
82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Attach iframe messaging after the iframe is rendered.
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
@@ -117,44 +96,12 @@ const Home = () => { | |
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| const checkNoticeAndShow = async () => { | ||
| const lastCloseDate = localStorage.getItem('notice_close_date'); | ||
| const today = new Date().toDateString(); | ||
| if (lastCloseDate !== today) { | ||
| try { | ||
| const res = await API.get('/api/notice'); | ||
| const { success, data } = res.data; | ||
| if (success && data && data.trim() !== '') { | ||
| setNoticeVisible(true); | ||
| } | ||
| } catch (error) { | ||
| console.error('获取公告失败:', error); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| checkNoticeAndShow(); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| displayHomePageContent().then(); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| const timer = setInterval(() => { | ||
| setEndpointIndex((prev) => (prev + 1) % endpointItems.length); | ||
| }, 3000); | ||
| return () => clearInterval(timer); | ||
| }, [endpointItems.length]); | ||
|
|
||
| return ( | ||
| <div className='w-full overflow-x-hidden'> | ||
| <NoticeModal | ||
| visible={noticeVisible} | ||
| onClose={() => setNoticeVisible(false)} | ||
| isMobile={isMobile} | ||
| /> | ||
| {homePageContentLoaded && homePageContent === '' ? ( | ||
| <div className='w-full overflow-x-hidden'> | ||
| {/* Banner 部分 */} | ||
|
|
@@ -182,33 +129,39 @@ const Home = () => { | |
| <div className='flex flex-col md:flex-row items-center justify-center gap-4 w-full mt-4 md:mt-6 max-w-md'> | ||
| <Input | ||
| readonly | ||
| value={serverAddress} | ||
| value={`${serverAddress}${endpointItems[endpointIndex] || ''}`} | ||
| className='flex-1 !rounded-full' | ||
| size={isMobile ? 'default' : 'large'} | ||
| suffix={ | ||
| <div className='flex items-center gap-2'> | ||
| <ScrollList | ||
| bodyHeight={32} | ||
| style={{ border: 'unset', boxShadow: 'unset' }} | ||
| > | ||
| <ScrollItem | ||
| mode='wheel' | ||
| cycled={true} | ||
| list={endpointItems} | ||
| selectedIndex={endpointIndex} | ||
| onSelect={({ index }) => setEndpointIndex(index)} | ||
| /> | ||
| </ScrollList> | ||
| <Button | ||
| type='primary' | ||
| onClick={handleCopyBaseURL} | ||
| icon={<IconCopy />} | ||
| className='!rounded-full' | ||
| /> | ||
| </div> | ||
| <Button | ||
| type='primary' | ||
| onClick={handleCopyBaseURL} | ||
| icon={<IconCopy />} | ||
| className='!rounded-full' | ||
| /> | ||
| } | ||
| /> | ||
| </div> | ||
| {endpointItems.length > 0 && ( | ||
| <div className='flex flex-wrap justify-center gap-2 mt-4 max-w-3xl'> | ||
| {endpointItems.slice(0, 6).map((endpoint) => ( | ||
| <button | ||
| key={endpoint} | ||
| type='button' | ||
| className={`px-3 py-1.5 rounded-full text-xs md:text-sm border transition-colors ${ | ||
| endpoint === endpointItems[endpointIndex] | ||
| ? 'bg-blue-500 text-white border-blue-500' | ||
| : 'bg-transparent text-semi-color-text-1 border-semi-color-border' | ||
| }`} | ||
| onClick={() => | ||
| setEndpointIndex(endpointItems.indexOf(endpoint)) | ||
| } | ||
| > | ||
| {endpoint} | ||
|
Comment on lines
+132
to
+160
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Render the endpoint value, not the whole object.
Proposed fix- value={`${serverAddress}${endpointItems[endpointIndex] || ''}`}
+ value={`${serverAddress}${endpointItems[endpointIndex]?.value || ''}`}
@@
- {endpointItems.slice(0, 6).map((endpoint) => (
+ {endpointItems.slice(0, 6).map(({ value }) => (
<button
- key={endpoint}
+ key={value}
type='button'
className={`px-3 py-1.5 rounded-full text-xs md:text-sm border transition-colors ${
- endpoint === endpointItems[endpointIndex]
+ value === endpointItems[endpointIndex]?.value
? 'bg-blue-500 text-white border-blue-500'
: 'bg-transparent text-semi-color-text-1 border-semi-color-border'
}`}
- onClick={() =>
- setEndpointIndex(endpointItems.indexOf(endpoint))
- }
+ onClick={() =>
+ setEndpointIndex(
+ endpointItems.findIndex((item) => item.value === value),
+ )
+ }
>
- {endpoint}
+ {value}
</button>
))}🤖 Prompt for AI Agents |
||
| </button> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* 操作按钮 */} | ||
|
|
@@ -262,72 +215,31 @@ const Home = () => { | |
| {t('支持众多的大模型供应商')} | ||
| </Text> | ||
| </div> | ||
| <div className='flex flex-wrap items-center justify-center gap-3 sm:gap-4 md:gap-6 lg:gap-8 max-w-5xl mx-auto px-4'> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Moonshot size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <OpenAI size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <XAI size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Zhipu.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Volcengine.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Cohere.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Claude.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Gemini.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Suno size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Minimax.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Wenxin.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Spark.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Qingyan.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <DeepSeek.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Qwen.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Midjourney size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Grok size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <AzureAI.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Hunyuan.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Xinference.Color size={40} /> | ||
| </div> | ||
| <div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'> | ||
| <Typography.Text className='!text-lg sm:!text-xl md:!text-2xl lg:!text-3xl font-bold'> | ||
| 30+ | ||
| </Typography.Text> | ||
| </div> | ||
| <div className='flex flex-wrap items-center justify-center gap-3 md:gap-4 max-w-5xl mx-auto px-4'> | ||
| {[ | ||
| 'OpenAI', | ||
| 'Claude', | ||
| 'Gemini', | ||
| 'DeepSeek', | ||
| 'Qwen', | ||
| 'Grok', | ||
| 'Midjourney', | ||
| 'Azure OpenAI', | ||
| 'Volcengine', | ||
| 'Cohere', | ||
| 'Moonshot', | ||
| 'Minimax', | ||
| ].map((provider) => ( | ||
| <span | ||
| key={provider} | ||
| className='px-3 py-2 rounded-full border border-semi-color-border text-sm text-semi-color-text-1' | ||
| > | ||
| {provider} | ||
| </span> | ||
| ))} | ||
| <span className='px-3 py-2 rounded-full border border-semi-color-border text-sm font-semibold'> | ||
| 30+ | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -336,7 +248,7 @@ const Home = () => { | |
| </div> | ||
| ) : ( | ||
| <div className='overflow-x-hidden w-full'> | ||
| {homePageContent.startsWith('https://') ? ( | ||
| {isExternalHomePage ? ( | ||
| <iframe | ||
| src={homePageContent} | ||
| className='w-full h-screen border-none' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the frontend image build on
bun run build.Calling
bunx vite buildhere sidesteps the repo’s canonical frontend build entrypoint, so Docker can silently diverge from local/CI builds oncepackage.jsonadds flags, wrappers, or pre/post hooks. Please fold the Vite/minify tweak into the normal build script/config instead of bypassing it.Based on learnings: Use Bun as the preferred package manager and script runner for the frontend (
web/directory). Usebun install,bun run dev,bun run build, andbun run i18n:*commands.🤖 Prompt for AI Agents