feat(core): ai onboarding for edgeless mode (#6556)

This commit is contained in:
CatsJuice
2024-04-15 07:25:36 +00:00
parent 257e946d5d
commit b93e79c59d
14 changed files with 88362 additions and 55 deletions

View File

@@ -0,0 +1,12 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const thumb = style({
borderRadius: 'inherit',
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
width: '100%',
height: 211,
background: cssVar('backgroundOverlayPanelColor'),
overflow: 'hidden',
});

View File

@@ -1,9 +1,73 @@
import { notify } from '@affine/component';
import { CurrentWorkspaceService } from '@affine/core/modules/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { AiIcon } from '@blocksuite/icons';
import { Doc, LiveData, useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import Lottie from 'lottie-react';
import { useTheme } from 'next-themes';
import { useEffect, useMemo, useRef } from 'react';
import * as styles from './edgeless.dialog.css';
import mouseDark from './lottie/edgeless/mouse-dark.json';
import mouseLight from './lottie/edgeless/mouse-light.json';
import trackPadDark from './lottie/edgeless/trackpad-dark.json';
import trackPadLight from './lottie/edgeless/trackpad-light.json';
import type { BaseAIOnboardingDialogProps } from './type';
export const AIOnboardingEdgeless = ({
onDismiss: _,
}: BaseAIOnboardingDialogProps) => {
return (
<div>{/* TODO: open edgeless in cloud workspace for the first time */}</div>
);
const EdgelessOnboardingAnimation = () => {
const { resolvedTheme } = useTheme();
const isTrackPad = false;
const data = useMemo(() => {
if (isTrackPad) {
return resolvedTheme === 'dark' ? trackPadDark : trackPadLight;
}
return resolvedTheme === 'dark' ? mouseDark : mouseLight;
}, [isTrackPad, resolvedTheme]);
return <Lottie loop autoplay animationData={data} className={styles.thumb} />;
};
// avoid notifying multiple times
const notifyId$ = new LiveData<string | number | null>(null);
export const AIOnboardingEdgeless = ({
onDismiss,
}: BaseAIOnboardingDialogProps) => {
const t = useAFFiNEI18N();
const notifyId = useLiveData(notifyId$);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace$
);
const isCloud = currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD;
const doc = useService(Doc);
const mode = useLiveData(doc.mode$);
useEffect(() => {
if (notifyId) return;
if (isCloud && mode === 'edgeless') {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
const id = notify(
{
title: t['com.affine.ai-onboarding.edgeless.title'](),
message: t['com.affine.ai-onboarding.edgeless.message'](),
icon: <AiIcon color={cssVar('processingColor')} />,
thumb: <EdgelessOnboardingAnimation />,
alignMessage: 'icon',
onDismiss,
},
{ duration: 1000 * 60 * 10 }
);
notifyId$.next(id);
}, 1000);
}
}, [isCloud, mode, notifyId, onDismiss, t]);
return null;
};

View File

@@ -16,13 +16,10 @@ const useDismiss = (key: AIOnboardingType) => {
return [dismiss, onDismiss] as const;
};
export const AIOnboarding = () => {
export const WorkspaceAIOnboarding = () => {
const [dismissGeneral, onDismissGeneral] = useDismiss(
AIOnboardingType.GENERAL
);
const [dismissEdgeless, onDismissEdgeless] = useDismiss(
AIOnboardingType.EDGELESS
);
const [dismissLocal, onDismissLocal] = useDismiss(AIOnboardingType.LOCAL);
return (
@@ -30,10 +27,22 @@ export const AIOnboarding = () => {
{dismissGeneral ? null : (
<AIOnboardingGeneral onDismiss={onDismissGeneral} />
)}
{dismissEdgeless ? null : (
<AIOnboardingEdgeless onDismiss={onDismissEdgeless} />
)}
{dismissLocal ? null : <AIOnboardingLocal onDismiss={onDismissLocal} />}
</Suspense>
);
};
export const PageAIOnboarding = () => {
const [dismissEdgeless, onDismissEdgeless] = useDismiss(
AIOnboardingType.EDGELESS
);
return (
<Suspense>
{dismissEdgeless ? null : (
<AIOnboardingEdgeless onDismiss={onDismissEdgeless} />
)}
</Suspense>
);
};

View File

@@ -22,7 +22,7 @@ import { matchPath } from 'react-router-dom';
import { Map as YMap } from 'yjs';
import { openQuickSearchModalAtom, openSettingModalAtom } from '../atoms';
import { AIOnboarding } from '../components/affine/ai-onboarding';
import { WorkspaceAIOnboarding } from '../components/affine/ai-onboarding';
import { AppContainer } from '../components/affine/app-container';
import { SyncAwareness } from '../components/affine/awareness';
import {
@@ -102,7 +102,7 @@ export const WorkspaceLayout = function WorkspaceLayout({
<Suspense fallback={<WorkspaceFallback />}>
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
{/* should show after workspace loaded */}
<AIOnboarding />
<WorkspaceAIOnboarding />
</Suspense>
</SWRConfigProvider>
);

View File

@@ -1,5 +1,6 @@
import { Scrollable } from '@affine/component';
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import type { PageRootService } from '@blocksuite/blocks';
import {
@@ -283,6 +284,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
<ImagePreviewModal pageId={currentPageId} docCollection={docCollection} />
<GlobalPageHistoryModal />
<PageAIOnboarding />
</>
);
});