import { useEffect, useMemo, useState, type ReactElement } from 'react'; import axios from 'axios'; import { ChevronDown, ChevronUp, MoonStar, SunMedium, X } from 'lucide-react'; import { useAppStore } from './store/appStore'; import { useBotsSync } from './hooks/useBotsSync'; import { APP_ENDPOINTS } from './config/env'; import { ImageFactoryModule } from './modules/images/ImageFactoryModule'; import { BotWizardModule } from './modules/onboarding/BotWizardModule'; import { BotDashboardModule } from './modules/dashboard/BotDashboardModule'; import { pickLocale } from './i18n'; import { appZhCn } from './i18n/app.zh-cn'; import { appEn } from './i18n/app.en'; import { LucentIconButton } from './components/lucent/LucentIconButton'; import { LucentTooltip } from './components/lucent/LucentTooltip'; import { clearPanelAccessPassword, getPanelAccessPassword, setPanelAccessPassword } from './utils/panelAccess'; import './App.css'; function getSingleBotPasswordKey(botId: string) { return `nanobot-bot-page-password:${String(botId || '').trim()}`; } function AuthenticatedApp({ forcedBotId, compactMode, }: { forcedBotId?: string; compactMode: boolean; }) { const { theme, setTheme, locale, setLocale, activeBots } = useAppStore(); const [showImageFactory, setShowImageFactory] = useState(false); const [showCreateWizard, setShowCreateWizard] = useState(false); const [singleBotPassword, setSingleBotPassword] = useState(''); const [singleBotPasswordError, setSingleBotPasswordError] = useState(''); const [singleBotUnlocked, setSingleBotUnlocked] = useState(false); const [singleBotSubmitting, setSingleBotSubmitting] = useState(false); useBotsSync(forcedBotId); const t = pickLocale(locale, { 'zh-cn': appZhCn, en: appEn }); const isSingleBotCompactView = compactMode && Boolean(String(forcedBotId || '').trim()); const [headerCollapsed, setHeaderCollapsed] = useState(isSingleBotCompactView); const forced = String(forcedBotId || '').trim(); const forcedBot = forced ? activeBots[forced] : undefined; const shouldPromptSingleBotPassword = Boolean(forced && forcedBot?.has_access_password && !singleBotUnlocked); useEffect(() => { if (!forced) { document.title = t.title; return; } const botName = String(forcedBot?.name || '').trim(); document.title = botName ? `${t.title} - ${botName}` : `${t.title} - ${forced}`; }, [forced, forcedBot?.name, t.title]); useEffect(() => { setHeaderCollapsed(isSingleBotCompactView); }, [isSingleBotCompactView, forcedBotId]); useEffect(() => { setSingleBotUnlocked(false); setSingleBotPassword(''); setSingleBotPasswordError(''); }, [forced]); useEffect(() => { if (!forced || !forcedBot?.has_access_password || singleBotUnlocked) return; const stored = typeof window !== 'undefined' ? window.sessionStorage.getItem(getSingleBotPasswordKey(forced)) || '' : ''; if (!stored) return; let alive = true; const boot = async () => { try { await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${encodeURIComponent(forced)}/auth/login`, { password: stored }); if (!alive) return; setSingleBotUnlocked(true); setSingleBotPassword(''); setSingleBotPasswordError(''); } catch { if (typeof window !== 'undefined') window.sessionStorage.removeItem(getSingleBotPasswordKey(forced)); if (!alive) return; setSingleBotPasswordError(locale === 'zh' ? 'Bot 密码错误,请重新输入。' : 'Invalid bot password. Please try again.'); } }; void boot(); return () => { alive = false; }; }, [forced, forcedBot?.has_access_password, locale, singleBotUnlocked]); const unlockSingleBot = async () => { const entered = String(singleBotPassword || '').trim(); if (!entered) { setSingleBotPasswordError(locale === 'zh' ? '请输入 Bot 密码。' : 'Enter the bot password.'); return; } if (!forced) return; setSingleBotSubmitting(true); try { await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${encodeURIComponent(forced)}/auth/login`, { password: entered }); if (typeof window !== 'undefined') { window.sessionStorage.setItem(getSingleBotPasswordKey(forced), entered); } setSingleBotPasswordError(''); setSingleBotUnlocked(true); setSingleBotPassword(''); } catch { if (typeof window !== 'undefined') { window.sessionStorage.removeItem(getSingleBotPasswordKey(forced)); } setSingleBotPasswordError(locale === 'zh' ? 'Bot 密码错误,请重试。' : 'Invalid bot password. Please try again.'); } finally { setSingleBotSubmitting(false); } }; return (
{ if (isSingleBotCompactView && headerCollapsed) setHeaderCollapsed(false); }} >
Nanobot

{t.title}

{isSingleBotCompactView ? ( ) : null}
{!headerCollapsed ? (
) : null}
setShowCreateWizard(true)} onOpenImageFactory={() => setShowImageFactory(true)} forcedBotId={forcedBotId || undefined} compactMode={compactMode} />
{!compactMode && showImageFactory && (
setShowImageFactory(false)}>
e.stopPropagation()}>

{t.nav.images.title}

setShowImageFactory(false)} tooltip={t.close} aria-label={t.close}>
)} {!compactMode && showCreateWizard && (
setShowCreateWizard(false)}>
e.stopPropagation()}>

