Skip to content

Commit

Permalink
Add AI proof read
Browse files Browse the repository at this point in the history
  • Loading branch information
arjunkomath committed Mar 2, 2024
1 parent 19606d4 commit e475ba3
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 1 deletion.
153 changes: 153 additions & 0 deletions apps/web/components/dialogs/ai-prood-read-dialog.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { SpinnerWithSpacing } from "@changes-page/ui";
import { convertMarkdownToPlainText } from "@changes-page/utils";
import { Dialog, Transition } from "@headlessui/react";
import { LightningBoltIcon } from "@heroicons/react/solid";
import { Fragment, useCallback, useEffect, useRef, useState } from "react";
import { getStreamingUrl } from "../../utils/useAiAssistant";
import { notifyError } from "../core/toast.component";

export default function AiProofReadDialog({ open, setOpen, content }) {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<string | null>(null);
const cancelButtonRef = useRef(null);

const proofRead = useCallback(async (text) => {
setLoading(true);

const { url } = await getStreamingUrl(
"wf_5a7eaceda859ee21c07771aaaecc9826"
);

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
content: text,
}),
});

if (!response.ok) {
notifyError("Too many requests");
}

const data = response.body;
if (!data) {
return;
}

const reader = data.getReader();
const decoder = new TextDecoder();
let done = false;

setLoading(false);

while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
setResult((prev) => (prev ?? "") + chunkValue);
}
}, []);

useEffect(() => {
if (open && content) {
setLoading(true);
setResult(null);

proofRead(convertMarkdownToPlainText(content)).catch(() => {
setLoading(false);
setOpen(false);
notifyError("Failed to process request, please contact support.");
});
}
}, [open, content]);

return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="fixed z-10 inset-0 overflow-y-auto"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-20 backdrop-blur transition-opacity" />
</Transition.Child>

{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white dark:bg-gray-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white dark:bg-gray-900 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left w-full">
<Dialog.Title
as="h3"
className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-50"
>
<LightningBoltIcon
className="h-6 w-6 text-indigo-600 inline-flex mr-2"
aria-hidden="true"
/>

{loading ? "Loading..." : "Here is the proofread result!"}
</Dialog.Title>

<div className="mt-5 w-full">
<div className="mt-1 space-y-1">
<dd className="mt-1 text-sm text-gray-900">
<div className="rounded-md border border-gray-200 dark:border-gray-600 dark:divide-gray-600 p-4">
{loading && <SpinnerWithSpacing />}

<p className="text-black dark:text-white whitespace-pre-wrap">
{result}
</p>
</div>
</dd>
</div>
</div>
</div>
</div>
</div>

<div className="bg-gray-50 dark:bg-gray-900 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-700 shadow-sm px-4 py-2 text-base font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpen(false)}
ref={cancelButtonRef}
>
Close
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}
32 changes: 31 additions & 1 deletion apps/web/components/forms/post-form.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { PrimaryButton } from "../core/buttons.component";
import MarkdownEditor from "../core/editor.component";
import { notifyError, notifySuccess } from "../core/toast.component";
import AiExpandConceptPromptDialogComponent from "../dialogs/ai-expand-concept-prompt-dialog.component";
import AiProofReadDialog from "../dialogs/ai-prood-read-dialog.component";
import AiSuggestTitlePromptDialogComponent from "../dialogs/ai-suggest-title-prompt-dialog.component";
import DateTimePromptDialog from "../dialogs/date-time-prompt-dialog.component";
import SwitchComponent from "./switch.component";
Expand Down Expand Up @@ -75,6 +76,7 @@ export default function PostFormComponent({
const [promptSchedule, setPromptSchedule] = useState(false);
const [promptTitleSuggestions, setPromptTitleSuggestions] = useState(false);
const [promptExpandConcept, setPromptExpandConcept] = useState(false);
const [promptProofRead, setPromptProofRead] = useState(false);
//
// For email notifications
const [emailNotified, setEmailNotified] = useState(false);
Expand Down Expand Up @@ -181,6 +183,14 @@ export default function PostFormComponent({
}
}, [formik.values.content]);

const proofRead = useCallback(() => {
if (formik.values.content) {
setPromptProofRead(true);
} else {
notifyError("Content cannot be empty");
}
}, [formik.values.content]);

if (loading) {
return <SpinnerWithSpacing />;
}
Expand Down Expand Up @@ -327,7 +337,7 @@ export default function PostFormComponent({
>
<Menu.Items
style={{
top: -106,
top: -146,
}}
className="absolute right-0 z-10 mt-1 max-h-56 w-52 overflow-auto rounded-lg bg-white dark:bg-gray-950 py-3 text-base shadow ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
>
Expand All @@ -353,6 +363,17 @@ export default function PostFormComponent({
Expand concept
</button>
</Menu.Item>
<Menu.Item>
<button
type="button"
onClick={proofRead}
className={classNames(
"block w-full text-left bg-white dark:bg-gray-950 select-none py-2 px-3 dark:text-gray-100 hover:bg-gray-200 hover:dark:bg-gray-800 cursor-pointer truncate font-medium"
)}
>
Proof read
</button>
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
Expand Down Expand Up @@ -625,6 +646,15 @@ export default function PostFormComponent({
);
}}
/>

<AiProofReadDialog
content={formik.values.content}
open={promptProofRead}
setOpen={(open: boolean) => {
setPromptProofRead(open);
track("AiProofRead");
}}
/>
</div>
);
}

0 comments on commit e475ba3

Please sign in to comment.