mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 05:12:33 +00:00
189 lines
8.6 KiB
TypeScript
189 lines
8.6 KiB
TypeScript
import * as Sentry from '@sentry/react';
|
|
import { useEffect } from 'react';
|
|
import { Provider } from 'react-redux';
|
|
import { HashRouter as Router, useLocation, useNavigate } from 'react-router-dom';
|
|
import { PersistGate } from 'redux-persist/integration/react';
|
|
|
|
import AppRoutes from './AppRoutes';
|
|
import AppUpdatePrompt from './components/AppUpdatePrompt';
|
|
import BottomTabBar from './components/BottomTabBar';
|
|
import CommandProvider from './components/commands/CommandProvider';
|
|
import ServiceBlockingGate from './components/daemon/ServiceBlockingGate';
|
|
import DictationHotkeyManager from './components/DictationHotkeyManager';
|
|
import ErrorFallbackScreen from './components/ErrorFallbackScreen';
|
|
import LocalAIDownloadSnackbar from './components/LocalAIDownloadSnackbar';
|
|
import MeshGradient from './components/MeshGradient';
|
|
import OpenhumanLinkModal from './components/OpenhumanLinkModal';
|
|
import PersistRehydrationScreen from './components/PersistRehydrationScreen';
|
|
import GlobalUpsellBanner from './components/upsell/GlobalUpsellBanner';
|
|
import AppWalkthrough from './components/walkthrough/AppWalkthrough';
|
|
// [#1123] Commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
// import { isWelcomeLocked } from './lib/coreState/store';
|
|
import { startNativeNotificationsService } from './lib/nativeNotifications';
|
|
import { startWebviewNotificationsService } from './lib/webviewNotifications';
|
|
import ChatRuntimeProvider from './providers/ChatRuntimeProvider';
|
|
import CoreStateProvider, { useCoreState } from './providers/CoreStateProvider';
|
|
import SocketProvider from './providers/SocketProvider';
|
|
import { startWebviewAccountService } from './services/webviewAccountService';
|
|
import { persistor, store } from './store';
|
|
// [#1123] useAppDispatch commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
import { useAppSelector } from './store/hooks';
|
|
// [#1123] Commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
// import { clearSelectedThread, deleteThread, setWelcomeThreadId } from './store/threadSlice';
|
|
import { isAccountsFullscreen } from './utils/accountsFullscreen';
|
|
import { DEV_FORCE_ONBOARDING } from './utils/config';
|
|
|
|
// Attach the `webview:event` listener at app boot so background recipe
|
|
// events (Google Meet captions → transcript flush, WhatsApp ingest, …)
|
|
// are handled even when the user hasn't navigated to /accounts yet.
|
|
// Idempotent — the service uses a `started` singleton guard.
|
|
startWebviewAccountService();
|
|
startWebviewNotificationsService();
|
|
startNativeNotificationsService();
|
|
|
|
function App() {
|
|
return (
|
|
<Sentry.ErrorBoundary
|
|
fallback={({ error, componentStack, resetError }) => (
|
|
<ErrorFallbackScreen error={error} componentStack={componentStack} onReset={resetError} />
|
|
)}>
|
|
<Provider store={store}>
|
|
<PersistGate loading={<PersistRehydrationScreen />} persistor={persistor}>
|
|
<CoreStateProvider>
|
|
<SocketProvider>
|
|
<ChatRuntimeProvider>
|
|
<Router>
|
|
<CommandProvider>
|
|
<ServiceBlockingGate>
|
|
<AppShell />
|
|
<DictationHotkeyManager />
|
|
<LocalAIDownloadSnackbar />
|
|
<AppUpdatePrompt />
|
|
</ServiceBlockingGate>
|
|
</CommandProvider>
|
|
</Router>
|
|
</ChatRuntimeProvider>
|
|
</SocketProvider>
|
|
</CoreStateProvider>
|
|
</PersistGate>
|
|
</Provider>
|
|
</Sentry.ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
/** Inner shell — lives inside the Router so it can use useLocation. */
|
|
function AppShell() {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const { snapshot, isBootstrapping } = useCoreState();
|
|
const activeAccountId = useAppSelector(state => state.accounts.activeAccountId);
|
|
// On /accounts, only the agent view keeps the tab bar + its reserved
|
|
// bottom padding. Any other selected "app" (e.g. WhatsApp) takes the
|
|
// full viewport so the embedded webview goes edge-to-edge.
|
|
const fullscreen = isAccountsFullscreen(location.pathname, activeAccountId);
|
|
// [#1123] Commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
// const welcomeLocked = isWelcomeLocked(snapshot);
|
|
const onOnboardingRoute = location.pathname.startsWith('/onboarding');
|
|
const onboardingPending =
|
|
!!snapshot.sessionToken && (DEV_FORCE_ONBOARDING || !snapshot.onboardingCompleted);
|
|
|
|
// Onboarding gate: while `onboarding_completed=false`, force any non-
|
|
// onboarding route back to `/onboarding`. Once completed, bounce the
|
|
// user off `/onboarding` so they don't get stuck on the stepper.
|
|
useEffect(() => {
|
|
if (isBootstrapping || !snapshot.sessionToken) return;
|
|
if (onboardingPending && !onOnboardingRoute) {
|
|
console.debug(
|
|
`[onboarding-gate] redirecting ${location.pathname} -> /onboarding (onboarding incomplete)`
|
|
);
|
|
navigate('/onboarding', { replace: true });
|
|
} else if (!onboardingPending && onOnboardingRoute) {
|
|
console.debug(
|
|
`[onboarding-gate] redirecting ${location.pathname} -> /home (onboarding complete)`
|
|
);
|
|
navigate('/home', { replace: true });
|
|
}
|
|
}, [
|
|
isBootstrapping,
|
|
snapshot.sessionToken,
|
|
onboardingPending,
|
|
onOnboardingRoute,
|
|
location.pathname,
|
|
navigate,
|
|
]);
|
|
|
|
// [#1123] Commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
// After the welcome agent calls `complete_onboarding` and
|
|
// `chat_onboarding_completed` flips false→true, discard the transient
|
|
// welcome thread we created in `OnboardingLayout`. The next user
|
|
// message will route to the orchestrator and create its own thread.
|
|
// const dispatch = useAppDispatch();
|
|
// const welcomeThreadId = useAppSelector(state => state.thread.welcomeThreadId);
|
|
// const chatOnboardingCompleted = snapshot.chatOnboardingCompleted;
|
|
// useEffect(() => {
|
|
// if (!chatOnboardingCompleted || !welcomeThreadId) return;
|
|
// let cancelled = false;
|
|
// console.debug(
|
|
// `[welcome-cleanup] chat_onboarding_completed=true — deleting welcome thread ${welcomeThreadId}`
|
|
// );
|
|
// // Await the delete before dropping the local id so a backend failure
|
|
// // leaves `welcomeThreadId` set for retry on the next render. Without
|
|
// // the await, a 500 from `threads.delete` would leave a stale row in
|
|
// // the user's thread list while the renderer thinks it's gone.
|
|
// (async () => {
|
|
// try {
|
|
// await dispatch(deleteThread(welcomeThreadId)).unwrap();
|
|
// if (cancelled) return;
|
|
// dispatch(clearSelectedThread());
|
|
// dispatch(setWelcomeThreadId(null));
|
|
// } catch (err) {
|
|
// console.warn('[welcome-cleanup] deleteThread failed; will retry on next render', err);
|
|
// }
|
|
// })();
|
|
// return () => {
|
|
// cancelled = true;
|
|
// };
|
|
// }, [chatOnboardingCompleted, welcomeThreadId, dispatch]);
|
|
//
|
|
// [#1123] Commented out — welcome-agent onboarding replaced by Joyride walkthrough
|
|
// Welcome lockdown (#883) — force any route other than `/chat` back to
|
|
// `/chat` while the welcome-agent conversation is still in progress.
|
|
// Skipped while onboarding is still pending (the onboarding gate above
|
|
// owns the route during that phase).
|
|
// useEffect(() => {
|
|
// if (!welcomeLocked || isBootstrapping) return;
|
|
// if (onboardingPending) return;
|
|
// if (location.pathname === '/chat') return;
|
|
// console.debug(
|
|
// `[welcome-lock] redirecting ${location.pathname} -> /chat (chat onboarding incomplete)`
|
|
// );
|
|
// navigate('/chat', { replace: true });
|
|
// }, [welcomeLocked, isBootstrapping, onboardingPending, location.pathname, navigate]);
|
|
|
|
return (
|
|
<div className="relative h-screen flex flex-col overflow-hidden">
|
|
<MeshGradient />
|
|
<div className="app-dotted-canvas relative z-10 flex-1 flex flex-col overflow-hidden">
|
|
<div
|
|
className={`flex-1 overflow-y-auto ${
|
|
// [#1123] welcomeLocked removed — welcome-agent onboarding replaced by Joyride walkthrough
|
|
fullscreen || onOnboardingRoute ? '' : 'pb-16'
|
|
}`}>
|
|
<GlobalUpsellBanner />
|
|
<AppRoutes />
|
|
</div>
|
|
{!onOnboardingRoute && <BottomTabBar />}
|
|
</div>
|
|
<OpenhumanLinkModal />
|
|
{/* Post-onboarding Joyride walkthrough — mounted here (outside routes) so
|
|
it persists across tab navigations. Joyride targets span Home + BottomTabBar
|
|
tabs so it must stay mounted while the user moves between routes. */}
|
|
{!isBootstrapping && !onOnboardingRoute && (
|
|
<AppWalkthrough onboarded={!!snapshot.onboardingCompleted} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|