+
+ Level up how you interact with posts using AI-powered prompts. Extract
+ insights, refine content, or run custom instructions to get more out
+ of every post in one click.
+
+
-
- Preferred language
-
-
- Choose your preferred language for the post titles on the feed
-
-
- {
- onLanguageChange(value);
-
- displayToast(
- labels.feed.settings.globalPreferenceNotice.contentLanguage,
- );
- }}
- icon={null}
- />
-
+
>
);
};
diff --git a/packages/shared/src/components/icons/CustomPrompt/filled.svg b/packages/shared/src/components/icons/CustomPrompt/filled.svg
new file mode 100644
index 0000000000..5f0275b0a4
--- /dev/null
+++ b/packages/shared/src/components/icons/CustomPrompt/filled.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/shared/src/components/icons/CustomPrompt/index.tsx b/packages/shared/src/components/icons/CustomPrompt/index.tsx
new file mode 100644
index 0000000000..12013d0dec
--- /dev/null
+++ b/packages/shared/src/components/icons/CustomPrompt/index.tsx
@@ -0,0 +1,10 @@
+import type { ReactElement } from 'react';
+import React from 'react';
+import type { IconProps } from '../../Icon';
+import Icon from '../../Icon';
+import OutlinedIcon from './outlined.svg';
+import FilledIcon from './filled.svg';
+
+export const CustomPromptIcon = (props: IconProps): ReactElement => (
+
+);
diff --git a/packages/shared/src/components/icons/CustomPrompt/outlined.svg b/packages/shared/src/components/icons/CustomPrompt/outlined.svg
new file mode 100644
index 0000000000..88dbaaa14f
--- /dev/null
+++ b/packages/shared/src/components/icons/CustomPrompt/outlined.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/shared/src/components/icons/EditPrompt/filled.svg b/packages/shared/src/components/icons/EditPrompt/filled.svg
new file mode 100644
index 0000000000..50831dfe79
--- /dev/null
+++ b/packages/shared/src/components/icons/EditPrompt/filled.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/shared/src/components/icons/EditPrompt/index.tsx b/packages/shared/src/components/icons/EditPrompt/index.tsx
new file mode 100644
index 0000000000..9fe1983544
--- /dev/null
+++ b/packages/shared/src/components/icons/EditPrompt/index.tsx
@@ -0,0 +1,10 @@
+import type { ReactElement } from 'react';
+import React from 'react';
+import type { IconProps } from '../../Icon';
+import Icon from '../../Icon';
+import OutlinedIcon from './outlined.svg';
+import FilledIcon from './filled.svg';
+
+export const EditPromptIcon = (props: IconProps): ReactElement => (
+
+);
diff --git a/packages/shared/src/components/icons/EditPrompt/outlined.svg b/packages/shared/src/components/icons/EditPrompt/outlined.svg
new file mode 100644
index 0000000000..0005ac16c4
--- /dev/null
+++ b/packages/shared/src/components/icons/EditPrompt/outlined.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/shared/src/components/icons/TLDR/filled.svg b/packages/shared/src/components/icons/TLDR/filled.svg
new file mode 100644
index 0000000000..ee8f08331d
--- /dev/null
+++ b/packages/shared/src/components/icons/TLDR/filled.svg
@@ -0,0 +1,6 @@
+
diff --git a/packages/shared/src/components/icons/TLDR/index.tsx b/packages/shared/src/components/icons/TLDR/index.tsx
new file mode 100644
index 0000000000..16e65ab51b
--- /dev/null
+++ b/packages/shared/src/components/icons/TLDR/index.tsx
@@ -0,0 +1,10 @@
+import type { ReactElement } from 'react';
+import React from 'react';
+import type { IconProps } from '../../Icon';
+import Icon from '../../Icon';
+import OutlinedIcon from './outlined.svg';
+import FilledIcon from './filled.svg';
+
+export const TLDRIcon = (props: IconProps): ReactElement => (
+
+);
diff --git a/packages/shared/src/components/icons/TLDR/outlined.svg b/packages/shared/src/components/icons/TLDR/outlined.svg
new file mode 100644
index 0000000000..075d96a96f
--- /dev/null
+++ b/packages/shared/src/components/icons/TLDR/outlined.svg
@@ -0,0 +1,6 @@
+
diff --git a/packages/shared/src/components/icons/index.ts b/packages/shared/src/components/icons/index.ts
index b9a8517e7b..bc1d0b8d69 100644
--- a/packages/shared/src/components/icons/index.ts
+++ b/packages/shared/src/components/icons/index.ts
@@ -129,4 +129,7 @@ export * from './ShieldWarning';
export * from './ShieldPlus';
export * from './Sidebar';
export * from './Folder';
+export * from './EditPrompt';
+export * from './CustomPrompt';
+export * from './TLDR';
export * from './Privacy';
diff --git a/packages/shared/src/components/modals/common.tsx b/packages/shared/src/components/modals/common.tsx
index 45ccf4f0ba..bdb040887a 100644
--- a/packages/shared/src/components/modals/common.tsx
+++ b/packages/shared/src/components/modals/common.tsx
@@ -214,6 +214,12 @@ const AddToCustomFeedModal = dynamic(
),
);
+const SmartPromptModal = dynamic(() =>
+ import(
+ /* webpackChunkName: "smartPromptModal" */ './plus/SmartPromptModal'
+ ).then((mod) => mod.SmartPromptModal),
+);
+
const CookieConsentModal = dynamic(
() =>
import(
@@ -264,6 +270,7 @@ export const modals = {
[LazyModal.ClickbaitShield]: ClickbaitShieldModal,
[LazyModal.MoveBookmark]: MoveBookmarkModal,
[LazyModal.AddToCustomFeed]: AddToCustomFeedModal,
+ [LazyModal.SmartPrompt]: SmartPromptModal,
[LazyModal.CookieConsent]: CookieConsentModal,
[LazyModal.ReportUser]: ReportUserModal,
};
diff --git a/packages/shared/src/components/modals/common/types.ts b/packages/shared/src/components/modals/common/types.ts
index 50a9914e82..2dcc154984 100644
--- a/packages/shared/src/components/modals/common/types.ts
+++ b/packages/shared/src/components/modals/common/types.ts
@@ -62,6 +62,7 @@ export enum LazyModal {
AddToCustomFeed = 'addToCustomFeed',
CookieConsent = 'cookieConsent',
ReportUser = 'reportUser',
+ SmartPrompt = 'smartPrompt',
}
export type ModalTabItem = {
diff --git a/packages/shared/src/components/modals/plus/SmartPromptModal.tsx b/packages/shared/src/components/modals/plus/SmartPromptModal.tsx
new file mode 100644
index 0000000000..4b00d30428
--- /dev/null
+++ b/packages/shared/src/components/modals/plus/SmartPromptModal.tsx
@@ -0,0 +1,62 @@
+import type { ReactElement } from 'react';
+import React from 'react';
+import type { ModalProps } from '../common/Modal';
+import { Modal } from '../common/Modal';
+import { Image } from '../../image/Image';
+import { smartPromptModalImage } from '../../../lib/image';
+import {
+ Typography,
+ TypographyColor,
+ TypographyType,
+} from '../../typography/Typography';
+import { PlusUser } from '../../PlusUser';
+import { Button, ButtonVariant } from '../../buttons/Button';
+import { webappUrl } from '../../../lib/constants';
+import { DevPlusIcon } from '../../icons';
+import { LogEvent, TargetId } from '../../../lib/log';
+import { usePlusSubscription } from '../../../hooks';
+
+export const SmartPromptModal = ({ ...props }: ModalProps): ReactElement => {
+ const { logSubscriptionEvent } = usePlusSubscription();
+ return (
+
+
+
+
+
+ Smart Prompts
+
+
+
+
+
+ Level up how you interact with posts using AI-powered prompts. Extract
+ insights, refine content, or run custom instructions to get more out
+ of every post in one click.
+
+ }
+ onClick={async () => {
+ logSubscriptionEvent({
+ event_name: LogEvent.UpgradeSubscription,
+ target_id: TargetId.SmartPrompt,
+ });
+ }}
+ >
+ Upgrade to Plus
+
+
+
+ );
+};
diff --git a/packages/shared/src/components/plus/ClickbaitTrial.tsx b/packages/shared/src/components/plus/PostUpgradeToPlus.tsx
similarity index 62%
rename from packages/shared/src/components/plus/ClickbaitTrial.tsx
rename to packages/shared/src/components/plus/PostUpgradeToPlus.tsx
index 30ac12626e..cc37054f6b 100644
--- a/packages/shared/src/components/plus/ClickbaitTrial.tsx
+++ b/packages/shared/src/components/plus/PostUpgradeToPlus.tsx
@@ -1,5 +1,6 @@
-import type { ReactElement } from 'react';
-import React, { useState } from 'react';
+import type { PropsWithChildren, ReactElement } from 'react';
+import React, { useCallback, useState } from 'react';
+import classNames from 'classnames';
import { PlusUser } from '../PlusUser';
import CloseButton from '../CloseButton';
import { ButtonSize, ButtonVariant } from '../buttons/common';
@@ -12,26 +13,48 @@ import { Button } from '../buttons/Button';
import { webappUrl } from '../../lib/constants';
import { DevPlusIcon } from '../icons';
import { usePlusSubscription } from '../../hooks';
-import { LogEvent, TargetId } from '../../lib/log';
+import type { TargetId } from '../../lib/log';
+import { LogEvent } from '../../lib/log';
-export const ClickbaitTrial = (): ReactElement => {
+type PostUpgradeToPlusProps = {
+ targetId: TargetId;
+ title: ReactElement | string;
+ className?: string;
+ onClose?: () => void;
+};
+
+export const PostUpgradeToPlus = ({
+ targetId: target_id,
+ title,
+ children,
+ className,
+ onClose,
+}: PostUpgradeToPlusProps & PropsWithChildren): ReactElement => {
const [show, setShow] = useState(true);
const { logSubscriptionEvent } = usePlusSubscription();
+ const onCloseClick = useCallback(() => {
+ onClose?.();
+ setShow(false);
+ }, [onClose]);
+
if (!show) {
return null;
}
return (
-
{
color={TypographyColor.Primary}
className="py-2"
>
- Want to automatically optimize titles across your feed?
-
-
- Clickbait Shield uses AI to automatically optimize post titles by fixing
- common problems like clickbait, lack of clarity, and overly promotional
- language.
-
-
- The result is clearer, more informative titles that help you quickly
- find the content you actually need.
+ {title}
+ {children}
diff --git a/packages/shared/src/components/post/PostContent.tsx b/packages/shared/src/components/post/PostContent.tsx
index fab1cc4491..7e84e243b7 100644
--- a/packages/shared/src/components/post/PostContent.tsx
+++ b/packages/shared/src/components/post/PostContent.tsx
@@ -4,7 +4,6 @@ import React, { useEffect } from 'react';
import dynamic from 'next/dynamic';
import { isVideoPost } from '../../graphql/posts';
import PostMetadata from '../cards/common/PostMetadata';
-import PostSummary from '../cards/common/PostSummary';
import { PostWidgets } from './PostWidgets';
import { TagLinks } from '../TagLinks';
import PostToc from '../widgets/PostToc';
@@ -27,6 +26,7 @@ import { withPostById } from './withPostById';
import { PostClickbaitShield } from './common/PostClickbaitShield';
import { useSmartTitle } from '../../hooks/post/useSmartTitle';
import { SharedByUserBanner } from '../SharedByUserBanner';
+import { SmartPrompt } from './smartPrompts/SmartPrompt';
export const SCROLL_OFFSET = 80;
export const ONBOARDING_OFFSET = 120;
@@ -164,9 +164,7 @@ export function PostContentRaw({
className="mb-7"
/>
)}
- {post.summary && (
-
- )}
+ {post.summary && }
{
const { openModal } = useLazyModal();
@@ -57,7 +58,19 @@ export const PostClickbaitShield = ({ post }: { post: Post }): ReactElement => {
{fetchedSmartTitle ? (
<>
This title was optimized with Clickbait Shield
-
+
+ Clickbait Shield uses AI to automatically optimize post titles by
+ fixing common problems like clickbait, lack of clarity, and overly
+ promotional language.
+
+
+ The result is clearer, more informative titles that help you
+ quickly find the content you actually need.
+
>
) : (
<>
diff --git a/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx b/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx
new file mode 100644
index 0000000000..0db8fa92a9
--- /dev/null
+++ b/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx
@@ -0,0 +1,118 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import type { ReactElement } from 'react';
+import {
+ Button,
+ ButtonColor,
+ ButtonSize,
+ ButtonVariant,
+} from '../../buttons/Button';
+import type { Post } from '../../../graphql/posts';
+import { useSmartPrompt } from '../../../hooks/prompt/useSmartPrompt';
+import { usePromptsQuery } from '../../../hooks/prompt/usePromptsQuery';
+import { PromptDisplay } from '../../../graphql/prompt';
+import { SearchProgressBar } from '../../search';
+import { isNullOrUndefined } from '../../../lib/func';
+import Alert, { AlertType } from '../../widgets/Alert';
+import { labels } from '../../../lib';
+import { RenderMarkdown } from '../../RenderMarkdown';
+import { CopyIcon, EditIcon } from '../../icons';
+import { useCopyText } from '../../../hooks/useCopy';
+import { postLogEvent } from '../../../lib/feed';
+import { LogEvent } from '../../../lib/log';
+import { useLogContext } from '../../../contexts/LogContext';
+
+type CustomPromptProps = {
+ post: Post;
+};
+
+export const CustomPrompt = ({ post }: CustomPromptProps): ReactElement => {
+ const { logEvent } = useLogContext();
+ const { data: prompts } = usePromptsQuery();
+ const [isEdit, setIsEdit] = useState(false);
+ const [copying, copy] = useCopyText();
+ const prompt = useMemo(
+ () => prompts?.find((p) => p.id === PromptDisplay.CustomPrompt),
+ [prompts],
+ );
+ const { executePrompt, data, isPending } = useSmartPrompt({ post, prompt });
+ const onSubmitCustomPrompt = useCallback(
+ (e) => {
+ e.preventDefault();
+ logEvent(
+ postLogEvent(LogEvent.SmartPrompt, post, {
+ extra: { prompt: 'custom-prompt' },
+ }),
+ );
+ setIsEdit(false);
+ executePrompt(e.target[0].value);
+ },
+ [executePrompt, logEvent, post],
+ );
+
+ if (!data || isEdit) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ setActiveDisplay(PromptDisplay.TLDR);
+ }}
+ >
+ Level up how you interact with posts using AI-powered prompts.
+ Extract insights, refine content, or run custom instructions to get
+ more out of every post in one click.
+
+
+
+