diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/custom-modal.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/custom-modal.tsx index eb0098fcc..e23682840 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/custom-modal.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/custom-modal.tsx @@ -11,6 +11,19 @@ import { useCurrentModal } from "./hooks" export const PlainModal = ({ children }: PropsWithChildren) => children +export const PlainWithAnimationModal = ({ children }: PropsWithChildren) => { + return ( + + {children} + + ) +} + export { PlainModal as NoopChildren } type ModalTemplateType = { diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx index 511d8b244..0d0f82b2b 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx @@ -2,6 +2,7 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { FeedViewType, getView, UserRole } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" import { useIsEntryStarred } from "@follow/store/collection/hooks" +import { isOnboardingEntryUrl } from "@follow/store/constants/onboarding" import { useEntry } from "@follow/store/entry/hooks" import { entrySyncServices } from "@follow/store/entry/store" import type { EntryModel } from "@follow/store/entry/types" @@ -265,6 +266,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed const shortcuts = useCommandShortcuts() const isCurrentVisitEntry = routeEntryId === entryId + const isOnboardingEntry = isOnboardingEntryUrl(entry?.url) const actionConfigs: EntryActionItem[] = useMemo(() => { if (!hasEntry) return [] @@ -334,6 +336,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed onClick: runCmdFn(COMMAND_ID.entry.copyLink, [{ entryId }]), hide: !entry.url, shortcut: shortcuts[COMMAND_ID.entry.copyLink], + disabled: isOnboardingEntry, entryId, }), new EntryActionMenuItem({ @@ -346,6 +349,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed id: COMMAND_ID.entry.imageGallery, hide: entry.imagesLength <= 5, onClick: runCmdFn(COMMAND_ID.entry.imageGallery, [{ entryId }]), + disabled: isOnboardingEntry, entryId, }), new EntryActionMenuItem({ @@ -353,6 +357,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed hide: !entry.url, onClick: runCmdFn(COMMAND_ID.entry.openInBrowser, [{ entryId }]), shortcut: shortcuts[COMMAND_ID.entry.openInBrowser], + disabled: isOnboardingEntry, entryId, }), new EntryActionMenuItem({ @@ -362,6 +367,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed ]), hide: isMobile() || !entry.url, active: isShowSourceContent, + disabled: isOnboardingEntry, entryId, }), new EntryActionMenuItem({ @@ -423,6 +429,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed hide: !!entry.readability || (view && getView(view)?.wideMode) || !entry.url, active: isEntryInReadability, notice: !entry.doesContentContainsHTMLTags && !isEntryInReadability, + disabled: isOnboardingEntry, entryId, }), @@ -492,6 +499,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view: Feed isEntryInReadability, integrationSettings.customIntegration, integrationSettings.enableCustomIntegration, + isOnboardingEntry, ]) return actionConfigs diff --git a/apps/desktop/layer/renderer/src/modules/new-user-guide/ai-chat-pane.tsx b/apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-chat-pane.tsx similarity index 92% rename from apps/desktop/layer/renderer/src/modules/new-user-guide/ai-chat-pane.tsx rename to apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-chat-pane.tsx index a483984c0..f940ad9f1 100644 --- a/apps/desktop/layer/renderer/src/modules/new-user-guide/ai-chat-pane.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-chat-pane.tsx @@ -1,4 +1,4 @@ -import { Logo } from "@follow/components/icons/logo.jsx" +import { Folo } from "@follow/components/icons/folo.js" import { Button } from "@follow/components/ui/button/index.js" import type { LexicalRichEditorRef } from "@follow/components/ui/lexical-rich-editor/index.js" import { @@ -10,7 +10,7 @@ import { useIsDark } from "@follow/hooks" import { tracker } from "@follow/tracker" import { nextFrame } from "@follow/utils" import { cn } from "@follow/utils/utils" -import { AnimatePresence } from "framer-motion" +import { AnimatePresence, m } from "framer-motion" import { useSetAtom } from "jotai" import type { EditorState } from "lexical" import { $getRoot, $getSelection, $isRangeSelection, createEditor } from "lexical" @@ -19,7 +19,7 @@ import type { RefObject } from "react" import { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react" import { useEventCallback } from "usehooks-ts" -import { useDialog } from "~/components/ui/modal/stacked/hooks" +import { GlassButton } from "~/components/ui/button/GlassButton" import { useI18n } from "~/hooks/common" import { ChatInput } from "~/modules/ai-chat/components/layouts/ChatInput" import { useAttachScrollBeyond } from "~/modules/ai-chat/hooks/useAttachScrollBeyond" @@ -103,17 +103,14 @@ function pickSuggestionKeys(previous?: readonly SuggestionKey[]): SuggestionKey[ export function AIChatPane() { return ( -
+
) } function AIChatPaneImpl() { - const t = useI18n() - const setStep = useSetAtom(stepAtom) - const dialog = useDialog() const hasMessages = useHasMessages() const chatInputRef = useRef(null) @@ -148,32 +145,18 @@ function AIChatPaneImpl() { } const handleSkip = useEventCallback(async () => { - const confirmed = await dialog.ask({ - title: t.app("new_user_guide.confirm_skip.title") as string, - message: t.app("new_user_guide.confirm_skip.message") as string, - confirmText: t.app("new_user_guide.actions.skip") as string, - }) - - if (!confirmed) { - return - } - - setStep("skip-pre-finish") + setStep("finish") }) return (
-
- +
- - - + AI
+ + +
@@ -205,10 +188,16 @@ function Welcome({ onSuggestionClick }: WelcomeProps) { }) return ( -
-
-
-

+ +

+
+

{(t.app("new_user_guide.ai_chat.intro") as string).split("\n").map((line) => ( {line} @@ -219,7 +208,7 @@ function Welcome({ onSuggestionClick }: WelcomeProps) {

-
+

{t.app("new_user_guide.ai_chat.you_can_say")} @@ -237,7 +226,7 @@ function Welcome({ onSuggestionClick }: WelcomeProps) { onClickSuggestion(suggestionText)} - animationDelay={index * 0.05} + animationDelay={index * 0.05 + 0.2} className="font-normal text-text" style={{ background: gradient }} > @@ -249,11 +238,11 @@ function Welcome({ onSuggestionClick }: WelcomeProps) {

-
+ ) } -// if the chat response has `tool-onboardingGetTrendingFeedsTool`, set the step to pre-finish +// if the chat response has `tool-onboardingGetTrendingFeedsTool`, mark the flow as finished function FinishListener() { const chatMessages = useMessages() const setStep = useSetAtom(stepAtom) @@ -262,7 +251,7 @@ function FinishListener() { msg.parts.some((p) => p.type === "tool-onboardingGetTrendingFeeds"), ) if (hasCalledConfirmTool) { - setStep("pre-finish") + setStep("finish") } }, [chatMessages, setStep]) @@ -521,8 +510,8 @@ function AIChatInterface({ inputRef }: AIChatInterfaceProps) { status === "ready" && hasFeedsSelection && (
-
)} diff --git a/apps/desktop/layer/renderer/src/modules/new-user-guide/guide-modal-content.tsx b/apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-onboarding-modal-content.tsx similarity index 52% rename from apps/desktop/layer/renderer/src/modules/new-user-guide/guide-modal-content.tsx rename to apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-onboarding-modal-content.tsx index 1e597de5b..bd36e7b13 100644 --- a/apps/desktop/layer/renderer/src/modules/new-user-guide/guide-modal-content.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-onboarding/ai-onboarding-modal-content.tsx @@ -6,24 +6,21 @@ import { AIChatRoot } from "~/modules/ai-chat/components/layouts/AIChatRoot" import { settingSyncQueue } from "../settings/helper/sync-queue" import { AIChatPane } from "./ai-chat-pane" -import { DiscoverImportStep } from "./discover-import-step" import { FeedsSelectionList } from "./feeds-selection-list" import { stepAtom } from "./store" -export function GuideModalContent({ onClose }: { onClose: () => void }) { +export function AiOnboardingModalContent({ onClose }: { onClose: () => void }) { const step = useAtomValue(stepAtom) useEffect(() => { tracker.onBoarding({ stepV2: step, - done: step === "finish" || step === "manual-import-finish" || step === "skip-finish", + done: step === "finish", }) }, [step]) useEffect(() => { - if (step !== "finish" && step !== "manual-import-finish") { - return - } + if (step !== "finish") return const syncSettings = async () => { try { @@ -37,7 +34,7 @@ export function GuideModalContent({ onClose }: { onClose: () => void }) { }, [step]) useEffect(() => { - if (step === "finish" || step === "manual-import-finish" || step === "skip-finish") { + if (step === "finish") { onClose() } }, [onClose, step]) @@ -47,25 +44,29 @@ export function GuideModalContent({ onClose }: { onClose: () => void }) { case "intro": case "selecting-feeds": { return ( -
- - +
+ {/* Left side - Feed Selection (45% width on large screens) */} +
+ +
+ + {/* Gradient divider */} +
+ + {/* Right side - AI Chat (55% width on large screens) */} +
+ +
) } - case "manual-import": { - return - } - case "pre-finish": - case "manual-import-pre-finish": - case "skip-pre-finish": { - return null - } - case "finish": - case "manual-import-finish": - case "skip-finish": { - return null - } + default: { return null } @@ -76,8 +77,8 @@ export function GuideModalContent({ onClose }: { onClose: () => void }) { return ( -
-
{content}
+
+ {content}
) diff --git a/apps/desktop/layer/renderer/src/modules/new-user-guide/feeds-selection-list.tsx b/apps/desktop/layer/renderer/src/modules/ai-onboarding/feeds-selection-list.tsx similarity index 88% rename from apps/desktop/layer/renderer/src/modules/new-user-guide/feeds-selection-list.tsx rename to apps/desktop/layer/renderer/src/modules/ai-onboarding/feeds-selection-list.tsx index 13587538f..4ed2eaf35 100644 --- a/apps/desktop/layer/renderer/src/modules/new-user-guide/feeds-selection-list.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-onboarding/feeds-selection-list.tsx @@ -39,7 +39,7 @@ export function FeedsSelectionList() { }, [hasFeedsSelection, setStep]) return ( -
+
{hasFeedsSelection ? : } @@ -49,6 +49,7 @@ export function FeedsSelectionList() { function FeedSelectionOperationScreen() { const chatMessages = useMessages() + const t = useI18n() const feedsToSelect: FeedToSelect[] = useMemo(() => { // find the last message that has the tool @@ -100,6 +101,21 @@ function FeedSelectionOperationScreen() { [selectedAtoms, store], ) + if (items.length === 0) { + return ( +
+ + +

+ {t.app("new_user_guide.selection.empty_title")} +

+

+ {t.app("new_user_guide.selection.empty_description")} +

+
+ ) + } + return (
@@ -187,11 +203,11 @@ function FeedSelectionFirstScreen() { return (