Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AI proof read #20

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 AiProofReadDialogComponent({ 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 AiProofReadDialogComponent 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({
);
}}
/>

<AiProofReadDialogComponent
content={formik.values.content}
open={promptProofRead}
setOpen={(open: boolean) => {
setPromptProofRead(open);
track("AiProofRead");
}}
/>
</div>
);
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "changes-page",
"version": "1.0.0",
"version": "1.1.0",
"scripts": {
"build": "pnpm --filter './packages/*' -r build",
"dev:page": "pnpm --filter './apps/page' -r dev",
Expand Down