diff --git a/packages/shared/src/components/MainFeedLayout.tsx b/packages/shared/src/components/MainFeedLayout.tsx index fa18faeb06..ba27bcda86 100644 --- a/packages/shared/src/components/MainFeedLayout.tsx +++ b/packages/shared/src/components/MainFeedLayout.tsx @@ -75,7 +75,6 @@ import { checkIsExtension } from '../lib/func'; import { useReadingReminderHero } from '../hooks/notifications/useReadingReminderHero'; import { useTrackQuestClientEvent } from '../hooks/useTrackQuestClientEvent'; import { useReadingReminderVariation } from '../hooks/notifications/useReadingReminderVariation'; -import { useNoAiFeed } from '../hooks/useNoAiFeed'; const FeedExploreHeader = dynamic( () => @@ -225,8 +224,6 @@ export default function MainFeedLayout({ hasUser: !!user, }); const { isCustomDefaultFeed, defaultFeedId } = useCustomDefaultFeed(); - const shouldEvaluateNoAi = - feedName === SharedFeedPage.MyFeed && !isCustomDefaultFeed; const isLaptop = useViewSize(ViewSize.Laptop); const feedVersion = useFeature(feature.feedVersion); const { time, contentCurationFilter } = useSearchContextProvider(); @@ -306,14 +303,6 @@ export default function MainFeedLayout({ feature: featureFeedV2Highlights, shouldEvaluate: shouldEvaluateFeedV2Highlights, }); - const { - isNoAi, - isNoAiAvailable, - isLoaded: isNoAiLoaded, - toggleNoAi, - } = useNoAiFeed({ - shouldEvaluate: shouldEvaluateNoAi, - }); const { isSearchPageLaptop } = useSearchResultsLayout(); @@ -389,7 +378,6 @@ export default function MainFeedLayout({ highlightsLimit: FEED_V2_HIGHLIGHTS_LIMIT, } : {}), - ...(shouldEvaluateNoAi && isNoAi ? { noAi: true } : {}), version: isDevelopment && !isProductionAPI ? 1 @@ -411,8 +399,6 @@ export default function MainFeedLayout({ tokenRefreshed, feedVersion, isFeedV2HighlightsEnabled, - isNoAi, - shouldEvaluateNoAi, ]); const [selectedAlgo, setSelectedAlgo, loadedAlgo] = usePersistentContext( @@ -465,10 +451,6 @@ export default function MainFeedLayout({ return null; } - if (shouldEvaluateNoAi && !isNoAiLoaded) { - return null; - } - if (feedNameProp === 'default' && isCustomDefaultFeed) { if (!defaultFeedId) { return null; @@ -491,15 +473,6 @@ export default function MainFeedLayout({ ), }; @@ -579,15 +552,6 @@ export default function MainFeedLayout({ ), }; @@ -619,11 +583,6 @@ export default function MainFeedLayout({ isLaptop, loadedAlgo, tokenRefreshed, - shouldEvaluateNoAi, - isNoAiLoaded, - isNoAiAvailable, - isNoAi, - toggleNoAi, ]); useEffect(() => { diff --git a/packages/shared/src/components/feeds/FeedSettings/sections/FeedSettingsContentPreferencesSection.tsx b/packages/shared/src/components/feeds/FeedSettings/sections/FeedSettingsContentPreferencesSection.tsx index 3e4ff64c51..6e68f5fbac 100644 --- a/packages/shared/src/components/feeds/FeedSettings/sections/FeedSettingsContentPreferencesSection.tsx +++ b/packages/shared/src/components/feeds/FeedSettings/sections/FeedSettingsContentPreferencesSection.tsx @@ -3,9 +3,6 @@ import React, { useContext, useMemo } from 'react'; import { FeedSettingsEditContext } from '../FeedSettingsEditContext'; import useFeedSettings from '../../../../hooks/useFeedSettings'; import { useAdvancedSettings } from '../../../../hooks/feed/useAdvancedSettings'; -import { useConditionalFeature, useToastNotification } from '../../../../hooks'; -import { useLogContext } from '../../../../contexts/LogContext'; -import { useSettingsContext } from '../../../../contexts/SettingsContext'; import { getAdvancedContentTypes, getContentCurationList, @@ -17,12 +14,7 @@ import { TypographyType, } from '../../../typography/Typography'; import { FilterCheckbox } from '../../../fields/FilterCheckbox'; -import { Switch } from '../../../fields/Switch'; import { FeedType } from '../../../../graphql/feed'; -import { featureNoAiFeed } from '../../../../lib/featureManagement'; -import { SidebarSettingsFlags } from '../../../../graphql/settings'; -import { labels } from '../../../../lib/labels'; -import { LogEvent, Origin, TargetId } from '../../../../lib/log'; export const TOGGLEABLE_TYPES = ['Videos', 'Polls', 'Social']; const CUSTOM_FEEDS_ONLY = ['Article']; @@ -30,9 +22,6 @@ const ADVANCED_SETTINGS_KEY = 'advancedSettings'; export const FeedSettingsContentPreferencesSection = (): ReactElement => { const { feed, editFeedSettings } = useContext(FeedSettingsEditContext); - const { flags, updateFlag } = useSettingsContext(); - const { displayToast } = useToastNotification(); - const { logEvent } = useLogContext(); const { advancedSettings } = useFeedSettings({ feedId: feed?.id }); const { selectedSettings, @@ -40,10 +29,6 @@ export const FeedSettingsContentPreferencesSection = (): ReactElement => { checkSourceBlocked, onToggleSource, } = useAdvancedSettings({ feedId: feed?.id }); - const { value: isNoAiFeatureEnabled } = useConditionalFeature({ - feature: featureNoAiFeed, - shouldEvaluate: feed?.type === FeedType.Main, - }); const toggleableTypes = useMemo( () => getAdvancedContentTypes( @@ -113,47 +98,6 @@ export const FeedSettingsContentPreferencesSection = (): ReactElement => { })} - {feed?.type === FeedType.Main && isNoAiFeatureEnabled && ( -
-
- - No AI mode - - - Keep AI topics filtered out across My Feed. You can hide the - homepage toggle once this is set. - -
- { - const newState = !(flags?.noAiFeedEnabled ?? false); - - editFeedSettings(() => - updateFlag(SidebarSettingsFlags.NoAiFeedEnabled, newState), - ); - displayToast( - newState ? labels.feed.noAi.hidden : labels.feed.noAi.visible, - ); - logEvent({ - event_name: LogEvent.ToggleNoAiFeed, - target_id: newState ? TargetId.On : TargetId.Off, - extra: JSON.stringify({ - origin: Origin.Settings, - }), - }); - }} - > - Keep AI topics filtered out - -
- )}
diff --git a/packages/shared/src/components/layout/common.spec.tsx b/packages/shared/src/components/layout/common.spec.tsx index 038e969c59..505797184c 100644 --- a/packages/shared/src/components/layout/common.spec.tsx +++ b/packages/shared/src/components/layout/common.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import SettingsContext from '../../contexts/SettingsContext'; import { useAuthContext } from '../../contexts/AuthContext'; import { useLogContext } from '../../contexts/LogContext'; @@ -10,7 +10,6 @@ import { useFeedName } from '../../hooks/feed/useFeedName'; import { useQueryState } from '../../hooks/utils/useQueryState'; import { checkIsExtension, getCurrentBrowserName } from '../../lib/func'; import { ActionType } from '../../graphql/actions'; -import { LogEvent, Origin, TargetId } from '../../lib/log'; import { SharedFeedPage } from '../utilities'; import { SearchControlHeader } from './common'; @@ -96,12 +95,10 @@ const mockGetCurrentBrowserName = getCurrentBrowserName as jest.Mock; const createActionsState = ({ dismissedInstallExtension = false, - dismissedNoAiToggle = false, isActionsFetched = true, completeAction = jest.fn(), }: { dismissedInstallExtension?: boolean; - dismissedNoAiToggle?: boolean; isActionsFetched?: boolean; completeAction?: jest.Mock; } = {}) => ({ @@ -110,31 +107,18 @@ const createActionsState = ({ return dismissedInstallExtension; } - if (type === ActionType.DismissNoAiFeedToggle) { - return dismissedNoAiToggle; - } - return false; }), completeAction, isActionsFetched, }); -const renderComponent = ({ - noAiState, -}: { - noAiState?: { - isAvailable: boolean; - isEnabled: boolean; - onToggle: () => Promise; - }; -} = {}) => +const renderComponent = () => render( , ); @@ -219,103 +203,4 @@ describe('SearchControlHeader', () => { screen.getByRole('link', { name: 'Get it for Chrome' }), ).toBeInTheDocument(); }); - - it('does not render the No AI switch when unavailable', () => { - mockUseActions.mockReturnValue( - createActionsState({ dismissedInstallExtension: true }), - ); - - renderComponent({ - noAiState: { - isAvailable: false, - isEnabled: false, - onToggle: jest.fn().mockResolvedValue(undefined), - }, - }); - - expect(screen.queryByText('No AI mode')).not.toBeInTheDocument(); - expect( - screen.queryByRole('checkbox', { name: 'Toggle No AI mode' }), - ).not.toBeInTheDocument(); - }); - - it('renders a No AI switch and logs when toggled', async () => { - const onToggle = jest.fn().mockResolvedValue(undefined); - const logEvent = jest.fn(); - mockUseLogContext.mockReturnValue({ logEvent }); - mockUseActions.mockReturnValue( - createActionsState({ dismissedInstallExtension: true }), - ); - - renderComponent({ - noAiState: { - isAvailable: true, - isEnabled: false, - onToggle, - }, - }); - - expect(screen.getByText('No AI mode')).toBeInTheDocument(); - const switchInput = screen.getByRole('checkbox', { - name: 'Toggle No AI mode', - }); - fireEvent.click(switchInput); - - expect(onToggle).toHaveBeenCalledTimes(1); - await waitFor(() => { - expect(logEvent).toHaveBeenCalledWith({ - event_name: LogEvent.ToggleNoAiFeed, - target_id: TargetId.On, - extra: JSON.stringify({ - origin: Origin.Feed, - }), - }); - }); - }); - - it('does not render the No AI switch after dismissal', () => { - mockUseActions.mockReturnValue( - createActionsState({ - dismissedInstallExtension: true, - dismissedNoAiToggle: true, - }), - ); - - renderComponent({ - noAiState: { - isAvailable: true, - isEnabled: false, - onToggle: jest.fn().mockResolvedValue(undefined), - }, - }); - - expect(screen.queryByText('No AI mode')).not.toBeInTheDocument(); - expect( - screen.queryByRole('checkbox', { name: 'Toggle No AI mode' }), - ).not.toBeInTheDocument(); - }); - - it('dismisses the No AI header card', () => { - const completeAction = jest.fn(); - mockUseActions.mockReturnValue( - createActionsState({ - dismissedInstallExtension: true, - completeAction, - }), - ); - - renderComponent({ - noAiState: { - isAvailable: true, - isEnabled: false, - onToggle: jest.fn().mockResolvedValue(undefined), - }, - }); - - fireEvent.click(screen.getByRole('button', { name: 'Dismiss No AI mode' })); - - expect(completeAction).toHaveBeenCalledWith( - ActionType.DismissNoAiFeedToggle, - ); - }); }); diff --git a/packages/shared/src/components/layout/common.tsx b/packages/shared/src/components/layout/common.tsx index f9a0b5816c..41b5d0c98f 100644 --- a/packages/shared/src/components/layout/common.tsx +++ b/packages/shared/src/components/layout/common.tsx @@ -31,13 +31,9 @@ import { useReadingStreak } from '../../hooks/streaks'; import type { AllFeedPages } from '../../lib/query'; import { QueryStateKeys, useQueryState } from '../../hooks/utils/useQueryState'; import type { AllowedTags, TypographyProps } from '../typography/Typography'; -import { - Typography, - TypographyColor, - TypographyType, -} from '../typography/Typography'; +import { Typography } from '../typography/Typography'; import { ToggleClickbaitShield } from '../buttons/ToggleClickbaitShield'; -import { LogEvent, Origin, TargetId } from '../../lib/log'; +import { LogEvent, Origin } from '../../lib/log'; import { AchievementTrackerButton } from '../filters/AchievementTrackerButton'; import { ActionType } from '../../graphql/actions'; import { @@ -47,23 +43,14 @@ import { isNullOrUndefined, } from '../../lib/func'; import { downloadBrowserExtension } from '../../lib/constants'; -import { cloudinaryNoAiFeedToggle } from '../../lib/image'; import { anchorDefaultRel } from '../../lib/strings'; import ConditionalWrapper from '../ConditionalWrapper'; -import { LazyImage } from '../LazyImage'; -import { Switch } from '../fields/Switch'; -import { Tooltip } from '../tooltip/Tooltip'; type State = [T, Dispatch>]; export interface SearchControlHeaderProps { feedName: AllFeedPages; algoState: State; - noAiState?: { - isAvailable: boolean; - isEnabled: boolean; - onToggle: () => void | Promise; - }; } export const LayoutHeader = classed( @@ -85,18 +72,10 @@ export const periodTexts = periods.map((period) => period.text); export const DEFAULT_ALGORITHM_KEY = 'feed:algorithm'; export const DEFAULT_ALGORITHM_INDEX = 0; -const noAiToggleTooltip = ( - <> - For when you are tired of AI launches, hot takes, -
- and vibe-coding discourse taking over your feed. - -); export const SearchControlHeader = ({ feedName, algoState: [selectedAlgo, setSelectedAlgo], - noAiState, }: SearchControlHeaderProps): ReactElement | null => { const [selectedPeriod, setSelectedPeriod] = useQueryState({ key: [QueryStateKeys.FeedPeriod], @@ -139,9 +118,6 @@ export const SearchControlHeader = ({ const hasDismissedInstallExtension = checkHasCompleted( ActionType.DismissInstallExtension, ); - const hasDismissedNoAiFeedToggle = checkHasCompleted( - ActionType.DismissNoAiFeedToggle, - ); const canInstallExtension = !checkIsExtension() && isNullOrUndefined(user?.flags?.lastExtensionUse); const shouldShowInstallExtensionPrompt = @@ -212,66 +188,7 @@ export const SearchControlHeader = ({ ), hasFeedActions && , ]; - const shouldShowNoAiControl = - noAiState?.isAvailable && isActionsFetched && !hasDismissedNoAiFeedToggle; - const noAiControl = shouldShowNoAiControl - ? (() => { - const handleNoAiToggle = async () => { - const isEnabled = !noAiState.isEnabled; - await noAiState.onToggle(); - logEvent({ - event_name: LogEvent.ToggleNoAiFeed, - target_id: isEnabled ? TargetId.On : TargetId.Off, - extra: JSON.stringify({ - origin: Origin.Feed, - }), - }); - }; - - return ( - - -
- -
- - No AI mode - -
- -
-
-