{t.nav.onboarding.title}

setShowCreateWizard(false)} tooltip={t.close} aria-label={t.close}>
{ setShowCreateWizard(false); }} onGoDashboard={() => setShowCreateWizard(false)} />
)} {shouldPromptSingleBotPassword ? (
event.stopPropagation()}> Nanobot

{forcedBot?.name || forced}

{locale === 'zh' ? '请输入该 Bot 的访问密码后继续。' : 'Enter the bot password to continue.'}

{ setSingleBotPassword(event.target.value); if (singleBotPasswordError) setSingleBotPasswordError(''); }} onKeyDown={(event) => { if (event.key === 'Enter') void unlockSingleBot(); }} placeholder={locale === 'zh' ? 'Bot 密码' : 'Bot password'} autoFocus /> {singleBotPasswordError ?
{singleBotPasswordError}
: null}
) : null}
); } function PanelLoginGate({ children, }: { children: (props: { forcedBotId?: string; compactMode: boolean }) => ReactElement; }) { const { theme, locale } = useAppStore(); const t = pickLocale(locale, { 'zh-cn': appZhCn, en: appEn }); const urlView = useMemo(() => { const params = new URLSearchParams(window.location.search); const pathMatch = window.location.pathname.match(/^\/bot\/([^/?#]+)/i); let forcedBotIdFromPath = ''; if (pathMatch?.[1]) { try { forcedBotIdFromPath = decodeURIComponent(pathMatch[1]).trim(); } catch { forcedBotIdFromPath = String(pathMatch[1]).trim(); } } const forcedBotIdFromQuery = (params.get('botId') || params.get('bot_id') || params.get('id') || '').trim(); const forcedBotId = forcedBotIdFromPath || forcedBotIdFromQuery; const compactRaw = (params.get('compact') || params.get('h5') || params.get('mobile') || '').trim().toLowerCase(); const compactByFlag = ['1', 'true', 'yes', 'on'].includes(compactRaw); const compactMode = compactByFlag || forcedBotId.length > 0; return { forcedBotId, compactMode }; }, []); const [checking, setChecking] = useState(true); const [required, setRequired] = useState(false); const [authenticated, setAuthenticated] = useState(false); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [submitting, setSubmitting] = useState(false); const bypassPanelGate = Boolean(String(urlView.forcedBotId || '').trim()); useEffect(() => { if (bypassPanelGate) { setRequired(false); setAuthenticated(true); setChecking(false); return; } let alive = true; const boot = async () => { try { const status = await axios.get<{ enabled: boolean }>(`${APP_ENDPOINTS.apiBase}/panel/auth/status`); if (!alive) return; const enabled = Boolean(status.data?.enabled); if (!enabled) { setRequired(false); setAuthenticated(true); setChecking(false); return; } setRequired(true); const stored = getPanelAccessPassword(); if (!stored) { setChecking(false); return; } try { await axios.post(`${APP_ENDPOINTS.apiBase}/panel/auth/login`, { password: stored }); if (!alive) return; setAuthenticated(true); } catch { clearPanelAccessPassword(); if (!alive) return; setError(locale === 'zh' ? '面板访问密码错误,请重新输入。' : 'Invalid panel access password. Please try again.'); } finally { if (alive) setChecking(false); } } catch { if (!alive) return; setRequired(false); setAuthenticated(true); setChecking(false); } }; void boot(); return () => { alive = false; }; }, [bypassPanelGate, locale]); const onSubmit = async () => { const next = String(password || '').trim(); if (!next) { setError(locale === 'zh' ? '请输入面板访问密码。' : 'Enter the panel access password.'); return; } setSubmitting(true); setError(''); try { await axios.post(`${APP_ENDPOINTS.apiBase}/panel/auth/login`, { password: next }); setPanelAccessPassword(next); setAuthenticated(true); } catch { clearPanelAccessPassword(); setError(locale === 'zh' ? '面板访问密码错误。' : 'Invalid panel access password.'); } finally { setSubmitting(false); } }; if (checking) { return (
Nanobot

{t.title}

{locale === 'zh' ? '正在校验面板访问权限...' : 'Checking panel access...'}

); } if (required && !authenticated) { return (
Nanobot

{t.title}

{locale === 'zh' ? '请输入面板访问密码后继续。' : 'Enter the panel access password to continue.'}

setPassword(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') void onSubmit(); }} placeholder={locale === 'zh' ? '面板访问密码' : 'Panel access password'} /> {error ?
{error}
: null}
); } return children(urlView); } function App() { return {(urlView) => }; } export default App;