Skip to content

Commit

Permalink
Bookmark summary generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucieo committed Oct 24, 2023
1 parent 2ee90c3 commit 7fb39a7
Show file tree
Hide file tree
Showing 17 changed files with 649 additions and 95 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@emotion/styled": "^11.10.6",
"@heroicons/react": "^2.0.16",
"@next-auth/prisma-adapter": "^1.0.5",
"@postlight/parser": "^2.2.3",
"@premieroctet/next-admin": "1.3.8",
"@prisma/client": "^4.11.0",
"@radix-ui/react-dialog": "^1.0.3",
Expand Down
12 changes: 12 additions & 0 deletions prisma/json-schema/json-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,18 @@
"string",
"null"
]
},
"subscriptionId": {
"type": [
"string",
"null"
]
},
"prompt": {
"type": [
"string",
"null"
]
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "teams" ADD COLUMN "subscriptionId" TEXT;
2 changes: 2 additions & 0 deletions prisma/migrations/20231024143643_team_prompt/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ADD COLUMN "prompt" TEXT;
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ model Team {
apiKey String?
nextSuggestedDigestTitle String?
color String?
subscriptionId String?
prompt String?
@@unique([id, slackTeamId])
@@unique(slug)
Expand Down
56 changes: 27 additions & 29 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,41 +88,39 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
disabled={disabled || isLoading}
{...rest}
>
{isLoading && (
<>
<span
role="status"
className="flex items-center justify-center position absolute"
>
{isLoading ? (
<div className="flex items-center gap-4">
{!!loadingText && loadingText}
<span role="status">
<LoadingIcon
className={clsx('animate-spin', iconVariants({ size }))}
aria-hidden="true"
/>
</span>
{!!loadingText && loadingText}
</>
</div>
) : (
<span className="flex items-center justify-center gap-2">
{children && (
<span
className={clsx({
'opacity-0 pointer-events-none': isLoading,
})}
>
{children}
</span>
)}
{icon && !isLoading && (
<span
className={clsx(
'flex items-center justify-center',
iconVariants({ size })
)}
>
{icon}
</span>
)}
</span>
)}
<span className="flex items-center justify-center gap-2">
{children && (
<span
className={clsx({
'opacity-0 pointer-events-none': isLoading,
})}
>
{children}
</span>
)}
{icon && !isLoading && (
<span
className={clsx(
'flex items-center justify-center',
iconVariants({ size })
)}
>
{icon}
</span>
)}
</span>
</button>
);
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const TextArea = forwardRef<
);
});

