diff --git a/packages/frontend/component/src/ui/notification/notification-card.tsx b/packages/frontend/component/src/ui/notification/notification-card.tsx
index 0b542bb551..6b1d21f6ab 100644
--- a/packages/frontend/component/src/ui/notification/notification-card.tsx
+++ b/packages/frontend/component/src/ui/notification/notification-card.tsx
@@ -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}
diff --git a/packages/frontend/component/src/ui/notification/types.ts b/packages/frontend/component/src/ui/notification/types.ts
index 42bf239611..f7b7bf8d22 100644
--- a/packages/frontend/component/src/ui/notification/types.ts
+++ b/packages/frontend/component/src/ui/notification/types.ts
@@ -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
;
+
// custom slots
thumb?: ReactNode;
title?: ReactNode;
diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx
index 58bae62bc3..92de0c744c 100644
--- a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx
+++ b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx
@@ -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: ,
- iconColor: cssVar('brandColor'),
- thumb: ,
- 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: ,
+ iconColor: cssVar('processingColor'),
+ thumb: ,
+ alignMessage: 'icon',
+ onDismiss,
+ },
+ { duration: 1000 * 60 * 10 }
+ );
+ edgelessNotifyId$.next(id);
+ }, 1000);
}, [
generalAIOnboardingOpened,
isCloud,
diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts
new file mode 100644
index 0000000000..d260ab953a
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.css.ts
@@ -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',
+});
diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx
index 2e14f2f8e2..be8e183045 100644
--- a/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx
+++ b/packages/frontend/core/src/components/affine/ai-onboarding/local.dialog.tsx
@@ -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 {/* TODO: open local workspace for the first time */}
;
+const LocalOnboardingAnimation = () => {
+ return (
+
+
+
+ );
+};
+
+const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
+ const t = useAFFiNEI18N();
+ return (
+
+ );
+};
+
+export const AIOnboardingLocal = ({
+ onDismiss,
+}: BaseAIOnboardingDialogProps) => {
+ const t = useAFFiNEI18N();
+ const workspaceService = useService(WorkspaceService);
+ const notifyId = useLiveData(localNotifyId$);
+ const timeoutRef = useRef>();
+
+ 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: (
+
+ {t['com.affine.ai-onboarding.local.title']()}
+
+ ),
+ message: t['com.affine.ai-onboarding.local.message'](),
+ icon: ,
+ iconColor: cssVar('brandColor'),
+ thumb: ,
+ alignMessage: 'icon',
+ onDismiss,
+ footer: (
+ {
+ onDismiss();
+ notify.dismiss(id);
+ }}
+ />
+ ),
+ rootAttrs: { className: styles.card },
+ },
+ { duration: 1000 * 60 * 10 }
+ );
+ localNotifyId$.next(id);
+ }, 1000);
+ }, [isLocal, notifyId, onDismiss, t]);
+
+ return null;
};
diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/state.ts b/packages/frontend/core/src/components/affine/ai-onboarding/state.ts
index 6322c3a76f..5aa2579f9c 100644
--- a/packages/frontend/core/src/components/affine/ai-onboarding/state.ts
+++ b/packages/frontend/core/src/components/affine/ai-onboarding/state.ts
@@ -5,4 +5,10 @@ import { LiveData } from '@toeverything/infra';
export const showAIOnboardingGeneral$ = new LiveData(false);
// avoid notifying multiple times
-export const edgelessNotifyId$ = new LiveData(null);
+export const edgelessNotifyId$ = new LiveData(
+ undefined
+);
+
+export const localNotifyId$ = new LiveData(
+ undefined
+);