From d38297aa1f6caca10b5bffb24e173a41af654760 Mon Sep 17 00:00:00 2001
From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com>
Date: Thu, 2 Jan 2025 16:02:40 +0100
Subject: [PATCH 01/14] feat: add `EditPrompt` icon
---
.../shared/src/components/icons/EditPrompt/filled.svg | 5 +++++
.../shared/src/components/icons/EditPrompt/index.tsx | 10 ++++++++++
.../src/components/icons/EditPrompt/outlined.svg | 4 ++++
packages/shared/src/components/icons/index.ts | 1 +
4 files changed, 20 insertions(+)
create mode 100644 packages/shared/src/components/icons/EditPrompt/filled.svg
create mode 100644 packages/shared/src/components/icons/EditPrompt/index.tsx
create mode 100644 packages/shared/src/components/icons/EditPrompt/outlined.svg
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/index.ts b/packages/shared/src/components/icons/index.ts
index 8a23faebd7..dd2beb3a00 100644
--- a/packages/shared/src/components/icons/index.ts
+++ b/packages/shared/src/components/icons/index.ts
@@ -127,3 +127,4 @@ export * from './ShieldWarning';
export * from './ShieldPlus';
export * from './Sidebar';
export * from './Folder';
+export * from './EditPrompt';
From 26e23b54fcc65d93efdd983a9cb0ab231732a152 Mon Sep 17 00:00:00 2001
From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com>
Date: Thu, 2 Jan 2025 16:02:49 +0100
Subject: [PATCH 02/14] feat: add `CustomPrompt` icon
---
.../src/components/icons/CustomPrompt/filled.svg | 10 ++++++++++
.../shared/src/components/icons/CustomPrompt/index.tsx | 10 ++++++++++
.../src/components/icons/CustomPrompt/outlined.svg | 10 ++++++++++
packages/shared/src/components/icons/index.ts | 1 +
4 files changed, 31 insertions(+)
create mode 100644 packages/shared/src/components/icons/CustomPrompt/filled.svg
create mode 100644 packages/shared/src/components/icons/CustomPrompt/index.tsx
create mode 100644 packages/shared/src/components/icons/CustomPrompt/outlined.svg
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/index.ts b/packages/shared/src/components/icons/index.ts
index dd2beb3a00..2039c917bd 100644
--- a/packages/shared/src/components/icons/index.ts
+++ b/packages/shared/src/components/icons/index.ts
@@ -128,3 +128,4 @@ export * from './ShieldPlus';
export * from './Sidebar';
export * from './Folder';
export * from './EditPrompt';
+export * from './CustomPrompt';
From a511e2c6d9a1fb9a395ed8df4396d4864a149789 Mon Sep 17 00:00:00 2001
From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com>
Date: Thu, 2 Jan 2025 16:02:56 +0100
Subject: [PATCH 03/14] feat: add `TLDR` icon
---
packages/shared/src/components/icons/TLDR/filled.svg | 6 ++++++
packages/shared/src/components/icons/TLDR/index.tsx | 10 ++++++++++
packages/shared/src/components/icons/TLDR/outlined.svg | 6 ++++++
packages/shared/src/components/icons/index.ts | 1 +
4 files changed, 23 insertions(+)
create mode 100644 packages/shared/src/components/icons/TLDR/filled.svg
create mode 100644 packages/shared/src/components/icons/TLDR/index.tsx
create mode 100644 packages/shared/src/components/icons/TLDR/outlined.svg
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 2039c917bd..7bb15fe47a 100644
--- a/packages/shared/src/components/icons/index.ts
+++ b/packages/shared/src/components/icons/index.ts
@@ -129,3 +129,4 @@ export * from './Sidebar';
export * from './Folder';
export * from './EditPrompt';
export * from './CustomPrompt';
+export * from './TLDR';
From 0a3f255f31577a41165c4c7f6a80de843ae17e2b Mon Sep 17 00:00:00 2001
From: Chris Bongers
Date: Fri, 3 Jan 2025 11:59:06 +0200
Subject: [PATCH 04/14] feat: setting addition (#4035)
---
.../FeedSettings/components/SmartPrompts.tsx | 121 ++++++++++++++++++
.../sections/FeedSettingsAISection.tsx | 3 +
.../shared/src/contexts/SettingsContext.tsx | 12 ++
packages/shared/src/graphql/prompt.ts | 29 +++++
packages/shared/src/graphql/settings.ts | 1 +
.../src/hooks/prompt/usePromptsQuery.ts | 40 ++++++
packages/shared/src/lib/labels.ts | 1 +
packages/shared/src/lib/log.ts | 3 +
packages/shared/src/lib/query.ts | 1 +
9 files changed, 211 insertions(+)
create mode 100644 packages/shared/src/components/feeds/FeedSettings/components/SmartPrompts.tsx
create mode 100644 packages/shared/src/graphql/prompt.ts
create mode 100644 packages/shared/src/hooks/prompt/usePromptsQuery.ts
diff --git a/packages/shared/src/components/feeds/FeedSettings/components/SmartPrompts.tsx b/packages/shared/src/components/feeds/FeedSettings/components/SmartPrompts.tsx
new file mode 100644
index 0000000000..c85739d552
--- /dev/null
+++ b/packages/shared/src/components/feeds/FeedSettings/components/SmartPrompts.tsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import type { ReactElement } from 'react';
+import {
+ Typography,
+ TypographyColor,
+ TypographyTag,
+ TypographyType,
+} from '../../../typography/Typography';
+import { PlusUser } from '../../../PlusUser';
+import ConditionalWrapper from '../../../ConditionalWrapper';
+import { SimpleTooltip } from '../../../tooltips';
+import { LogEvent, Origin, TargetId } from '../../../../lib/log';
+import { Button, ButtonSize, ButtonVariant } from '../../../buttons/Button';
+import { plusUrl } from '../../../../lib/constants';
+import { DevPlusIcon } from '../../../icons';
+import { usePlusSubscription, useToastNotification } from '../../../../hooks';
+import { usePromptsQuery } from '../../../../hooks/prompt/usePromptsQuery';
+import { FilterCheckbox } from '../../../fields/FilterCheckbox';
+import { useSettingsContext } from '../../../../contexts/SettingsContext';
+import { labels } from '../../../../lib';
+import { useLogContext } from '../../../../contexts/LogContext';
+
+export const SmartPrompts = (): ReactElement => {
+ const { isPlus, logSubscriptionEvent } = usePlusSubscription();
+ const { displayToast } = useToastNotification();
+ const { logEvent } = useLogContext();
+ const { flags, updatePromptFlag } = useSettingsContext();
+ const { prompt: promptFlags } = flags;
+ const { data: prompts, isLoading } = usePromptsQuery();
+
+ 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.
+
+
+
+
+ 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
+
+
{
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 b0141a9139..a7b1f40691 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();
@@ -61,7 +62,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/PromptButtons.tsx b/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx
new file mode 100644
index 0000000000..7b95e7a073
--- /dev/null
+++ b/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx
@@ -0,0 +1,135 @@
+import React, { forwardRef, useState } from 'react';
+import type { ReactElement, Ref } from 'react';
+import { ColorName } from '../../../styles/colors';
+import { ArrowIcon, CustomPromptIcon } from '../../icons';
+import type { ButtonProps } from '../../buttons/Button';
+import {
+ Button,
+ ButtonIconPosition,
+ ButtonSize,
+ ButtonVariant,
+} from '../../buttons/Button';
+import { IconSize } from '../../Icon';
+import { usePromptsQuery } from '../../../hooks/prompt/usePromptsQuery';
+import { ElementPlaceholder } from '../../ElementPlaceholder';
+import type { PromptFlags } from '../../../graphql/prompt';
+import { PromptDisplay } from '../../../graphql/prompt';
+import { usePromptButtons } from '../../../hooks/feed/usePromptButtons';
+import { useViewSize, ViewSize } from '../../../hooks';
+import { SimpleTooltip } from '../../tooltips';
+import { promptColorMap, PromptIconMap } from './common';
+
+type PromptButtonProps = ButtonProps<'button'> & {
+ active: boolean;
+ flags: PromptFlags;
+};
+
+const PromptButton = forwardRef(
+ (
+ { children, flags, active, ...props }: PromptButtonProps,
+ ref?: Ref,
+ ): ReactElement => {
+ const PromptIcon = PromptIconMap[flags.icon] || CustomPromptIcon;
+ const variant = active ? ButtonVariant.Primary : ButtonVariant.Subtle;
+ const color = active ? flags.color : undefined;
+ return (
+
+ }
+ {...props}
+ ref={ref}
+ >
+ {children}
+
+ );
+ },
+);
+PromptButton.displayName = 'PromptButton';
+
+type PromptButtonsProps = {
+ activePrompt: string;
+ setActivePrompt: (prompt: string) => void;
+ width: number;
+};
+
+export const PromptButtons = ({
+ activePrompt,
+ setActivePrompt,
+ width,
+}: PromptButtonsProps): ReactElement => {
+ const isMobile = useViewSize(ViewSize.MobileL);
+ const [showAll, setShowAll] = useState(false);
+ const { data: prompts, isLoading } = usePromptsQuery();
+ const promptList = usePromptButtons({
+ prompts,
+ width,
+ offset: 82,
+ base: 16,
+ showAll: showAll || isMobile,
+ });
+
+ const promptsCount = prompts?.length || 0;
+ const remainingTags = promptsCount - promptList?.length;
+
+ if (isLoading) {
+ return (
+
+ );
+};
diff --git a/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx
new file mode 100644
index 0000000000..342d1e4675
--- /dev/null
+++ b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx
@@ -0,0 +1,90 @@
+import React, { useRef, useState } from 'react';
+import type { ReactElement } from 'react';
+import type { Post } from '../../../graphql/posts';
+import PostSummary from '../../cards/common/PostSummary';
+import { Tab, TabContainer } from '../../tabs/TabContainer';
+import { usePlusSubscription } from '../../../hooks';
+import { PromptButtons } from './PromptButtons';
+import { PromptDisplay } from '../../../graphql/prompt';
+import { PostUpgradeToPlus } from '../../plus/PostUpgradeToPlus';
+import { TargetId } from '../../../lib/log';
+import ShowMoreContent from '../../cards/common/ShowMoreContent';
+
+export const SmartPrompt = ({ post }: { post: Post }): ReactElement => {
+ const { isPlus, showPlusSubscription } = usePlusSubscription();
+ const [activeDisplay, setActiveDisplay] = useState(
+ PromptDisplay.TLDR,
+ );
+ const [activePrompt, setActivePrompt] = useState(PromptDisplay.TLDR);
+ const elementRef = useRef(null);
+ const width = elementRef?.current?.getBoundingClientRect()?.width || 0;
+
+ const onSetActivePrompt = (prompt: string) => {
+ setActivePrompt(prompt);
+ if (!isPlus && prompt !== PromptDisplay.TLDR) {
+ setActiveDisplay(PromptDisplay.UpgradeToPlus);
+ return;
+ }
+
+ switch (prompt) {
+ case PromptDisplay.TLDR:
+ setActiveDisplay(PromptDisplay.TLDR);
+ break;
+ case PromptDisplay.CustomPrompt:
+ setActiveDisplay(PromptDisplay.CustomPrompt);
+ break;
+ default:
+ setActiveDisplay(PromptDisplay.SmartPrompt);
+ break;
+ }
+ };
+
+ if (!showPlusSubscription) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Smart prompt - {activePrompt}
+
+
+
+
+
+
+
+ {
+ 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.
+
+
+
+