feat(core): add local ai onboarding dialog (#6600)

This commit is contained in:
CatsJuice
2024-04-18 15:48:19 +00:00
parent 28f2ff24b9
commit 7970d9b8c9
6 changed files with 172 additions and 24 deletions

View File

@@ -18,7 +18,11 @@ import { useEffect, useMemo, useRef } from 'react';
import * as styles from './edgeless.dialog.css';
import mouseTrackDark from './lottie/edgeless/mouse-track-dark.json';
import mouseTrackLight from './lottie/edgeless/mouse-track-light.json';
import { edgelessNotifyId$, showAIOnboardingGeneral$ } from './state';
import {
edgelessNotifyId$,
localNotifyId$,
showAIOnboardingGeneral$,
} from './state';
import type { BaseAIOnboardingDialogProps } from './type';
const EdgelessOnboardingAnimation = () => {
@@ -63,24 +67,27 @@ export const AIOnboardingEdgeless = ({
if (settingModalOpen.open) return;
if (generalAIOnboardingOpened) return;
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 />,
iconColor: cssVar('brandColor'),
thumb: <EdgelessOnboardingAnimation />,
alignMessage: 'icon',
onDismiss,
},
{ duration: 1000 * 60 * 10 }
);
edgelessNotifyId$.next(id);
}, 1000);
}
if (mode !== 'edgeless') return;
if (!isCloud) return;
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
// try to close local onboarding
notify.dismiss(localNotifyId$.value);
const id = notify(
{
title: t['com.affine.ai-onboarding.edgeless.title'](),
message: t['com.affine.ai-onboarding.edgeless.message'](),
icon: <AiIcon />,
iconColor: cssVar('processingColor'),
thumb: <EdgelessOnboardingAnimation />,
alignMessage: 'icon',
onDismiss,
},
{ duration: 1000 * 60 * 10 }
);
edgelessNotifyId$.next(id);
}, 1000);
}, [
generalAIOnboardingOpened,
isCloud,

View File

@@ -0,0 +1,40 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const card = style({
borderRadius: 12,
boxShadow: cssVar('menuShadow'),
});
export const thumb = style({
width: '100%',
height: 211,
borderRadius: 'inherit',
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
export const thumbContent = style({
width: 'calc(100% + 4px)',
height: 'calc(100% + 4px)',
});
export const title = style({
fontWeight: 500,
});
export const footerActions = style({
display: 'flex',
justifyContent: 'flex-end',
gap: 12,
marginTop: 8,
});
export const actionButton = style({
fontSize: cssVar('fontSm'),
padding: '0 2px',
});

View File

@@ -1,5 +1,96 @@
import { Button, notify } from '@affine/component';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { AiIcon } from '@blocksuite/icons';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { useEffect, useRef } from 'react';
import * as styles from './local.dialog.css';
import { edgelessNotifyId$, localNotifyId$ } from './state';
import type { BaseAIOnboardingDialogProps } from './type';
export const AIOnboardingLocal = (_: BaseAIOnboardingDialogProps) => {
return <div>{/* TODO: open local workspace for the first time */}</div>;
const LocalOnboardingAnimation = () => {
return (
<div className={styles.thumb}>
<video
className={styles.thumbContent}
src="/onboarding/ai-onboarding.general.1.mov"
autoPlay
loop
muted
playsInline
/>
</div>
);
};
const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
const t = useAFFiNEI18N();
return (
<div className={styles.footerActions}>
<Button onClick={onDismiss} type="plain" className={styles.actionButton}>
<span style={{ color: cssVar('textSecondaryColor') }}>
{t['com.affine.ai-onboarding.local.action-dismiss']()}
</span>
</Button>
<a href="https://ai.affine.pro" target="_blank" rel="noreferrer">
<Button className={styles.actionButton} type="plain">
<span style={{ color: cssVar('textPrimaryColor') }}>
{t['com.affine.ai-onboarding.local.action-learn-more']()}
</span>
</Button>
</a>
</div>
);
};
export const AIOnboardingLocal = ({
onDismiss,
}: BaseAIOnboardingDialogProps) => {
const t = useAFFiNEI18N();
const workspaceService = useService(WorkspaceService);
const notifyId = useLiveData(localNotifyId$);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const isLocal = workspaceService.workspace.flavour === WorkspaceFlavour.LOCAL;
useEffect(() => {
if (!isLocal) return;
if (notifyId) return;
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
// try to close edgeless onboarding
notify.dismiss(edgelessNotifyId$.value);
const id = notify(
{
title: (
<div className={styles.title}>
{t['com.affine.ai-onboarding.local.title']()}
</div>
),
message: t['com.affine.ai-onboarding.local.message'](),
icon: <AiIcon />,
iconColor: cssVar('brandColor'),
thumb: <LocalOnboardingAnimation />,
alignMessage: 'icon',
onDismiss,
footer: (
<FooterActions
onDismiss={() => {
onDismiss();
notify.dismiss(id);
}}
/>
),
rootAttrs: { className: styles.card },
},
{ duration: 1000 * 60 * 10 }
);
localNotifyId$.next(id);
}, 1000);
}, [isLocal, notifyId, onDismiss, t]);
return null;
};

View File

@@ -5,4 +5,10 @@ import { LiveData } from '@toeverything/infra';
export const showAIOnboardingGeneral$ = new LiveData(false);
// avoid notifying multiple times
export const edgelessNotifyId$ = new LiveData<string | number | null>(null);
export const edgelessNotifyId$ = new LiveData<string | number | undefined>(
undefined
);
export const localNotifyId$ = new LiveData<string | number | undefined>(
undefined
);