mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-26 02:51:57 +03:00
feat(core): add local ai onboarding dialog (#6600)
This commit is contained in:
parent
28f2ff24b9
commit
7970d9b8c9
@ -30,6 +30,7 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => {
|
||||
footer,
|
||||
alignMessage = 'title',
|
||||
onDismiss,
|
||||
rootAttrs,
|
||||
} = notification;
|
||||
|
||||
const onActionClicked = useCallback(() => {
|
||||
@ -49,7 +50,8 @@ export const NotificationCard = ({ notification }: NotificationCardProps) => {
|
||||
[styles.iconColor]: getIconColor(style, theme, iconColor),
|
||||
})}
|
||||
data-with-icon={icon ? '' : undefined}
|
||||
className={styles.card}
|
||||
{...rootAttrs}
|
||||
className={clsx(styles.card, rootAttrs?.className)}
|
||||
>
|
||||
{thumb}
|
||||
<div className={styles.cardInner}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
|
||||
import type { ButtonProps } from '../button';
|
||||
|
||||
@ -23,6 +23,8 @@ export interface Notification {
|
||||
autoClose?: boolean;
|
||||
};
|
||||
|
||||
rootAttrs?: HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
// custom slots
|
||||
thumb?: ReactNode;
|
||||
title?: ReactNode;
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
});
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user