interface SelectOptionProps<T = string> {
export interface SelectOptionProps<T = string> {
label: string;
value: T;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default function BlockBookmarkCard({
...(title && { title }),
...(description && { description }),
}}
url={url}
/>
</>
);
Expand Down
13 changes: 12 additions & 1 deletion src/components/digests/dialog/EditBookmarkDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Button from '../../Button';
import { Dialog, DialogContent } from '../../Dialog';
import { Input, Select, TextArea } from '../../Input';
import { Props as BookmarkCardProps } from '../block-card/BlockCard';
import SummaryButton from './SummaryButton';

interface IFormValues {
title: string;
Expand All @@ -25,6 +26,7 @@ interface Props {
setIsOpen: (isOpen: boolean) => void;
bookmarkDigest: BookmarkCardProps['block'];
defaultValues?: Partial<IFormValues>;
url: string;
}

const StyleSelectOptions: Array<{
Expand All @@ -50,20 +52,22 @@ export default function EditBookmarkDialog({
setIsOpen,
bookmarkDigest,
defaultValues,
url,
}: Props) {
const {
register,
handleSubmit,
formState: { errors, isDirty },
reset,
setValue,
} = useForm<IFormValues>({
defaultValues,
});
const { successToast, errorToast } = useCustomToast();
const { isRefreshing, refresh } = useTransitionRefresh();

const params = useParams();
const { id: teamId } = useTeam();
const { id: teamId, subscriptionId } = useTeam();

const { mutate: updateBookDigest, isLoading: isRemoving } = useMutation<
AxiosResponse,
Expand Down Expand Up @@ -153,6 +157,13 @@ export default function EditBookmarkDialog({
<label htmlFor="description">Description</label>
<TextArea className="min-h-[10rem]" {...register('description')} />
</fieldset>
<SummaryButton
url={url}
handleSuccess={(text) =>
setValue('description', text, { shouldDirty: true })
}
hasAccess={!!subscriptionId}
/>
<fieldset className="flex flex-col gap-2 w-full">
<label htmlFor="style">
Style{' '}
Expand Down
64 changes: 64 additions & 0 deletions src/components/digests/dialog/SummaryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Button from '@/components/Button';
import { useTeam } from '@/contexts/TeamContext';
import useCustomToast from '@/hooks/useCustomToast';
import api from '@/lib/api';
import { LightBulbIcon } from '@heroicons/react/24/outline';
import { AxiosError, AxiosResponse } from 'axios';
import { useMutation } from 'react-query';

const SummaryButton = ({
url,
handleSuccess,
hasAccess,
}: {
url: string;
handleSuccess: (text: string) => void;
hasAccess: boolean;
}) => {
const { successToast, errorToast } = useCustomToast();
const { id: teamId } = useTeam();

const { mutate: generateSummary, isLoading } = useMutation<
AxiosResponse,
AxiosError<ErrorResponse>,
{ url: string }
>(
'generate-bookmark-summary',
({ url }) => {
return api.post(`/teams/${teamId}/bookmark/summary`, {
url,
});
},
{
onSuccess: (response) => {
successToast('Summary generated');
handleSuccess(response.data);
},
onError: (error: AxiosError<ErrorResponse>) => {
errorToast(error.response?.data.error || 'Something went wrong');
},
}
);

if (!hasAccess) return null;

return (
<div className="flex flex-col justify-start w-full">
<span className="pb-2">Lacking inspiration ?</span>
<div>
<Button
icon={<LightBulbIcon />}
onClick={() => {
generateSummary({ url });
}}
isLoading={isLoading}
loadingText="Generating"
>
Generate link summary
</Button>
</div>
</div>
);
};

export default SummaryButton;
20 changes: 16 additions & 4 deletions src/components/teams/form/settings/SettingsField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input, TextArea } from '@/components/Input';
import { Input, Select, TextArea } from '@/components/Input';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { FieldData } from './form-data';
Expand All @@ -13,7 +13,8 @@ export default function SettingsField({
registerOptions,
prefix,
rightElement,
leftElement,
selectDefault,
selectOptions,
}: FieldData) {
const {
register,
Expand All @@ -30,7 +31,17 @@ export default function SettingsField({

<div className="mt-2 w-full">
<>
{input === 'text' ? (
{input === 'select' && selectOptions?.length && (
<Select
className="sm:max-w-md"
{...register(id, {
...(!!registerOptions && registerOptions),
})}
defaultValue={selectDefault}
options={selectOptions}
/>
)}
{input === 'text' && (
<div className="px-3 py-1 flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 sm:max-w-md">
{!!prefix && (
<span className="flex select-none items-center text-gray-500 sm:text-base">
Expand All @@ -55,7 +66,8 @@ export default function SettingsField({
</span>
)}
</div>
) : (
)}
{input === 'textarea' && (
<TextArea
className="sm:max-w-md"
defaultValue={defaultValue || ''}
Expand Down
23 changes: 15 additions & 8 deletions src/components/teams/form/settings/SettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import updateTeamInfo from '@/actions/update-team-info';
import TeamColorField from './TeamColorField';
import useTransitionRefresh from '@/hooks/useTransitionRefresh';

const PRO_FIELDS = ['prompt'];

type SettingsForm = Record<FieldName, string>;

const SettingsForm = ({ team }: { team: Team }) => {
Expand All @@ -30,14 +32,14 @@ const SettingsForm = ({ team }: { team: Team }) => {
[FIELDS.github]: team?.github || '',
[FIELDS.twitter]: team?.twitter || '',
[FIELDS.color]: team?.color || '#6d28d9',
[FIELDS.prompt]: team?.prompt || 'english',
},
});

const {
handleSubmit,
reset,
formState: { isDirty, dirtyFields },
getValues,
} = methods;
const { refresh, isRefreshing } = useTransitionRefresh();

Expand Down Expand Up @@ -69,13 +71,18 @@ const SettingsForm = ({ team }: { team: Team }) => {
<form action={onSubmit}>
<div className="flex flex-col gap-6 pt-4">
<div className="flex flex-col gap-4">
{fieldsData.map((field) => (
<SettingsField
{...field}
key={field.id}
defaultValue={team[field.id] || ''}
/>
))}
{fieldsData
.filter(
(field) =>
team?.subscriptionId || !PRO_FIELDS?.includes(field?.id)
)
.map((field) => (
<SettingsField
{...field}
key={field.id}
defaultValue={team[field.id] || ''}
/>
))}

<TeamColorField id="color" label="Team Color" team={team} />

Expand Down
14 changes: 13 additions & 1 deletion src/components/teams/form/settings/form-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ImGithub } from '@react-icons/all-files/im/ImGithub';
import { ImTwitter } from '@react-icons/all-files/im/ImTwitter';
import { ImLink } from '@react-icons/all-files/im/ImLink';
import { RegisterOptions } from 'react-hook-form';
import { SelectOptionProps } from '@/components/Input';

export const FIELDS = {
bio: 'bio',
Expand All @@ -10,13 +11,14 @@ export const FIELDS = {
github: 'github',
twitter: 'twitter',
color: 'color',
prompt: 'prompt',
} as const;

export type FieldName = (typeof FIELDS)[keyof typeof FIELDS];

export interface FieldData {
id: FieldName;
input: 'text' | 'textarea';
input: 'text' | 'textarea' | 'select';
inputType: 'text' | 'email' | 'password' | 'url';
defaultValue?: string;
label: string;
Expand All @@ -25,6 +27,8 @@ export interface FieldData {
leftElement?: React.ReactNode;
rightElement?: React.ReactNode;
prefix?: string;
selectDefault?: string;
selectOptions?: SelectOptionProps[];
}

export const fieldsData: FieldData[] = [
Expand Down Expand Up @@ -77,4 +81,12 @@ export const fieldsData: FieldData[] = [
prefix: '@',
placeholder: '',
},
{
id: FIELDS.prompt,
input: 'textarea',
inputType: 'text',
label: 'Summary generator prompt',
placeholder:
'Add a custom prompt for bookmark summary generation. Your prompt will be followed by : + article content',
},
];
Loading

0 comments on commit 7fb39a7

Please sign in to comment.