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);
}}
>
{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()}>
{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 (
{t.title}
{locale === 'zh' ? '正在校验面板访问权限...' : 'Checking panel access...'}
);
}
if (required && !authenticated) {
return (
{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;